Create a new portal
Prerequisite: You must be familiar with the Syntax used in Tutorials and have already created an extension.
In this tutorial, we will see what is required to create a new portal from scratch.
You still need to check the Portal XML reference as it
provides all customization possibilities, while
this tutorial does not.
You may read the Portal
customization Overview if the below tutorial is too detailled
for you.
Portal declaration
First you will have to declare that portal in XML.
-
You will specify, which users can access it and which cannot. But rather than specifying the Users, you will specify the profiles.
-
Note that the console, is a portal with a specific id (id=
backoffice
), but it works the same in terms of allowing or denying profiles.
Default declaration
Here is how it is declared within iTop out of the box:
- itop_design
-
<portals> <portal id="itop-portal"> <!-- This is the User Portal --> <...> <allow/> <!-- No specified profiles, means all allowed --> <deny/> <!-- No restriction, which explain why normal console users have access --> </portal> <portal id="backoffice"> <!-- This is the Console --> <...> <allow/> <!-- allow or deny can be used, combination is supported but wierd --> <deny> <!-- Users having one of the below profiles, will be denied --> <profile id="Portal user"/> </deny> <!-- deny takes precedence on allow --> </portal> </portals>
What do we want?
In this example, we will imagine that we want to create a new
portal, but keep also the standard User Portal
(id=itop-portal
) and of course keep the Console
When you start to have more than 2 portals, it's a bit more complicated to define profiles. You have to imagine all the cases, but a combination of allowed and denied profiles, can quickly end up in deadlock
The new portal must be restricted to users having a newly
created profile Extension Publisher
. Those users must
not have access to the console and may or may not
have access to the standard itop-portal, depending on if they have
Portal user
profile or not.
Users of my company, do not have Portal user
profile otherwise they could not access the console, which they
need to. But they still can access the itop-portal. Let's suppose
that I want to allow them to access the new portal also. In that
case I will create a new profile “XX employee” where XX is my
company name.
Possible declaration
Here is one way to declare the 3 portals, to achive the above requirements:
- itop_design
-
<portals> <portal id="backoffice" _delta="must_exist"> <deny _delta="redefine"> <profile id="Portal user"/> <profile id="Extension Publisher"/> </deny> </portal> <portal id="itop-portal" _delta="must_exist"> <allow _delta="redefine"> <profile id="Portal user"/> <profile id="XX employee"/> </allow> </portal> <portal id="extension-publisher-portal" _delta="define"> <url>pages/exec.php?exec_module=itop-portal-base&exec_page=index.php&portal_id=extension-publisher-portal</url> <rank>1.0</rank> <handler/> <allow> <profile id="Extension Publisher"/> <profile id="XX employee"/> </allow> <deny/> </portal> </portals>
Portal design
You need to specify:
-
classes
: which classes and objects will be displayed in the portal -
forms
: for each class, fields visibility and layout in creation, vizualisation and edition -
bricks
: define the menus and bricks allowing to navigate
Classes / Scopes
Scopes are required to specify explicitly the classes which will
be used in the portal. Unless you have defined a scope for a given
class, that class will never be visible in the
portal, regardless of your profile. You could well be
Administrator
that it would not be enough.
What user can see
-
A scope will define within a class, the instances of objects that the users can see.
-
The below example specify a read scope, which is applicable to all users accessing the portal, not because the id is “all” but because there is no <allowed_profiles> tag specified for this scope, which means all profiles are allowed.
- itop_design / module_designs / module_design@itop-portal / classes / class@Ticket / scopes
-
<scope id="all"> <oql_view><![CDATA[SELECT Ticket WHERE (Condition1) ]]></oql_view> </scope>
-
a scope can be limited to users having a particular profile, with the <allowed_profiles> tag.
-
If multiple scopes applies to a user, then they are combined with a UNION.
Example users with profile Portal power user
can
see Tickets defined by the scope “power”
- itop_design / module_designs / module_design@itop-portal / classes / class@Ticket / scopes
-
<scope id="power"> <oql_view><![CDATA[SELECT Ticket WHERE (Condition2) ]]></oql_view> <allowed_profiles> <allowed_profile id="Portal power user"/> </allowed_profiles> <scope>
But they can see also tickets included in the scope “all”, as a result they will see any Ticket returned by this OQL
SELECT Ticket WHERE (Condition1) UNION SELECT Ticket WHERE (Condition2)
Allowed organizations
limitation put on the
users:-
A portal user, might be restricted to his own organization, by setting
Allowed organizations
on his iTop user. That's quite common, especially for Service providers. Nevertheless, thisOrganizations
restriction defined at the user level, can be overwritten by a portal scope.-
It's not automatic and the scope must explicitely mention that the allowed organizations of the users must be ignored.
-
It applies for this scope and this one only.
-
An example of this tag can be found on the standard “User portal”, to enable users to see the catalogue of Services, despite they do not belong to the user's organization
-
- itop_design / module_designs / module_design@itop-portal / classes / class@Service / scopes
-
<class id="Service"> <scopes> <scope id="all"> <oql_view> <![CDATA[ SELECT Service AS s JOIN lnkCustomerContractToService AS l1 ON l1.service_id=s.id JOIN CustomerContract AS cc ON l1.customercontract_id=cc.id WHERE cc.org_id = :current_contact->org_id AND s.status != 'obsolete' ]]> </oql_view> <ignore_silos>true</ignore_silos> </scope> </scopes> </class>
Tip 1 Because of the UserProfileBrick which must be activated on any portal, the below scope must also be declared on every portal
- itop_design / module_designs / module_design@my-portal
-
<class id="User"> <scopes> <scope id="all"> <oql_view> <![CDATA[SELECT User AS U JOIN Person AS P ON U.contactid=P.id WHERE P.id = :current_contact_id]]> </oql_view> </scope> </scopes> </class>
Tip 2
read
on the special group All
classes (*)- itop_design / user_rights / profiles / profile@my-new-profiles / groups
-
<group id="*"> <actions> <action id="action:read">allow</action> </actions> </group>
Sidenote for Combodo customers: Classes Attachments
and
TagSetFieldDataFor_class__field_code
cannot be put in groups in the ITSM Designer in January 2021.
Tip 3
Tip 4
What user can modify
-
The scope can also define the objects that the users can modify. A user is never alloweds to modify an object that he cannot see, so when specifying what he can modify, just specify additional restriction if needed. For example if the user should be allowed to modify any Ticket that he can see, then
- itop_design / module_designs / module_design@itop-portal / classes / class@Ticket / scopes
-
<scope id="all"> <oql_edit><![CDATA[SELECT Ticket]]></oql_edit>
will do the job, regardless of the <oql_view> queries applying to the user.
Other example with more restriction on the Tickets which can be modified versus those which can be viewed
- itop_design / module_designs / module_design@itop-portal / classes / class@Ticket / scopes
-
<scope id="all"> <oql_view><![CDATA[SELECT Ticket WHERE (Condition3) ]]></oql_view> <oql_edit><![CDATA[SELECT Ticket WHERE (Condition4) ]]></oql_edit>
Then users will be only allowed to modify Tickets returned by this OQL
SELECT Ticket WHERE (Condition1) AND (Condition2)
Classes / others
Deny transitions
Allow to prevent users with some profile, to perform transitions that they are allowed to do in general, but that should not do it in this Portal, but rather on the Console or on another portal.
🚧 Propose an example of denying transitions on Ticket for
Support Agent
Lists display
This part allow to specify which fields from a given class, should be displayed when those objects are inside the form of another class, as a LinkedSet or LinkedSetIndirect attributes. If not specified, then the list of fields displayed will be the same as in the console, which may be fine.
🚧 add example of contacts within Ticket with screenshot
Forms
A form specify how objects of a class are displayed in the portal, including fields presentation and their flags: read only, writable, mandatory.
For a given class, you can have only one single
form, defining how to visualize the objects.
-
show or hide some fields based on the user profiles
-
show or hide some fields based on object status
In fact you can have different forms per object, assuming they are used in different situations (modes). You can have at maximum:
-
one form for object creation,
-
one form for modification,
-
one form for visualization,
-
and a form for each transition.
Those forms can be specific to a case or reuse in multiple situations.
Bricks
UrlMaker
UrlMaker classes allows iTop to build URLs pointing to a specific GUI (eg. your new portal). If no UrlMaker is registered for the portal, URL will be malformed in several places (eg. in notifications).
To do so, you should also have at least one class extending the
AbstractPortalUrlMaker
class to overload the default
behavior portal ID with the new portal ID. Then register it so it
can be used in the portal, notifications and such.
<?php use Combodo\iTop\Portal\UrlMaker\AbstractPortalUrlMaker; require_once APPROOT.'/lib/autoload.php'; /** * Hyperlinks to the "edition" of the object (vs view) */ class ThirdPartyDevelopersPortalEditUrlMaker extends AbstractPortalUrlMaker { const PORTAL_ID = 'extension-publisher-portal'; } /** * Hyperlinks to the "view" of the object (vs edition) */ class ThirdPartyDevelopersPortalViewUrlMaker extends MyPortalEditUrlMaker { /** * @inheritDoc */ public static function MakeObjectURL($sClass, $iId) { return static::PrepareObjectURL($sClass, $iId, 'view'); } } // Default portal hyperlink (for notifications) is the edit hyperlink DBObject::RegisterURLMakerClass('extension-publisher-portal', 'ThirdPartyDevelopersPortalEditUrlMaker');
Full XML
- itop_design / module_designs
-
<bricks> <brick id="user-profile" xsi:type="Combodo\iTop\Portal\Brick\UserProfileBrick"> <rank> <!-- Can be either a <default> tag for both home page and navigation menu or distinct <home> or/and <navigation_menu> tags--> <default>1</default> </rank> <title> <!-- Can be either a <default> tag for both home page and navigation menu or distinct <home> or/and <navigation_menu> tags--> <default>Brick:Portal:UserProfile:Navigation:Dropdown:MyProfil</default> </title> <decoration_class> <default>fas fa-user fa-2x</default> </decoration_class> <!-- Show / hide some of the user profile forms by setting the tag value to true|false --> <!--<show_picture_form>true</show_picture_form>--> <!--<show_preferences_form>true</show_preferences_form>--> <!--<show_password_form>true</show_password_form>--> <form> <!-- Optional tag to list the fields. If empty only fields from <twig> tag will be displayed, if omitted fields from zlist details will. --> <fields/> <!-- Optional tag to specify the form layout. Fields that are not positioned in the layout will be placed at the end of the form --> <twig> <!-- data-field-id attribute must be an attribute code of the class --> <!-- data-field-flags attribute contains flags among read_only/hidden/mandatory/must_prompt/must_change --> <div class="form_field" data-field-id="first_name" data-field-flags="read_only"> </div> <div class="form_field" data-field-id="name" data-field-flags="read_only"> </div> <div class="form_field" data-field-id="org_id" data-field-flags="read_only"> </div> <div class="form_field" data-field-id="email" data-field-flags="read_only"> </div> <div class="form_field" data-field-id="phone"> </div> <div class="form_field" data-field-id="location_id"> </div> <div class="form_field" data-field-id="function"> </div> <div class="form_field" data-field-id="manager_id" data-field-flags="read_only"> </div> </twig> </form> </brick> <brick id="services" xsi:type="Combodo\iTop\Portal\Brick\BrowseBrick"> <active>true</active> <width>6</width> <rank> <default>10</default> </rank> <title> <default>Brick:Portal:NewRequest:Title</default> </title> <description>Brick:Portal:NewRequest:Title+</description> <decoration_class> <default>fc fc-new-request fc-2x</default> </decoration_class> <!-- <fields /> Optional tag to add attributes to the table by their code, can be specified for each level --> <levels> <level id="1"> <class>ServiceFamily</class> <image_att>icon</image_att> <levels> <!-- Level IDs must be numeric --> <level id="1"> <!-- Can be either a class tag with the class name or an oql tag with the query --> <class>Service</class> <!-- Attribute code of the above class [from the OQL] that point to the upper level class --> <parent_att>servicefamily_id</parent_att> <!-- Attribute code of the above class [from the OQL] used to display the object name, default is 'name'. --> <name_att/> <!-- Attribute code of the above class [from the OQL] used to display in a tooltip when mouse is over the object --> <tooltip_att>description</tooltip_att> <!-- Attribute code of the above class [from the OQL] used to display a small text beside the object's name --> <!-- Note: This is not used in "list" mode --> <description_att>description</description_att> <!-- Attribute code of the above class [from the OQL] used to display a image beside the object's name --> <!-- Note: This is used in "mosaic" mode only for now --> <image_att>icon</image_att> <!-- Title of the level, will be display in lists and others browse modes --> <title>Class:Service</title> <!-- Optional tag to add attributes to the table by their code, can be specified for each level --> <!-- Note: Fields will only be displayed in "list" mode but will still be used for filter in other modes --> <!-- <fields /> --> <!-- Can be empty on intermediate levels, default is drilldown --> <actions> <action id="drilldown" xsi:type="drilldown"/> </actions> <levels> <level id="1"> <!-- Note : We could have used just a class tag and put the OQL in the scope for everybody --> <oql><![CDATA[SELECT ServiceSubcategory WHERE ServiceSubcategory.status != 'obsolete']]></oql> <parent_att>service_id</parent_att> <name_att/> <tooltip_att>description</tooltip_att> <description_att>description</description_att> <title>Class:ServiceSubcategory</title> <actions> <action id="create_from_this" xsi:type="create_from_this"> <!-- Can be either a class tag containing the class of the object to create, or a static method taking the origin object as a parameter and that will return a object of the desired class --> <!-- (eg. \Ticket::FromServiceSubcategory($oOrigin) that should return either a UserRequest or Incident regarding the request type) --> <class>UserRequest</class> <!-- Optional tag that can be used on any action type --> <!--<title>Create a ticket</title>--> <!-- Optional tag to define if the action should be done in a modal window ("modal"), a new window ("new") or the current window ("self") --> <!--<opening_target>modal</opening_target>--> <icon_class>fc fc-new-request fc-1-6x fc-flip-horizontal</icon_class> <!-- Optional tag to order actions --> <!--<rank>1</rank>--> <rules> <rule id="contact-to-userrequest"/> <rule id="servicesubcategory-to-userrequest"/> <rule id="go-to-open-request-on-submit"/> </rules> </action> <action id="view" xsi:type="view"/> </actions> <levels/> </level> </levels> </level> </levels> </level> </levels> <browse_modes> <availables> <mode id="list"/> <mode id="tree"/> <mode id="mosaic"/> </availables> <default>list</default> </browse_modes> <!-- Optional. Set the default number of item lists will display. --> <!-- <default_list_length>20</default_list_length> --> <data_loading>auto</data_loading> <!-- lazy|full|auto. Let the consultant choose if the list/tree data are load progressively at each page/level or in one-shot or if it is up to the system regarding the "lazy_loading_threshold" parameter --> </brick> <brick id="ongoing-tickets-for-portal-user" xsi:type="Combodo\iTop\Portal\Brick\ManageBrick"> <active>true</active> <rank> <default>20</default> </rank> <width>6</width> <title> <default>Brick:Portal:OngoingRequests:Title</default> </title> <!-- Optional tag to define which display modes can be used in the brick's page and the brick's tile --> <!--<display_modes>--> <!-- Optional tag that must contain at least 1 <mode> tag. --> <!--<availables>--> <!--<mode id="list"/>--> <!--<mode id="pie-chart"/>--> <!--<mode id="bar-chart"/>--> <!--</availables>--> <!-- Optional tag to define which display mode will be used by default when opening the brick --> <!--<default>list</default>--> <!-- Optional tag to define which display mode will be used to render the brick's tile --> <!--<tile>text</tile>--> <!--</display_modes>--> <description>Brick:Portal:OngoingRequests:Title+</description> <decoration_class> <default>fc fc-ongoing-request fc-2x</default> </decoration_class> <oql><![CDATA[SELECT Ticket]]></oql> <!-- Optional tag to define if the action should be done in a modal window ("modal"), a new window ("new") or the current window ("self") --> <!--<opening_target>modal</opening_target>--> <!-- Optional tag to define the how the objects should be opened. Values can be edit|view. Note that even if this is set to edit, objects not allowed in edition mode for the user (cf. scopes and security layers) will open in view mode --> <!-- <opening_mode>edit</opening_mode> --> <!-- Can be either a class tag with the class name or an oql tag with the query --> <!-- <class>Ticket</class> --> <fields> <field id="title"/> <field id="start_date"/> <field id="status"/> <field id="service_id"/> <field id="servicesubcategory_id"/> <field id="priority"/> <field id="caller_id"/> </fields> <!-- Optional tag to add attributes to the table by their code --> <grouping> <!-- Mandatory --> <tabs> <!-- Optional. Show object count for each tabs. Available values are true|false. Default is false. --> <show_tab_counts>true</show_tab_counts> <!-- Mandatory. Grouping by tabs --> <!--<attribute>operational_status</attribute>--> <!-- attribute xor groups tag --> <groups> <!-- Can be used only with ../oql tag, not ../class tag. Reason is that we can't know the class alias to apply to the condition's fields. We might have an exception saying that the field in ambiguous for the generated query. --> <group id="opened"> <rank>1</rank> <title>Brick:Portal:OngoingRequests:Tab:OnGoing</title> <!-- Optional. A string or dictionary entry to display under the page title for this tab --> <!-- <description>Brick:Portal:OngoingRequests:Tab:OnGoing+</description> --> <condition><![CDATA[SELECT Ticket AS T WHERE operational_status NOT IN ('closed', 'resolved')]]></condition> </group> <group id="resolved"> <rank>2</rank> <title>Brick:Portal:OngoingRequests:Tab:Resolved</title> <condition><![CDATA[SELECT Ticket AS T WHERE operational_status = 'resolved']]></condition> </group> </groups> </tabs> <!-- Implicit grouping on y axis by finalclass --> </grouping> <!-- Optional. Set the default number of item lists will display. --> <!-- <default_list_length>20</default_list_length> --> <data_loading>full</data_loading> <export> <export_default_fields>true</export_default_fields> </export> </brick> <brick id="closed-tickets-for-portal-user" xsi:type="Combodo\iTop\Portal\Brick\ManageBrick"> <active>true</active> <rank> <navigation_menu>50</navigation_menu> </rank> <visible> <home>false</home> </visible> <width>12</width> <title> <default>Brick:Portal:ClosedRequests:Title</default> </title> <description/> <decoration_class> <default>fc fc-closed-request fc-2x</default> </decoration_class> <oql><![CDATA[SELECT Ticket WHERE operational_status = 'closed']]></oql> <!-- Can be either a class tag with the class name or an oql tag with the query --> <!-- <class>Ticket</class> --> <fields> <field id="finalclass"/> <field id="title"/> <field id="start_date"/> <field id="status"/> <field id="servicesubcategory_id"/> <field id="priority"/> <field id="caller_id"/> </fields> <grouping> <tabs> <!-- Optional. Show object count for each tabs. Available values are true|false. Default is false. --> <!--<show_tab_counts>false</show_tab_counts>--> <groups> <group id="all"> <rank>1</rank> <title>Brick:Portal:ClosedRequests:Title</title> <condition><![CDATA[SELECT Ticket]]></condition> </group> </groups> </tabs> <!-- Implicit grouping on y axis by finalclass --> </grouping> <data_loading>auto</data_loading> <export> <export_default_fields>true</export_default_fields> </export> </brick> <brick id="approvals" xsi:type="Combodo\iTop\Portal\Brick\ApprovalBrick"> <!-- TODO: restreindre les droits de visibilité à cette brique --> <active>true</active> <rank> <default>60</default> </rank> <width>12</width> <title> <default>Approval:Portal:Menu</default> </title> <description>Approval:Portal:Menu+</description> <decoration_class> <default>fa fa-check-circle fa-2x</default> </decoration_class> <classes> <!-- Class to be considered (inc. derived classes) --> <class id="UserRequest"> <fields> <field id="title"/> <field id="start_date"/> <field id="status"/> <field id="service_id"/> <field id="servicesubcategory_id"/> <field id="priority"/> <field id="caller_id"/> </fields> </class> </classes> <!-- TODO: get rid of this? --> <data_loading>full</data_loading> </brick> <brick id="communication" xsi:type="Combodo\iTop\Portal\Brick\CommunicationBrick"> <!-- Should be the class name (eg : CommunicationBrick) --> <active>true</active> <!-- yes|no --> <rank>1</rank> <!-- float --> <height>15</height> <!-- integer , size in em --> <width>12</width> <!-- integer , must be between 1 and 12 --> <title>Portal:Communications</title> <!-- string --> <oql/> <!-- Query for the displayed communications (if authorized to the current user, see Communication::IsAllowedToUser). Leave empty to preserve the default behavior. Use :now instead of NOW(). --> <security> <!-- Order is deny/allow Pseudo OQL traduction : WHERE user_profile NOT IN (:denied_profiles) AND user_profile IN (:allowed_profiles) --> <denied_profiles/> <!-- OQL query. Used only when not empty --> <allowed_profiles/> <!-- OQL query. Used only when not empty --> </security> </brick> </bricks> <forms> <form id="service-view"> <class>Service</class> <fields/> <twig> <div class="row"> <div class="col-sm-6"> <div class="form_field" data-field-id="name"> </div> <div class="form_field" data-field-id="description"> </div> </div> <div class="col-sm-6"> <div class="form_field" data-field-id="org_id"> </div> <div class="form_field" data-field-id="servicefamily_id"> </div> <div class="form_field" data-field-id="status"> </div> </div> </div> </twig> <modes> <mode id="view"/> </modes> </form> <form id="servicesubcategory"> <class>ServiceSubcategory</class> <!-- Optional tag to list the fields. If empty only fields from <twig> tag will be displayed, if omitted fields from zlist details will. --> <fields/> <!-- Optional tag to specify the form layout. Fields that are not positioned in the layout will be placed at the end of the form --> <twig> <div class="row"> <div class="col-sm-6"> <!-- data-field-id attribute must be an attribute code of the class --> <div class="form_field" data-field-id="service_id"> </div> <!-- data-field-flags attribute contains flags among read_only/hidden/mandatory/must_prompt/must_change --> <div class="form_field" data-field-id="name" data-field-flags="read_only"> </div> <div class="form_field" data-field-id="status" data-field-flags="read_only"> </div> </div> <div class="col-sm-6"> <div class="form_field" data-field-id="service_org_id"> </div> <div class="form_field" data-field-id="request_type"> </div> </div> </div> <div> <div class="form_field" data-field-id="description"> </div> </div> </twig> </form> <form id="ticket-create"> <class>Ticket</class> <properties> <navigation_rules> <submit> <default>go-to-open-requests</default> </submit> </navigation_rules> </properties> <fields/> <twig> <div class="row"> <div class="col-sm-6"> <div class="form_field" data-field-id="service_id" data-field-flags="mandatory"> </div> </div> <div class="col-sm-6"> <div class="form_field" data-field-id="servicesubcategory_id" data-field-flags="mandatory"> </div> </div> </div> <div id="service_details_placeholder"> <div class="form_field" data-field-id="service_details"> </div> </div> <div class="row"> <div class="col-sm-6"> <div class="form_field" data-field-id="impact"> </div> </div> <div class="col-sm-6"> <div class="form_field" data-field-id="urgency"> </div> </div> </div> <div> <div class="form_field" data-field-id="title"> </div> <div class="form_field" data-field-id="description"> </div> <div class="form_field" data-field-id="contacts_list"> </div> </div> </twig> <modes> <!-- mode id can among create / edit / view --> <mode id="create"/> </modes> </form> <form id="ticket-edit"> <class>Ticket</class> <properties> <!-- Optional, display mode of the form fields. "cosy" for a regular labels over values layout; "compact" for a side-by-side layout with input aligned; "dense" for a side-by-side layout with input filling all available space. You can also use a custom css class that will be used on the form as "form_xxx", as well as on the fields as "form_field_xxx".--> <!--<display_mode>cosy</display_mode>--> <!-- Optional, when set to false, submit button is hidden when transitions are available on the object (in creation and edit mode). Default is false. --> <!--<always_show_submit>false</always_show_submit>--> <!-- Optional, navigation rules to define where to go when clicking on the submit/cancel buttons of the form --> <navigation_rules> <submit> <default>go-to-open-requests</default> </submit> </navigation_rules> </properties> <fields/> <twig> <div class="row"> <div class="col-sm-7"> <fieldset> <legend>{{'Ticket:baseinfo'|dict_s}}</legend> <div class="col-sm-6"> <div class="form_field" data-field-id="title" data-field-flags="read_only"/> <div class="form_field" data-field-id="service_id" data-field-flags="read_only"/> </div> <div class="col-sm-6"> <div class="form_field" data-field-id="caller_id" data-field-flags="read_only"/> <div class="form_field" data-field-id="servicesubcategory_id" data-field-flags="read_only"/> </div> <div class="col-sm-12"> <div class="form_field" data-field-id="description" data-field-flags="read_only"/> <div class="form_field" data-field-id="solution" data-field-flags="read_only"/> </div> <div class="col-sm-6"> <div class="form_field" data-field-id="user_satisfaction" data-field-flags="read_only"/> </div> <div class="col-sm-6"> <div class="form_field" data-field-id="user_comment" data-field-flags="read_only"/> </div> </fieldset> </div> <div class="col-sm-5"> <fieldset> <legend>{{'Ticket:Type'|dict_s}} & {{'Ticket:date'|dict_s}}</legend> <div class="col-sm-6"> <div class="form_field" data-field-id="status" data-field-flags="read_only"/> <div class="form_field" data-field-id="impact" data-field-flags="read_only"/> <div class="form_field" data-field-id="urgency" data-field-flags="read_only"/> <div class="form_field" data-field-id="priority" data-field-flags="read_only"/> </div> <div class="col-sm-6"> <div class="form_field" data-field-id="start_date" data-field-flags="read_only"/> <div class="form_field" data-field-id="last_update" data-field-flags="read_only"/> <div class="form_field" data-field-id="resolution_date" data-field-flags="read_only"/> <div class="form_field" data-field-id="agent_id" data-field-flags="read_only"/> </div> </fieldset> </div> </div> <div> <div class="form_field" data-field-id="contacts_list"/> <div class="form_field" data-field-id="public_log"/> </div> </twig> <modes> <mode id="edit"/> <mode id="view"/> </modes> </form> <form id="ticket-reopen"> <class>Ticket</class> <properties> <navigation_rules> <submit> <default>go-to-open-requests</default> </submit> </navigation_rules> </properties> <fields/> <twig> <div> <div class="form_field" data-field-id="public_log" data-field-flags="must_change"/> <div class="form_field" data-field-id="team_id" data-field-flags="hidden"/> <div class="form_field" data-field-id="agent_id" data-field-flags="hidden"/> </div> </twig> <modes> <mode id="apply_stimulus"> <stimuli> <stimulus id="ev_reopen"/> </stimuli> </mode> </modes> </form> <form id="ticket-apply-stimulus"> <class>Ticket</class> <properties> <navigation_rules> <submit> <default>go-to-open-requests</default> </submit> </navigation_rules> </properties> <fields/> <twig/> <modes> <mode id="apply_stimulus"/> </modes> </form> <form id="person-view"> <class>Person</class> <fields/> <twig> <div class="row"> <div class="col-sm-4"> <div class="form_field" data-field-id="picture" data-field-flags="read_only"> </div> </div> <div class="col-sm-4"> <div class="form_field" data-field-id="name" data-field-flags="read_only"> </div> <div class="form_field" data-field-id="first_name" data-field-flags="read_only"> </div> <div class="form_field" data-field-id="status" data-field-flags="read_only"> </div> </div> <div class="col-sm-4"> <div class="form_field" data-field-id="org_id" data-field-flags="read_only"> </div> <div class="form_field" data-field-id="function" data-field-flags="read_only"> </div> <div class="form_field" data-field-id="manager_id" data-field-flags="read_only"> </div> </div> </div> </twig> </form> </forms> <action_rules> <action_rule id="contact-to-userrequest"> <!-- source_oql|source_class is only necessary if there is some copy preset|retrofit --> <source_oql><![CDATA[SELECT Contact AS C WHERE C.id = :current_contact_id]]></source_oql> <presets> <!-- Only set() and copy() are supported for now --> <preset id="1">set(caller_id, $current_contact_id$)</preset> <preset id="2">copy(org_id, org_id)</preset> <preset id="3">set(origin, portal)</preset> </presets> <retrofits/> </action_rule> <action_rule id="service-to-userrequest"> <source_class>Service</source_class> <presets> <preset id="1">copy(id, service_id)</preset> </presets> </action_rule> <action_rule id="servicesubcategory-to-userrequest"> <source_class>ServiceSubcategory</source_class> <presets> <preset id="1">copy(id, servicesubcategory_id)</preset> <preset id="2">copy(service_id, service_id)</preset> </presets> </action_rule> <!-- Deprecated, will be removed in iTop 2.8 --> <action_rule id="go-to-open-request-on-submit"> <submit xsi:type="goto"> <brick>ongoing-tickets-for-portal-user</brick> </submit> </action_rule> </action_rules> <navigation_rules> <!-- Close form (either the modal or the whole page) --> <navigation_rule id="close-form" xsi:type="close"/> <!-- Go to the homepage --> <navigation_rule id="go-to-homepage" xsi:type="go-to-homepage"/> <!-- Open a manage brick (simple method) --> <navigation_rule id="go-to-open-requests" xsi:type="go-to-manage-brick"> <!-- Mandatory, ID of the ManageBrick to go to --> <id>ongoing-tickets-for-portal-user</id> <!-- Optional, must be an ID of the available display modes of the brick (//brick/display_modes/availables/mode) --> <!-- <display_mode>list</mode> --> <!-- Optional, must be an ID of the grouping tab of the brick (//brick/grouping/tabs/groups/group)--> <!-- <grouping_tab>resolved</grouping_tab> --> <!-- Optional, a string to preset as filter in the brick. ":this->XXX" can be used for the current object --> <!-- <filter>:this->caller_id_friendlyname</filter> --> </navigation_rule> <!-- Open a browse brick --> <navigation_rule id="go-to-services" xsi:type="go-to-browse-brick"> <!-- Mandatory, ID of the BrowseBrick to go to --> <id>services</id> <!-- Optional, must be an ID of the available browse modes of the brick (//brick/browse_modes/availables/mode) --> <!-- <browse_mode>tree</browse_mode> --> <!-- Optional, a string to preset as filter in the brick. ":this->XXX" can be used for the current object --> <!-- <filter>computer</filter> --> </navigation_rule> <!-- Open a brick, developer method --> <!-- <navigation_rule id="go-to-open-requests-alternate-way" xsi:type="go-to-brick"> --> <!-- <route> --> <!-- <id>p_manage_brick_display_as</id> --> <!-- <params> --> <!-- <param id="sBrickId">ongoing-tickets-for-portal-user</param> --> <!-- <param id="sDisplayMode">pie-chart</param> --> <!-- <param id="sGroupingTab">resolved</param> --> <!-- </params> --> <!-- </route> --> <!-- </navigation_rule> --> </navigation_rules>
- itop_design / module_designs
-
<module_design id="combodo-developers-portal" _delta="define"> <properties> <name>Developers portal</name> <urlmaker_class>ThirdPartyDevelopersPortalEditUrlMaker</urlmaker_class> <triggers_query> <![CDATA[SELECT TriggerOnPortalUpdate AS t WHERE t.target_class IN (:parent_classes)]]> </triggers_query> </properties> <classes> <class id="User"> <scopes> <scope id="all"> <oql_view> <![CDATA[SELECT User AS U JOIN Person AS P ON U.contactid=P.id WHERE P.id = :current_contact_id]]> </oql_view> </scope> </scopes> </class> <class id="Organization" _delta="define"> <scopes> <scope id="all"> <oql_view><![CDATA[SELECT Organization WHERE id = :current_contact->org_id]]></oql_view> </scope> </scopes> </class> <class id="Contact" _delta="define"> <scopes> <scope id="all"> <oql_view><![CDATA[SELECT Contact WHERE org_id = :current_contact->org_id]]></oql_view> </scope> </scopes> </class> <class id="Extension" _delta="define"> <scopes> <scope id="all"> <oql_view><![CDATA[SELECT Extension WHERE org_id = :current_contact->org_id]]></oql_view> <oql_edit><![CDATA[SELECT Extension]]></oql_edit> </scope> </scopes> </class> <class id="TargetExtension" _delta="define"> <scopes> <scope id="all"> <oql_view><![CDATA[SELECT TE FROM TargetExtension AS TE JOIN Extension AS E ON TE.extension_id = E.id WHERE E.org_id = :current_contact->org_id]]></oql_view> <oql_edit><![CDATA[SELECT TargetExtension]]></oql_edit> </scope> </scopes> <lists> <list id="list"> <items> <item id="target_date"> <rank>2</rank> </item> <item id="step"> <rank>3</rank> </item> </items> </list> <list id="default"> <items> <item id="extension_id"> <rank>2</rank> </item> <item id="step"> <rank>3</rank> </item> </items> </list> </lists> </class> <class id="LicenseType" _delta="define"> <scopes> <scope id="all"> <oql_view><![CDATA[SELECT LicenseType]]></oql_view> </scope> </scopes> </class> </classes> <action_rules> <action_rule id="contact-to-extension"> <source_oql><![CDATA[SELECT Contact AS C WHERE C.id = :current_contact_id]]></source_oql> <presets> <preset id="1">copy(org_id, org_id)</preset> <preset id="2">set(person_id, $current_contact_id$)</preset> <preset id="3">set(category, public)</preset> <preset id="4">set(status, beta)</preset> <preset id="5">set(acquisition_cost, 0)</preset> </presets> </action_rule> <action_rule id="extension-to-targetextension"> <source_class>Extension</source_class> <presets> <preset id="1">copy(id, extension_id)</preset> <preset id="2">set(freeze_date,$current_date$)</preset> </presets> </action_rule> </action_rules> <navigation_rules> <navigation_rule id="go-to-extension" xsi:type="go-to-object"> <!-- Mandatory, opens the first result from the OQL. Result may vary ¯\_(ツ)_/¯ but ":this" available! --> <oql>SELECT Extension WHERE id = :this->id</oql> <!-- Optional, mode of the object form, either view|edit --> <mode>edit</mode> <!-- Optional, how to open the object form, replace the current form (current), in a modal (modal) or in the page (page) --> <opening_target>current</opening_target> </navigation_rule> <navigation_rule id="go-to-extension-from-targetextension" xsi:type="go-to-object"> <!-- Mandatory, opens the first result from the OQL. Result may vary ¯\_(ツ)_/¯ but ":this" available! --> <oql>SELECT Extension WHERE id = :this->extension_id</oql> <!-- Optional, mode of the object form, either view|edit --> <mode>edit</mode> <!-- Optional, how to open the object form, replace the current form (current), in a modal (modal) or in the page (page) --> <opening_target>current</opening_target> </navigation_rule> <navigation_rule id="go-to-workshop" xsi:type="go-to-browse-brick"> <id>workshop</id> <browse_mode>mosaic</browse_mode> </navigation_rule> <navigation_rule id="go-to-version" xsi:type="go-to-object"> <!-- Mandatory, opens the first result from the OQL. Result may vary ¯\_(ツ)_/¯ but ":this" available! --> <oql>SELECT TargetExtension WHERE id = :this->id</oql> <!-- Optional, mode of the object form, either view|edit --> <mode>edit</mode> <!-- Optional, how to open the object form, replace the current form (current), in a modal (modal) or in the page (page) --> <opening_target>current</opening_target> </navigation_rule> <navigation_rule id="go-to-new-version" xsi:type="go-to-brick"> <route> <id>p_create_brick</id> <params> <param id="sBrickId">new-version</param> </params> </route> </navigation_rule> </navigation_rules> <forms> <form id="extension-create" _delta="define"> <class>Extension</class> <properties> <display_mode>compact</display_mode> <navigation_rules> <submit> <default>go-to-workshop</default> </submit> </navigation_rules> </properties> <fields/> <twig> <div class="row"> <div class="col-sm-4"> <fieldset> <legend>General information</legend> <div class="form_field" data-field-id="org_id_friendlyname" data-field-flags="read_only"/> <div class="form_field" data-field-id="name" data-field-flags="mandatory"/> <div class="form_field" data-field-id="build_identifier" data-field-flags="mandatory"/> <div class="form_field" data-field-id="licensetype_id" data-field-flags="mandatory"/> </fieldset> </div> <div class="col-sm-8"> <fieldset> <legend>Description</legend> <div class="form_field" data-field-id="short" data-field-flags="mandatory" data-field-display-mode="dense"/> <div class="form_field" data-field-id="description" data-field-flags="mandatory"/> <div class="form_field" data-field-id="wiki" data-field-flags="mandatory" data-field-display-mode="dense"/> <div class="form_field" data-field-id="repository_url" data-field-flags="" data-field-display-mode="dense"/> <div class="form_field" data-field-id="images" data-field-flags=""/> </fieldset> </div> </div> <br/> <div class="alert alert-info"> <div><b>Important:</b> Upload the cover image as an attachment. it will be used to represent the extension on iTop Hub.</div> <ul> <li>Accepted formats are JPG/PNG</li> <li>File must be named <b><Extension Code>-icon.jpg</b> or <b><Extension Code>-icon.png</b></li> <li>Size must be 256x170 pixels</li> </ul> </div> </twig> <modes> <mode id="create"/> </modes> </form> <form id="extension-edit" _delta="define"> <class>Extension</class> <properties> <display_mode>compact</display_mode> </properties> <fields/> <twig> <div class="row"> <div class="col-sm-6"> <fieldset> <legend>General information</legend> <div class="form_field" data-field-id="name" data-field-flags="mandatory"/> <div class="form_field" data-field-id="build_identifier" data-field-flags="read_only"/> <div class="form_field" data-field-id="licensetype_id" data-field-flags="mandatory"/> <div class="form_field" data-field-id="short" data-field-flags="mandatory" data-field-display-mode="dense"/> <div class="form_field" data-field-id="description" data-field-flags="mandatory"/> <div class="form_field" data-field-id="wiki" data-field-flags="mandatory" data-field-display-mode="dense"/> <div class="form_field" data-field-id="repository_url" data-field-flags="" data-field-display-mode="dense"/> <div class="form_field" data-field-id="images" data-field-flags=""/> <div class="form_field" data-field-id="tags" data-field-flags=""/> <div class="form_field" data-field-id="icon" data-field-flags="read_only"/> <div class="form_field" data-field-id="publication_date" data-field-flags="read_only" data-field-display-mode="dense"/> <div class="form_field" data-field-id="last_publication" data-field-flags="read_only" data-field-display-mode="dense"/> <div class="form_field" data-field-id="last_update" data-field-flags="read_only" data-field-display-mode="dense"/> <div class="form_field" data-field-id="org_id_friendlyname" data-field-flags="read_only"/> </fieldset> </div> <div class="col-sm-6"> <div class="form_field" data-field-id="targetextensions_list" data-field-flags="read_only" data-field-opened="true"/> <div class="form_field" data-field-id="log" data-field-flags=""/> </div> </div> <div class="alert alert-info"> <div><b>Important:</b> To change the cover image, upload it as an attachment. it will be used to represent the extension on iTop Hub.</div> <ul> <li>Accepted formats are JPG/PNG</li> <li>File must be named <b><Extension Code>-icon.jpg</b> or <b><Extension Code>-icon.png</b></li> <li>Size must be 256x170 pixels</li> </ul> </div> </twig> <modes> <mode id="edit"/> <mode id="view"/> </modes> </form> <form id="targetextension-create" _delta="define"> <class>TargetExtension</class> <properties> <display_mode>dense</display_mode> <always_show_submit>true</always_show_submit> <navigation_rules> <submit> <default>go-to-extension-from-targetextension</default> </submit> </navigation_rules> </properties> <fields/> <twig> <div class="row"> <div class="col-sm-4"> <fieldset> <legend>General information</legend> <div class="form_field" data-field-id="extension_id_friendlyname" data-field-flags=""/> <div class="form_field" data-field-id="extension_build_identifier" data-field-flags="read_only"/> <div class="form_field" data-field-id="version" data-field-flags=""/> <div class="form_field" data-field-id="itop_min_version" data-field-flags=""/> <div class="form_field" data-field-id="itop_max_version" data-field-flags=""/> <div class="form_field" data-field-id="step" data-field-flags="read_only"/> <div class="form_field" data-field-id="freeze_date" data-field-flags="read_only"/> </fieldset> </div> <div class="col-sm-8"> <div class="form_field" data-field-id="changelog" data-field-flags=""/> </div> </div> <br/> <div class="alert alert-info"> <div><b>Important:</b> Upload the zip package as an attachment.</div> <ul> <li>Accepted formats is ZIP only</li> <li>File must be named <b><Extension Code>-X.Y.Z-ABC.zip</b></li> <li>X, Y, Z must be version numbers (decimals) eg. 2.0.1</li> <li>ABC must be the build number (decimals) eg. 541 (internal build number) or 20201004120000 (date time)</li> </ul> </div> </twig> <modes> <mode id="create"/> </modes> </form> <form id="targetextension-edit" _delta="define"> <class>TargetExtension</class> <properties> <display_mode>dense</display_mode> <always_show_submit>true</always_show_submit> </properties> <fields/> <twig> <div class="row"> <div class="col-sm-4"> <fieldset> <legend>General information</legend> <div class="form_field" data-field-id="extension_id_friendlyname"/> <div class="form_field" data-field-id="extension_build_identifier" data-field-flags="read_only"/> <div class="form_field" data-field-id="version" data-field-flags=""/> <div class="form_field" data-field-id="itop_min_version" data-field-flags=""/> <div class="form_field" data-field-id="itop_max_version" data-field-flags=""/> <div class="form_field" data-field-id="step" data-field-flags="read_only"/> <div class="form_field" data-field-id="freeze_date" data-field-flags="read_only"/> <div class="form_field" data-field-id="target_date" data-field-flags="read_only"/> <div class="form_field" data-field-id="last_update" data-field-flags="read_only"/> </fieldset> </div> <div class="col-sm-8"> <div class="form_field" data-field-id="changelog" data-field-flags=""/> <div class="form_field" data-field-id="package" data-field-flags=""/> </div> </div> <br/> <div class="alert alert-info"> <div><b>Important:</b> To change the zip package, upload it as an attachment.</div> <ul> <li>Accepted formats is ZIP only</li> <li>File must be named <b><Extension Code>-X.Y.Z-ABC.zip</b></li> <li>X, Y, Z must be version numbers (decimals) eg. 2.0.1</li> <li>ABC must be the build number (decimals) eg. 541 (internal build number) or 20201004120000 (date time)</li> </ul> </div> </twig> <modes> <mode id="edit"/> <mode id="view"/> </modes> </form> <form id="targetextension-stimulus" _delta="define"> <class>TargetExtension</class> <properties> <navigation_rules> <submit> <default>go-to-version</default> </submit> <cancel> <default>go-to-version</default> </cancel> </navigation_rules> </properties> <fields/> <twig> <div> <div class="form_field" data-field-id="log" data-field-flags="must_prompt"/> </div> </twig> <modes> <mode id="apply_stimulus"> <stimuli> <stimulus id="ev_cancel"/> <stimulus id="ev_open"/> </stimuli> </mode> </modes> </form> <form id="targetextension-submit-stimulus" _delta="define"> <class>TargetExtension</class> <properties> <display_mode>dense</display_mode> <navigation_rules> <submit> <default>go-to-version</default> </submit> <cancel> <default>go-to-version</default> </cancel> </navigation_rules> </properties> <fields/> <twig> <div class="row"> <div class="col-sm-6"> <div class="form_field" data-field-id="itop_min_version" data-field-flags="must_prompt"/> </div> <div class="col-sm-6"> <div class="form_field" data-field-id="itop_max_version" data-field-flags="must_prompt"/> </div> </div> <div> <div class="form_field" data-field-id="changelog" data-field-flags="must_prompt"/> <div class="form_field" data-field-id="log" data-field-flags="must_prompt"/> </div> </twig> <modes> <mode id="apply_stimulus"> <stimuli> <stimulus id="ev_request_validation"/> <stimulus id="ev_republish"/> </stimuli> </mode> </modes> </form> <form id="contact" _delta="define"> <class>Person</class> <properties> <display_mode>compact</display_mode> </properties> <fields/> <twig> <div class="row"> <div class="form_field" data-field-id="civility" data-field-flags="read_only"/> <div class="form_field" data-field-id="first_name" data-field-flags="read_only"/> <div class="form_field" data-field-id="name" data-field-flags="read_only"/> <div class="form_field" data-field-id="email" data-field-flags="read_only"/> <div class="form_field" data-field-id="org_id_friendlyname" data-field-flags="read_only"/> </div> </twig> <modes> <mode id="view"/> </modes> </form> <form id="organization" _delta="define"> <class>Organization</class> <fields/> <twig> <div class="form_field" data-field-id="name" data-field-flags="read_only"/> </twig> </form> </forms> <bricks> <brick id="workshop" xsi:type="Combodo\iTop\Portal\Brick\BrowseBrick" _delta="define"> <active>true</active> <rank> <default>10</default> </rank> <width>6</width> <title> <default>Workshop</default> </title> <description>Modify your Extensions, add new versions, request publication,...</description> <decoration_class> <default>fas fa-drafting-compass fa-2x</default> </decoration_class> <levels> <level id="1"> <oql><![CDATA[SELECT Extension WHERE status !='obsolete']]></oql> <description_att>short</description_att> <image_att>icon</image_att> <fields> <field id="short"/> <field id="publication_date"/> <field id="last_publication"/> <field id="last_update"/> <field id="status"/> <field id="description"> <hidden>true</hidden> </field> </fields> <actions> <action id="edit" xsi:type="edit"> <rank>1</rank> </action> </actions> </level> </levels> <browse_modes> <availables> <mode id="mosaic"/> <mode id="list"/> </availables> <default>mosaic</default> </browse_modes> <data_loading>lazy</data_loading> </brick> <brick id="new-extension" xsi:type="Combodo\iTop\Portal\Brick\CreateBrick" _delta="define"> <active>true</active> <rank> <default>20</default> </rank> <width>6</width> <title> <default>New extension</default> </title> <description>Submit a brand new extension (new version of existing extensions can be added through the workshop)</description> <decoration_class> <default>fas fa-plus fa-2x</default> </decoration_class> <modal>true</modal> <class>Extension</class> <rules> <rule id="contact-to-extension"/> </rules> </brick> <!-- <brick id="new-version" xsi:type="Combodo\iTop\Portal\Brick\CreateBrick" _delta="define"> <active>false</active> <rank> <default>40</default> </rank> <width>6</width> <title> <default>New version</default> </title> <description>Submit a new version on the current extension</description> <decoration_class> <default>fas fa-plus fa-2x</default> </decoration_class> <modal>true</modal> <class>Extension</class> <rules> <rule id="extension-to-targetextension"/> </rules> </brick>--> <brick id="communication" xsi:type="Combodo\iTop\Portal\Brick\CommunicationBrick" _delta="define"> <rank>1</rank> <!-- float --> <oql><![CDATA[SELECT Communication WHERE portals MATCHES ('developer') AND status = 'ongoing' AND start_date <= :now]]></oql> <height>15</height> <!-- integer , size in em --> <width>12</width> <!-- integer , must be between 1 and 12 --> <title>Portal:Communications</title> </brick> <brick id="user-profile" xsi:type="Combodo\iTop\Portal\Brick\UserProfileBrick"> <rank> <default>1</default> </rank> <title> <default>Brick:Portal:UserProfile:Navigation:Dropdown:MyProfil</default> </title> <decoration_class> <default>fa fa-user fa-2x</default> </decoration_class> <!-- Show / hide some of the user profile forms by setting the tag value to true|false --> <!--<show_picture_form>true</show_picture_form>--> <!--<show_preferences_form>true</show_preferences_form>--> <!--<show_password_form>true</show_password_form>--> <form> <fields/> <twig> <div class="form_field" data-field-id="first_name" data-field-flags="read_only"/> <div class="form_field" data-field-id="name" data-field-flags="read_only"/> <div class="form_field" data-field-id="org_id" data-field-flags="read_only"/> <div class="form_field" data-field-id="email"/> <div class="form_field" data-field-id="phone"/> <div class="form_field" data-field-id="function"/> </twig> </form> </brick> </bricks> </module_design>