# Formation développeur iTop [TOC] ## Structure d'iTop ![](https://i.imgur.com/0XvyzFc.png) | Emplacement | Contenu | ---------|--------| | datamodels/2.x <br> extensions | Code XML représentant le modèle de données | env-production | Modèle de donnée compilé | | application <br> core <br> pages | Coeur d'iTop | ## Création d'une extension Outil de génération d'extension vide : https://www.itophub.io/wiki/page?id=2_6_0%3Acustomization%3Adatamodel ![](https://i.imgur.com/c3qor0a.png) ### Contenu de l'archive générée <i class='fa fa-file'></i> ***datamodel.sample-module.xml*** ```xml=1 <?xml version="1.0" encoding="UTF-8"?> <itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"> <constants> </constants> <classes> </classes> <menus> </menus> <user_rights> <groups> </groups> <profiles> </profiles> </user_rights> </itop_design> ``` <i class='fa fa-file'></i> ***module.sample-module.php*** ```php=1 <?php // // iTop module definition file // SetupWebPage::AddModule( __FILE__, // Path to the current file, all other file names are relative to the directory containing this file 'sample-module/1.0.0', array( // Identification // 'label' => 'Sample Module', 'category' => 'business', // Setup // 'dependencies' => array( ), 'mandatory' => false, 'visible' => true, // Components // 'datamodel' => array( 'model.sample-module.php' ), 'webservice' => array( ), 'data.struct' => array( // add your 'structure' definition XML files here, ), 'data.sample' => array( // add your sample data XML files here, ), // Documentation // 'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any 'doc.more_information' => '', // hyperlink to more information, if any // Default settings // 'settings' => array( // Module specific settings go here, if any ), ) ); ?> ``` <i class='fa fa-file'></i> ***en.dict.sample-module.php*** ```php=1 <?php /** * Localized data * * @copyright Copyright (C) 2013 XXXXX * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('EN US', 'English', 'English', array( // Dictionary entries go here )); ?> ``` <i class='fa fa-file'></i> ***model.sample-module.php*** ```php=1 <?php // PHP Data Model definition file // WARNING - WARNING - WARNING // DO NOT EDIT THIS FILE (unless you know what you are doing) // // If you use supply a datamodel.xxxx.xml file with your module // the this file WILL BE overwritten by the compilation of the // module (during the setup) if the datamodel.xxxx.xml file // contains the definition of new classes or menus. // // The recommended way to define new classes (for iTop 2.0) is via the XML definition. // This file remains in the module's template only for the cases where there is: // - either no new class or menu defined in the XML file // - or no XML file at all supplied by the module ``` :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°1** Créer et installer une extension vide. ::: :::warning <i class="fa fa-2x fa-info-circle"></i> Il est nécessaire de créer manuellement le fichier contenant les traductions françaises. Le nom du fichier doit commencer par `fr.dict` et contenir le code suivant : ```php Dict::Add('FR FR', 'French', 'Français' ... ``` ::: ## Notion de delta Le modèle de donnée d'une extention est écrit en XML. Chaque noeud possède une propriété "_delta" qui est utilisé par itop pour déterminer si ce noeud ajoute, modifie ou supprime des données. Les différents deltas sont les suivants : * define * redefine * merge * delete Par défaut un noeud à le delta "merge" ou le delta de son parent si il existe. ## Ajout d'un menu Documentation : https://www.itophub.io/wiki/page?id=2_6_0:customization:xml_reference#menus ### Principaux types de menus |Type de menu|Utilisation |-|- |MenuGroup|Groupe de menu, contient des sous menus. |NewObjectMenuNode|Lien vers la création d'un nouvel object d'un type donné |SearchMenuNode|Lien vers une recherche sur un type d'objet donné |OQLMenuNode|Lien vers le résultat d'une requête OQL :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°2** Créer un nouveau menu sous "Gestion des demandes" permettant de rechercher des objets de type "Produit". ::: ### Indications - Créer un nouveau noeud xml "menu" sous "menus". - En plus des propriétées indiquées dans la documentation, ce nouveau noeud devra avoir une propriété `_delta` ayant la valeur `define`. Ceci est nécessaire à chaque fois que l'on ajoute un nouveau noeud xml. - Créer uniquement les noeuds enfants indiqués comme `mandatory` par la documentation. - Vous allez avoir besoin de l'indentifiant de la classe représenant les produits. Cette information est accessible depuis l'interface d'iTop sous "Outils d'admin" -> "Modèle de données". - Vous allez également avoir besoin de l'identifiant du menu parent, celui ci n'est malheuresement pas disponible dans le modèle de donnée il n'y a donc pas d'autre options que rechercher manuellement dans les fichiers. Une manière "facile" de trouver cet identifiant est de faire une recherche sur sa traduction. - Utiliser le **Toolkit** d'iTop pour recompiler l'environnement de production avec votre nouveau menu. :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°3** Ajouter une traduction française pour votre nouveau menu. ::: ### Gestion de la visibilité des menus Trois paramètres : `enable_class`, `enable_action` et `enable_permission`. Si l'utilisateur à le droit `enable_action` sur l'objet `enable_class` alors il a la permission `enable_permission`; :::info Exemples : `enable_class` UserRequest `enable_action` UR_ACTION_DELETE `enable_permission` UR_ALLOWED_YES L'utilisateur **est autorisé** (UR_ALLOWED_YES) à acceder au menu si il a le droit de suppression (UR_ACTION_DELETE) sur les demandes utilisateurs (UserRequest). --- `enable_class` Computer `enable_action` UR_ACTION_READ `enable_permission` UR_ALLOWED_NO L'utilisateur **n'est pas autorisé** (UR_ALLOWED_NO) à acceder au menu si il a le droit de lecture (UR_ACTION_READ) sur les ordinateurs (Computer). ::: OU uniquement `enable_stimulus` : autorise l'action si l'utilisateur à le droit d'utiliser le stimulus indiqué. ## Ajout d'un objet Documentations : - https://www.itophub.io/wiki/page?id=2_6_0:customization:xml_reference#xml_general_structure - https://www.itophub.io/wiki/page?id=2_6_0:customization:xml_reference#fields - https://www.itophub.io/wiki/page?id=2_6_0:customization:xml_reference#presentation :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°4** Créer un nouveau type d'objet. On ne renseignera que les informations obligatoires. L'objet aura un champ unique "Nom" de type `AttributeString`. Le champ sera affiché dans la présentation de l'object (partie `details`). Il faudra ajouter une traduction pour le nom de l'objet et son champ. Le menu créé précédemment devra être reconfiguré pour permettre la recherche sur ce nouveau type. ::: Format pour les traductions : ```php // Nom du type d'objet 'Class:XXXX' => '...', // Nom de l'attribut 'Class:XXXX/Attribute:YYYY' => '...', ``` ## Différents types de champs ### Champs simples Ces champs basiques demandent les mêmes informations : nom de la colonne (sql), valeur par défault et si la valeur null est autorisée. |Nom|Type|Extra |-|-|- |AttributeString|Chaine de caractères|validation_pattern |AttributeCaseLog|Journal |AttributeDate|Date |AttributeDecimal|Nombre à virgule|digits, decimals |AttributeDateTime|Date et heure |AttributeDuration|Durée |AttributeEmailAddress|Email|validation_pattern |AttributeEncryptedString|Chaine de caractères encryptée|validation_pattern |AttributeHTML|Bloc de texte HTML|width, height |AttributeInteger|Entier |AttributeIPAddress|IP |AttributePercentage|Pourcentage |AttributePhoneNumber|Numéro de téléphone |AttributeText|Bloc de texte|width, height |AttributeLongText|Bloc de texte|width, height |AttributeURL|Lien :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°5** Ajouter, afficher et traduire trois nouveaux attributs sur votre objet : * Description : AttributeHTML * Email : AttributeEmailAddress * Téléphone : AttributePhoneNumber ::: ### Énumérations et tags Le type des énumérations est `AttributeEnum` et les valeurs possibles sont définies dans `values`. Il est possible de choisir parmi les affichages suivants : * select * radio_horizontal * radio_vertical Il est nécessaire d'ajouter des traduction pour chaque valeur possibles : ```php 'Class:XXXX/Attribute:YYYY/Value:ZZZZ' => '...', ``` Pour définir une liste de tags, il faut utiliser `AttributeTagSet`. Ce dernier se configure ensuite dans l'interface graphique (administration des données). :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°6** Ajouter ces deux types d'attributs sur votre objet. ::: ### Relations 1-1 :::info <i class="fa fa-2x fa-info"></i> **Relation 1-1** Un objet A est lié directement à un objet B. ::: Pour définir une relation 1-1, il est nécessaire de mettre en place un `AttributeExternalKey` et éventuellement des `AttributesExternalField`. `AttributeExternalKey` définit la relation via `target_class` (type d'objet cible) et `filter` (requête OQL utilisée pour définir les cibles valides). `AttributesExternalField` permet d'afficher des champs de l'objet lié. :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°7** Ajouter une relation entre votre objet et des produits. Ajouter le statut du produit dans votre objet. ::: ### Relations 1-N :::info <i class="fa fa-2x fa-info"></i> **Relation 1-N** 1 objet A est lié à 1 ou plusieurs objets B. Cette relation induit une relation 1-1 de B vers A. ::: Pour définir une relation 1-N, il est nécessaire de mettre en place un `AttributeLinkedSet`. Ce dernier définit la relation via `ext_key_to_me`. L'interface de ce type de relation est configurable via `edit_actions` : * add_remove : Il est possible d'ajouter des objets **existants** à la relation. * in_place : Il est possible d'ajouter des **nouveaux** objets créés à la volée à la relation. * actions : Identique à in_place avec la possibilité de faire des modifications massives sur le contenu de la relation. * add_only : Indentique à in_place mais sans la possibilité de supprimer des éléments. * none : Lecture seule. :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°8** Ajouter une relation 1-N entre votre objet et des PC (avec interface en "add_remove"). Pour avoir une cible valide dans `ext_key_to_me`, il faudra créer une relation 1-1 sur l'objet PC. ::: ### Relations N-N :::info <i class="fa fa-2x fa-info"></i> **Relation N-N** 1 ou plusieurs objets A sont liés à 1 ou plusieurs objets B. Cette relation induit l'utilisation d'un lien externe appelé "lnk". ::: Pour définir une relation N-N, il est néssaire de mettre en place un `AttributeLinkedSetIndirect` et de créer un lnk. Un lnk correspond à un nouveau type d'objet avec : * Propriété `is_link` à 1 * Deux attributs `AttributeExternalKey` pointant vers les deux composants du lien * Convention de nommage : LnkAToB La relation est ensuite définie dans `AttributeLinkedSetIndirect` via `linked_class` (lnk), `ext_key_to_me` (attribut du lnk pointant vers notre objet) et `ext_key_to_remote` (attribut du lnk pointant vers le type d'objet cible). :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°9** Ajouter une relation N-N entre votre objet et des serveurs. Il faudra d'abord créer le lnk. ::: ## Cycle de vie Documentation : * https://www.itophub.io/wiki/page?id=2_6_0:customization:xml_reference * https://www.itophub.io/wiki/page?id=2_6_0:customization:xml_reference#stimuli Pour définir le cycle de vie d'un objet, il faut définir les éléments suivants : * Attribut contenant l'état de l'objet * Liste de stimulus possibles sur l'objet * Liste des états possibles de l'objet et leur réaction aux différents stimulis :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°10** Créer un cycle de vie basique pour votre objet : * Ajouter un attribut correspond à l'état (status par exemple) * Définisser deux stimulus : ev_validated et ev_closed. * Définisser trois états : nouveau, validé et clos. * Les transitions possibles seront : * * nouveau -> validé * nouveau -> clos * validé -> clos ::: ### Affichage dynamique selon le cycle de vie L'affichage dynamique des champs est gérer par la propriété `flags` d'un état ou d'un transition. Les flags disponibles sont les suivants : |Flag|Contexte|Résultat |-|-|- |hidden|État|Champ non visible |read_only|État|Champ en lecture seule. |must_prompt|Transition|Le champ doit être proposé lors de la transition |must_change|Transition|Le champ doit être modifié lors de la transition |mandatory|État|Le champ est obligatoire :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°11** Modifications sur le cycle de vie de votre objet : * Le champ description ne doit pas être visible dans l'état nouveau * Le champ email doit être présenté lorsque l'on effectue une validation ::: ### Exécution de fonctions lors d'une transition Il est possible d'éxécuter une fonction lors de l'éxécution d'une transition via la propriété `actions` de la transition. Bien qu'il soit possible d'appeler n'importe fonction de notre objet, iTop défini des fonctions utilitaires qui permettent de traiter la plupart des cas : |Fonction|Paramètres|Résultat |-|-|- |Reset|attcode| Réinitialise l'attribut |Copy|attcodeSource, attcodeDestination| Copie la valeur d'un attribut sur un autre attribut |SetCurrentDate|attcode|Applique la date actuelle à un attribut |SetCurrentUser|attcode|Applique l'utilisateur connecté à un attribut |SetCurrentPerson|attcode|Applique la personne connectée à un attribut |SetElapsedTime|attcode, refAttcode, (timeComputer)|Applique une durée depuis un point de référence à un attribut (avec la possibilité d'indiquer comment cette durée doit être calculée). :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°12** Ajouter un champ "date de clôture" à votre objet. Ce champ doit être renseigné automatiquement. ::: ## Ajout de métodes Il est utile de remplir la partie `<methods>` d'un objet dans plusieurs cas : * Si l'on souhaite rédéfinir des métodes définies dans l'objet parent `DBObject` * Si l'on souhaite définir des méthodes pour les utiliser dans les transitions * Si l'on souhaite définir des méthodes qui seront utilisées par d'autre objets ### Métodes notables de DBObject qui peuvent être redéfinies #### GetAttributeFlags ```php=1 /** * Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...) * for the given attribute in the current state of the object * @param $sAttCode string $sAttCode The code of the attribute * @param $aReasons array To store the reasons why the attribute is read-only (info about the synchro replicas) * @param $sTargetState string The target state in which to evalutate the flags, if empty the current state will be used * @return integer Flags: the binary combination of the flags applicable to this attribute */ public function GetAttributeFlags( $sAttCode, &$aReasons = array(), $sTargetState = '' ){ ``` Flags disponibles : * OPT_ATT_NORMAL * OPT_ATT_HIDDEN * OPT_ATT_READONLY * OPT_ATT_MANDATORY * OPT_ATT_MUSTCHANGE * OPT_ATT_MUSTPROMPT #### GetTransitionFlags ```php=1 /** * Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...) * for the given attribute in a transition * @param $sAttCode string $sAttCode The code of the attribute * @param $sStimulus string The stimulus code to apply * @param $aReasons array To store the reasons why the attribute is read-only (info about the synchro replicas) * @param $sOriginState string The state from which to apply $sStimulus, if empty current state will be used * @return integer Flags: the binary combination of the flags applicable to this attribute */ public function GetTransitionFlags( $sAttCode, $sStimulus, &$aReasons = array(), $sOriginState = '' ){ ``` #### GetInitialStateAttributeFlags ```php=1 /** * Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...) * for the given attribute for the current state of the object considered as an INITIAL state * @param string $sAttCode The code of the attribute * @return integer Flags: the binary combination of the flags applicable to this attribute */ public function GetInitialStateAttributeFlags( $sAttCode, &$aReasons = array() ){ ``` :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°13** Ajouter un champ de type `AttributeInteger` à votre objet. Ce champ ne doit être affiché que si il est inférieur à 5. Le code la métode doit être dans une balise `<![CDATA[ ]]>` ::: #### CheckValue ```php=1 // check if the given (or current) value is suitable for the attribute // return true if successfull // return the error desciption otherwise public function CheckValue($sAttCode, $value = null) { ``` :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°14** Le champ ajouté précédemment ne peut pas être inférieur à 3. ::: #### Insert / Update / Delete ```php=1 protected function OnInsert(){} protected function AfterInsert(){} protected function OnUpdate(){} protected function AfterUpdate(){} protected function OnDelete(){} protected function AfterDelete(){} ``` :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°15** Ajouté un nouveau champ représentant la date de dernière mise à jour. Ce champ doit être mis à jour automatiquement. ::: ### PrefillCreationForm / PrefillTransitionForm / PrefillSearchForm ```php= public function PrefillCreationForm(&$aContextParam){} public function PrefillTransitionForm(&$aContextParam){} public function PrefillSearchForm(&$aContextParam){} ``` :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°16** Pré-remplir le champ numérique ajouté à l'exercie 14 avec une valeur valide lors de la création de votre objet. ::: ### Métodes utilitaires de DBObject Pour rappel, les métodes utilitaires décrites dans la partie cycle de vie sont également utilisables ici : * Reset * Copy * SetCurrentDate * SetCurrentUser * SetCurrentPerson * SetElapsedTime #### ListChanges ```php=1 // List the attributes that have been changed // Returns an array of attname => currentvalue public function ListChanges() { ``` :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°17** Ajouté un nouveau champ "compteur de modifications". Ce champ doit être incrémenté automatiquement lorsque la description de votre objet est modifiée. ::: #### IsNew ```php=1 public function IsNew() { ``` #### GetKey ```php=1 public function GetKey() { ``` #### DBInsert, DBUpdate et DBDelete ```php=1 public function DBInsert(){} public function DBUpdate(){} public function DBDelete(&$oDeletionPlan = null){} ``` :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°18** Lors de la création de votre objet, un produit doit être créé avec le même nom. Ce produit sera ensuite attaché au champ produit. Le champ produit étant maintenant gérer automatiquement, il ne doit plus être modifiable. ::: #### ApplyStimulus ```php=1 /** * Designed as an action to be called when a stop watch threshold times out * or from within the framework * @param $sStimulusCode * @param bool|false $bDoNotWrite * @return bool * @throws CoreException * @throws CoreUnexpectedValue */ public function ApplyStimulus($sStimulusCode, $bDoNotWrite = false) { ``` ### Métodes utilitaires de Metamodel #### GetObject ```php=1 /** * Search for the specified class and id. * * @param string $sClass * @param $iKey * @param bool $bMustBeFound * @return DBObject|null null if : (the object is not found) or (archive mode disabled and object is archived and $bMustBeFound=false) * @throws CoreException if no result found and $bMustBeFound=true * @throws ArchivedObjectException if archive mode disabled and result is archived and $bMustBeFound=true */ public static function GetObject($sClass, $iKey, $bMustBeFound = true) { ``` :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°19** Si le nom de votre objet est modifié, le nom du produit lié doit être modifié en conséquence. ::: #### GetObjectFromOQL ```php=1 public static function GetObjectFromOQL( $sQuery, $aParams = null, $bAllowAllData = false ){ ``` #### NewObject ```php=1 /** * @param string $sClass * @param array|null $aValues array of attcode => value * @return DBObject */ public static function NewObject($sClass, $aValues = null) { ``` ### Métodes OQL : DBObjectSet et DBObjectSearch Exemple d'exécution d'une requête OQL : ```php=1 $query = "SELECT Person WHERE name LIKE 'a%'"; $search = DBObjectSearch::FromOQL($query); $set = new DBObjectSet($search); while ($person = $set->Fetch()) { ... } ``` :::success <i class="fa fa-2x fa-keyboard-o"></i> **Exercice n°20** Lorsque votre objet est crée, lié automatiquement des serveurs. ::: ## Modifications du portail Documentation éditeur : https://www.itophub.io/wiki/page?id=2_6_0%3Acustomization%3Aportal_xml Il est possibles de définir le contenu du portail (bricks), le contenu des formulaire de création ou de modification du portail (forms) et la visibilité des objets (scopes). ### Bricks Les différents pages du portail sont définies sous forme de briques : |Nom|Détails |-|- |UserProfileBrick|Affiche les détails de l'utilisateur connecté |BrowseBrick|Affiche une collection d'objet, permet de naviger cette collection de manière hiérarchique |ManageBrick|Affiche une collection d'objets modifiables |CreateBrick|Affiche un formulaire de création d'objet Exemple de brique : Liste des tickets ouverts ```xml= <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> <description>Brick:Portal:OngoingRequests:Title+</description> <decoration_class> <default>fc fc-ongoing-request fc-2x</default> </decoration_class> <oql><![CDATA[SELECT Ticket]]></oql> <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> <grouping> <tabs> <show_tab_counts>true</show_tab_counts> <groups> <group id="opened"> <rank>1</rank> <title>Brick:Portal:OngoingRequests:Tab:OnGoing</title> <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> </grouping> <data_loading>full</data_loading> <export> <export_default_fields>true</export_default_fields> </export> </brick> ``` ### Forms Il est possible de définir les formulaires de création et de modification pour chaque objets. Exemple d'un formulaire : création de ticket ```xml= <form id="ticket-create"> <class>Ticket</class> <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> <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> ``` ### Scopes Il est possible de définir la visibilité (requête OQL) de chaque objets sur le portail. Exemple de scope : visibilité des utilisateurs sur le portail ```xml= <class id="User"> <scopes> <!-- Note : Silos apply to those scope queries --> <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> <!-- No object of this class can be edited --> <oql_edit /> <!-- Everybody --> <allowed_profiles /> </scope> </scopes> </class> ``` ## Interface implémentables iTop propose plusieurs interfaces qui peuvent être implémentés pour modifier son comportement. ### iApplicationUIExtension Cette extension permet d'afficher du contenu supplémentaire dans les pages d'iTop. ```php=0 interface iApplicationUIExtension { // Invoked when an object is being displayed (wiew or edit) public function OnDisplayProperties( $oObject, WebPage $oPage, $bEditMode = false ); // Invoked when an object is being displayed (wiew or edit) public function OnDisplayRelations( $oObject, WebPage $oPage, $bEditMode = false ); // Invoked when the end-user clicks on Modify from the object edition form public function OnFormSubmit( $oObject, $sFormPrefix = '' ); // Invoked when the end-user clicks on Cancel from the object edition form public function OnFormCancel($sTempId); } ``` Exemple d'implémentation : AttachmentPlugin(pièces jointes) ```php= public function OnDisplayProperties($oObject, WebPage $oPage, $bEditMode = false) { if ($this->GetAttachmentsPosition() == 'properties') { $this->DisplayAttachments($oObject, $oPage, $bEditMode); } } public function OnDisplayRelations($oObject, WebPage $oPage, $bEditMode = false) { if ($this->GetAttachmentsPosition() == 'relations') { $this->DisplayAttachments($oObject, $oPage, $bEditMode); } } public function OnFormSubmit($oObject, $sFormPrefix = '') { if ($this->IsTargetObject($oObject)) { // For new objects attachments are processed in OnDBInsert if (!$oObject->IsNew()) { self::UpdateAttachments($oObject); } } } ``` ### iApplicationObjectExtension Cette extension permet d'intéragir avec les évènements des objets d'iTop. ```php= interface iApplicationObjectExtension { /** * Invoked to determine wether an object has been modified in memory * * The GUI calls this verb to determine the message that will be displayed to the end-user. * Anyhow, this API can be called in other contexts such as the CSV import tool. * * If the extension returns false, then the framework will perform the usual evaluation. * Otherwise, the answer is definitively "yes, the object has changed". * * @param DBObject $oObject The target object * @return boolean True if something has changed for the target object */ public function OnIsModified($oObject); /** * Invoked to determine wether an object can be written to the database * * The GUI calls this verb and reports any issue. * Anyhow, this API can be called in other contexts such as the CSV import tool. * * @param DBObject $oObject The target object * @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user. */ public function OnCheckToWrite($oObject); /** * Invoked to determine wether an object can be deleted from the database * * The GUI calls this verb and stops the deletion process if any issue is reported. * * Please not that it is not possible to cascade deletion by this mean: only stopper issues can be handled. * * @param DBObject $oObject The target object * @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user. */ public function OnCheckToDelete($oObject); /** * Invoked when an object is updated into the database * * The method is called right <b>after</b> the object has been written to the database. * * @param DBObject $oObject The target object * @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information once for all the changes made within the current page * @return void */ public function OnDBUpdate($oObject, $oChange = null); /** * Invoked when an object is created into the database * * The method is called right <b>after</b> the object has been written to the database. * * @param DBObject $oObject The target object * @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information once for all the changes made within the current page * @return void */ public function OnDBInsert($oObject, $oChange = null); /** * Invoked when an object is deleted from the database * * The method is called right <b>before</b> the object will be deleted from the database. * * @param DBObject $oObject The target object * @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information once for all the changes made within the current page * @return void */ public function OnDBDelete($oObject, $oChange = null); } ``` Exemple d'implémentation : Email reply (notification sur mise à jour d'un journal) ```php= public function OnDBUpdate($oObject, $oChange = null) { $this->HandleTriggers($oObject); } public function OnDBInsert($oObject, $oChange = null) { $this->HandleTriggers($oObject); } ``` ### iPopupMenuExtension Cette extension permet d'ajouter de nouvelle entrées dans les menus "Action" et "Boite à outil" de iTop. ```php= interface iPopupMenuExtension { /** * Insert an item into the Actions menu of a list * * $param is a DBObjectSet containing the list of objects */ const MENU_OBJLIST_ACTIONS = 1; /** * Insert an item into the Toolkit menu of a list * * $param is a DBObjectSet containing the list of objects */ const MENU_OBJLIST_TOOLKIT = 2; /** * Insert an item into the Actions menu on an object details page * * $param is a DBObject instance: the object currently displayed */ const MENU_OBJDETAILS_ACTIONS = 3; /** * Insert an item into the Dashboard menu * * The dashboad menu is shown on the top right corner when a dashboard * is being displayed. * * $param is a Dashboard instance: the dashboard currently displayed */ const MENU_DASHBOARD_ACTIONS = 4; /** * Insert an item into the User menu (upper right corner) * * $param is null */ const MENU_USER_ACTIONS = 5; /** * Insert an item into the Action menu on an object item in an objects list in the portal * * $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object on the current line) */ const PORTAL_OBJLISTITEM_ACTIONS = 7; /** * Insert an item into the Action menu on an object details page in the portal * * $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object currently displayed) */ const PORTAL_OBJDETAILS_ACTIONS = 8; /** * Insert an item into the Actions menu of a list in the portal * Note: This is not implemented yet ! * * $param is an array('portal_id' => $sPortalId, 'object_set' => $oSet) containing DBObjectSet containing the list of objects * @todo */ const PORTAL_OBJLIST_ACTIONS = 6; /** * Insert an item into the user menu of the portal * Note: This is not implemented yet ! * * $param is the portal id * @todo */ const PORTAL_USER_ACTIONS = 9; /** * Insert an item into the navigation menu of the portal * Note: This is not implemented yet ! * * $param is the portal id * @todo */ const PORTAL_MENU_ACTIONS = 10; /** * Get the list of items to be added to a menu. * * This method is called by the framework for each menu. * The items will be inserted in the menu in the order of the returned array. * @param int $iMenuId The identifier of the type of menu, as listed by the constants MENU_xxx * @param mixed $param Depends on $iMenuId, see the constants defined above * @return object[] An array of ApplicationPopupMenuItem or an empty array if no action is to be added to the menu */ public static function EnumItems($iMenuId, $param); } ``` Exemple d'implémentation : ObjectCopier (actions permettant de copier des objets iTop) ```php= public static function EnumItems($iMenuId, $param) { if ($iMenuId == iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS) { ... $aParams = $oAppContext->GetAsHash(); $aParams['operation'] = 'new'; $aParams['rule'] = $iRule; $aParams['source_id'] = $oObject->GetKey(); $aParams['source_class'] = get_class($oObject); return new URLPopupMenuItem ( 'object_copier_'.$iRule, self::FormatMessage($aRuleData, 'menu_label'), utils::GetAbsoluteUrlModulePage( 'itop-object-copier', 'copy.php', $aParams ) ); } } ``` ## Tâches d'arrière plan Il est possible de définir des tâches d'arrière plan dans iTop via l'interface iBackgroundProcess ```php /** * interface iProcess * Something that can be executed * * @copyright Copyright (C) 2010-2012 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ interface iProcess { /** * @param int $iUnixTimeLimit * * @return string status message * @throws \ProcessException * @throws \ProcessFatalException * @throws MySQLHasGoneAwayException */ public function Process($iUnixTimeLimit); } /** * interface iBackgroundProcess * Any extension that must be called regularly to be executed in the background * * @copyright Copyright (C) 2010-2012 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ interface iBackgroundProcess extends iProcess { /** * @return int repetition rate in seconds */ public function GetPeriodicity(); } ``` Exemple d'implémentation : AutoCloseTicket (fermeture automatique des tickets) ```php= class AutoCloseTicket implements iBackgroundProcess { public function GetPeriodicity() { return 86400; // Once a day } public function Process($iTimeLimit) { $aReport = array(); if (MetaModel::IsValidClass('UserRequest')) { // Get Resolved user request to be closed automatically $iAutoCloseDelay = MetaModel::GetModuleSetting( 'combodo-autoclose-ticket', 'userrequest_autoclose_delay', '' ); $oSetUserRequest = new DBObjectSet( DBObjectSearch::FromOQL(" SELECT UserRequest AS u WHERE u.status = 'resolved' AND u.resolution_date <= DATE_SUB( NOW(), INTERVAL $iAutoCloseDelay DAY ) ") ); while ((time() < $iTimeLimit) && $oToClose = $oSetUserRequest->Fetch() { $oToClose->ApplyStimulus('ev_close'); //$oToEscalate->Set('tto_escalation_deadline', null); $oToClose->DBUpdate(); $aReport['reached Auto close deadline'][] = $oToClose->Get('ref'); } } if (MetaModel::IsValidClass('Incident')) { ... } $aStringReport = array(); foreach ($aReport as $sOperation => $aTicketRefs) { if (count($aTicketRefs) > 0) { $aStringReport[] = $sOperation.': '.count($aTicketRefs). ' {'.implode(', ', $aTicketRefs).'}'; } } if (count($aStringReport) == 0) { return "No ticket to process"; echo "No ticket to process"; } else { return "Some tickets were closed - ".implode('; ', $aStringReport); echo "Some tickets were closed - ".implode('; ', $aStringReport); } } } ``` Pour que les tâches d'arrière plan s'éxécutent, il faut que le fichier webservices/cron.php d'iTop soit exécuté à interval régulier par la crontab du serveur. ## Synchronisations Les synchronisations se trouvent sur une machine à part (ETL) et utilisent l'API d'iTop pour mettre à jour les données. La synchronisation se déroule en 4 étapes : * Récupération de données externes * Stockage de ces données dans des fichiers csv. * Remplissage de la table de "staging". * Mise à jour sur iTop ### Script de "surcouche" Teclib ```bash=1 #!/bin/bash ########## # CONFIG # ########## collector_directory=/home/teclib/dev declare -A collectors collectors[Affectations]=AffectationCollector collectors[Données complémentaires personnes]=PersonSecondaryCollector collectors[Etablissements]=EstablishmentCollector collectors[Lien Etablissements / Personnes]=EstablishmentToPersonCollector collectors[Lien Personne / Profil utilisateur]=PersonToPortalProfileCollector collectors[Personnes]=PersonCollector collectors[Utilisateurs]=UserCollector ########## # SCRIPT # ########## echo "+------- START ----------+" current_date=`date +%Y-%m-%d-%H:%M` echo "+--- $current_date ---+" echo "+------------------------+" echo "+----- REMOVE JSON ------+" rm -f $collector_directory/collectors/*.json echo "+----- REMOVE CSV -------+" rm -f $collector_directory/data/*.csv echo "+---- GENERATE JSON -----+" for K in "${!collectors[@]}"; do echo $K \> ${collectors[$K]}.json; php $collector_directory/toolkit/dump_tasks.php --task_name="$K" > $collector_directory/collectors/${collectors[$K]}.json; done echo "+---- EXECUTE SYNCHRO ---+" php $collector_directory/exec.php echo "+-------- STOP ----------+" current_date=`date +%Y-%m-%d-%H:%M` echo "+--- $current_date ---+" echo "+------------------------+" ``` crontab : ```bash=1 0 13,21 * * * teclib /home/teclib/preprod/synchro.sh >> /home/teclib/preprod/log/synchro.log 2>&1 0 13,21 * * * teclib /home/teclib/prod/synchro.sh >> /home/teclib/prod/log/synchro.log 2>&1 ``` ### Traitement d'un collecteur Un collecteur doit réaliser la partie "ET" de l'ETL : - **E**xtract : Récupérer les données depuis la source externe - **T**ransform : Convertir les données dans le format attendu par iTop. Les collecteurs existants se base du TeclibBaseCollector et/ou TeclibLDAPCollector. ### TeclibBaseCollector ```php=1 // Extract the data Utils::log(LOG_INFO, "Starting data collection"); $aRawData = $this->Extract(); // Failure to extract data if ($aRawData === null) { Utils::log(LOG_ERR, "Unable to extract data from external source : " . $this->sError); return false; } // Count the rows $iTotalRows = count($aRawData); Utils::log(LOG_INFO, "Number of entries found : $iTotalRows"); // Transform the data Utils::log(LOG_INFO, "Starting data transformation"); $aData = $this->Transform($aRawData); // Failure to transform data if ($aData === null) { Utils::log(LOG_ERR, "Unable to transform the data recovered an from external source : " . $this->sError); return false; } // Load the data Utils::log(LOG_INFO, "End of data collection and transformation : " . count($aData) . " replica will be created from the original $iTotalRows data rows"); $this->aData = $aData; return true; ``` Les collecteurs enfants de `TeclibBaseCollector` doivent implémenter les métodes suivantes : ```php=1 abstract protected function Extract(); abstract protected function Transform($aRawData); ``` ### TeclibLDAPCollector ```php= public function connect($sHost, $iPort, $sUser, $sPassword) { // Connection to LDAP $this->oLDAP = ldap_connect($sHost, $iPort); if (!$this->oLDAP) { throw new ErrorException("unable to connect to LDAP"); } // Authentification $bAuth = ldap_bind($this->oLDAP, $sUser, $sPassword); if (!$bAuth) { ldap_close($this->oLDAP); throw new ErrorException("unable to bind to LDAP : " . ldap_error($this->oLDAP)); } } ``` ```php= protected function executeSearch($sDN, $sFilter, $aAttributes) { $oSearch = ldap_list($this->oLDAP, $sDN, $sFilter, $aAttributes); if (!$oSearch) { ldap_close($this->oLDAP); throw new ErrorException("unable to search on LDAP : " . ldap_error($this->oLDAP) . " [$sDN => $sFilter]"); } // Read results $aData = ldap_get_entries($this->oLDAP, $oSearch); if (!$aData) { ldap_close($this->oLDAP); throw new ErrorException("unable to read results from LDAP : " . ldap_error($this->oLDAP)); } // Remove the data count unset($aData['count']); // Return LDAP data return $aData; } ``` ### Exemple de collecteur ldap : Personne ```php=1 <?php /** * Synchronise the "Person" objects */ class PersonCollector extends TeclibLDAPCollector { /** * LDAP DN to query */ const LDAP_DN = [ "ou=personnels EN,ou=ac-montpellier,ou=education,o=gouv,c=fr,scope=sub", "ou=fonctionnelles,ou=ac-montpellier,ou=education,o=gouv,c=fr,scope=sub", "ou=autres,ou=ac-montpellier,ou=education,o=gouv,c=fr,scope=sub", "ou=mairies,ou=ac-montpellier,ou=education,o=gouv,c=fr,scope=sub", ]; /** * LDAP filters */ const LDAP_FILTER = [ "(mail=*)", "(|(mail=ce.rec*)(mail=ce.0*)(mail=ce.130*)(mail=gest.0*)(mail=gest.130*))", // "(|(title=AGRIC)(title=MARIT)(title=COLTER))", "(&(mail=*)(rne=*))", "(fredufonctadm=MAI)", ]; /** * LDAP attributes to retrieve for the main search */ const LDAP_ATTRIBUTES = [ "uid", "cn", "codecivilite", "sn", "nompatro", "givenname", "mail", "inetuserstatus", "telephonenumber" ]; /** * Gather data from LDAP */ protected function Extract() { try { $aPersons = []; // Connect to the LDAP $this->connect( Utils::GetConfigurationValue("LDAP_HOST"), Utils::GetConfigurationValue("LDAP_PORT"), Utils::GetConfigurationValue("LDAP_USER"), Utils::GetConfigurationValue("LDAP_PASSWORD") ); // Get data from the LDAP for ($i=0; $i<count(self::LDAP_DN); $i++) { $aPersons = array_merge( $aPersons, $this->executeSearch ( self::LDAP_DN[$i], self::LDAP_FILTER[$i], self::LDAP_ATTRIBUTES ) ); } $this->closeLDAP(); return $aPersons; } catch (ErrorException $e) { $this->sError = "Failed to extract data from LDAP : " . $e->getMessage(); return null; } } /** * Transform the data recovered from LDAP into a format that is understandable by iTop */ protected function Transform($aRawData) { $aData = []; foreach ($aRawData as $aPerson) { $sEmployeeNumber = $aPerson['uid'][0] ?? ''; // Skip if no uid if ($sEmployeeNumber === "") { continue; } $aData[] = [ 'primary_key' => $sEmployeeNumber, 'employee_number' => $sEmployeeNumber, 'org_id' => "Académie de Montpellier", 'civility' => $this->readCivility($aPerson['codecivilite'][0] ?? ''), 'name' => $this->replaceEmptyValues($aPerson['sn'][0] ?? ''), 'full_name' => $aPerson['cn'][0] ?? '', 'surname' => $aPerson['nompatro'][0] ?? '', 'first_name' => $this->replaceEmptyValues($aPerson['givenname'][0] ?? ''), 'email' => trim($aPerson['mail'][0] ?? ''), 'locked_mail' => isset($aPerson['inetuserstatus'][0]) && $aPerson['inetuserstatus'][0] === "active" ? 0 : 1, 'notify' => 'yes', 'phone' => $this->formatPhoneNumber($aPerson['telephonenumber'][0] ?? ''), 'in_function' => 1, 'status' => 'active', ]; } return $aData; } /** * Determine the civility from the LDAP value */ public function readCivility($sCivility) { switch (trim ($sCivility)) { case 'M.': case 'M': return 'M'; case 'MM': case 'ML': case 'Melle': case 'Mlle': case 'Mle': case 'Mme': case 'Mlle.': return 'MME'; default: return ''; } } public function replaceEmptyValues($sValue) { if (empty($sValue)) { $sValue = '-'; } return $sValue; } public function formatPhoneNumber($sNumber) { if (strlen($sNumber) === 10) { $sNumber = wordwrap($sNumber, 2, " ", true); } return $sNumber; } } ``` ### Exemple de collecteur MySQL : Affectations ```php=1 <?php class AffectationCollector extends TeclibBaseCollector { /** * LDAP filters */ const ANNUACAD_QUERY = " SELECT id_annuacad_personnel, uid, titre, libelle_division1, libelle_division2, libelle_division3, libelle_division4, libelle_personnel_fonction, num_bureau, lib_lieugeo, tel_interne, tel_public, date_debut, date_fin, sigle_division1, sigle_division2, sigle_division3, sigle_division4 FROM t_historique_personnel "; /** * Gather data from AnnuAcad */ protected function Extract() { try { // Database config $sHost = Utils::GetConfigurationValue("ANNUACAD_HOST"); $sDB = Utils::GetConfigurationValue("ANNUACAD_DB"); // Get data from AnnuAcad $oPDO = new PDO("mysql:host=$sHost;dbname=$sDB;charset=UTF8", Utils::GetConfigurationValue("ANNUACAD_USER"), Utils::GetConfigurationValue("ANNUACAD_PASSWORD") ); return $oPDO->query(self::ANNUACAD_QUERY) ->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { $this->sError = "Failed to connect to AnnuAcad : " . $e->getMessage(); return null; } } /** * Transform the data recovered from AnnuAcad into a format that is understandable by iTop */ protected function Transform($aRawData) { $aData = []; foreach($aRawData as $aAffectation) { $aData[] = [ "primary_key" => $aAffectation["id_annuacad_personnel"], "annuacad_primary_key" => $aAffectation["id_annuacad_personnel"], "person_id" => $aAffectation["uid"], "name" => $aAffectation["titre"] != "" ? $aAffectation["titre"] : "Non précisée", "service" => $this->formatService( $aAffectation["libelle_division1"], $aAffectation["libelle_division2"], $aAffectation["libelle_division3"], $aAffectation["libelle_division4"] ), "sigle" => $this->formatService( $aAffectation["sigle_division1"], $aAffectation["sigle_division2"], $aAffectation["sigle_division3"], $aAffectation["sigle_division4"] ), "responsability" => $aAffectation["libelle_personnel_fonction"], "office" => $aAffectation["num_bureau"], "internal_phone" => $aAffectation["tel_interne"], "external_phone" => $aAffectation["tel_public"], "begin_date" => substr($aAffectation["date_debut"], 0, 10), "end_date" => ( substr($aAffectation["date_fin"], 0, 10) !== "0000-00-00" ? substr($aAffectation["date_fin"], 0, 10) : null ), "location" => $aAffectation["lib_lieugeo"], ]; } return $aData; } private function formatService($sLib1, $sLib2, $sLib3, $sLib4) { if ($sLib2 != "" && $sLib2 != null) { $sLib1 .= " - $sLib2"; } if ($sLib3 != "" && $sLib3 != null) { $sLib1 .= " - $sLib3"; } if ($sLib4 != "" && $sLib4 != null) { $sLib1 .= " - $sLib4"; } return html_entity_decode($sLib1); } } ``` ### Exemple de collecteur DB2 : Établissements ```php=1 <?php class EstablishmentCollector extends TeclibBaseCollector { /** * LDAP filters */ const RAMSESE_QUERY = " SELECT u.UAIDNP, u.UAIRNE, u.UAIDNC, ao.VALEUR, u.SECTCO, tu.LIBELLE_LONG, u.UAIADR, u.UAIMDI, u.UAILIE, u.UAICPL, u.UAILOC, u.UAIBPT, u.UAICOP, u.UAITEL, u.UAIMAIL, p1.NOM P1_NOM, p1.PRENOM P1_PRENOM, p2.NOM P2_NOM, p2.PRENOM P2_PRENOM, r.UAIRNE R_UAIRNE FROM DB2IRAMS.V_UAI u JOIN DB2IRAMS.V_AAPPELLATION_OFFICIELLE ao ON u.UAIRNE = ao.NUMERO_UAI LEFT JOIN DB2IRAMS.V_TYPE_UAI tu ON u.UAIRNE = tu.NUMERO_UAI LEFT JOIN (SELECT * FROM DB2IRAMS.V_PERS WHERE CODE_FONCTION IN (1, 3, 5, 90, 32)) p1 ON u.UAIRNE = p1.UAI LEFT JOIN (SELECT * FROM DB2IRAMS.V_PERS WHERE CODE_FONCTION IN (2, 4, 6)) p2 ON u.UAIRNE = p2.UAI LEFT JOIN DB2IRAMS.V_RATT r ON u.UAIRNE = r.UAIRAT WHERE ao.DATE_FIN = '3999-12-31' AND U.ACADCO = 11 AND u.UAIDTF IS NULL "; /** * Gather data from RAMSESE */ protected function Extract() { // Database config $sHost = Utils::GetConfigurationValue("RAMSESE_HOST"); $sDB = Utils::GetConfigurationValue("RAMSESE_DB"); $sPort = Utils::GetConfigurationValue("RAMSESE_PORT"); $sUser = Utils::GetConfigurationValue("RAMSESE_USER"); $sPassword = Utils::GetConfigurationValue("RAMSESE_PASSWORD"); // Get data from RAMSESE $oDB2 = db2_connect( "DATABASE=$sDB;HOSTNAME=$sHost;PORT=$sPort;PROTOCOL=TCPIP;UID=$sUser;PWD=$sPassword;" null, null ); if (!$oDB2) { $this->sError = "Failed to connect to RAMSESE : " . db2_conn_errormsg(); return null; } // Execute request $aRawData = []; $oStmt = db2_prepare($oDB2, self::RAMSESE_QUERY); db2_execute($oStmt); // Fetch data while ($oRow = db2_fetch_assoc($oStmt)) { $aRawData[] = $oRow; } return $aRawData; } /** * Transform the data recovered from RAMSESE into a format that is understandable by iTop */ public function Transform($aRawData) { $aData = []; $aRne = []; foreach($aRawData as $aEtablissement) { // Skip if no UAI or duplicate if ($aEtablissement["UAIRNE"] == "" || array_search($aEtablissement["UAIRNE"], $aRne) !== false) { continue; } // Insert a new data row $aData[] = [ "primary_key" => $aEtablissement["UAIRNE"], "org_id" => "Académie de Montpellier", "name" => $aEtablissement["UAIDNP"] . " " . $aEtablissement["UAIDNC"], "ref_rne" => $aEtablissement["UAIRNE"], "official_designation" => $aEtablissement["VALEUR"], "status_ramsese" => $aEtablissement["SECTCO"] == "PU" ? "Privé" : $aEtablissement["SECTCO"] == "PR" ? "Public" : "", "establishmenttype" => $aEtablissement["LIBELLE_LONG"], "address" => $aEtablissement["UAIADR"], "additional_address" => $aEtablissement["UAIMDI"], "lieu_dit" => $aEtablissement["UAILIE"], "postal_code" => $aEtablissement["UAICPL"], "city" => $aEtablissement["UAILOC"], "post_box" => $aEtablissement["UAICPL"], "fax" => $aEtablissement["UAICOP"], "phone" => $aEtablissement["UAITEL"], "email" => trim($aEtablissement["UAIMAIL"]), "headmaster" => $aEtablissement["P1_NOM"] . " " . $aEtablissement["P1_PRENOM"], "deputy" => $aEtablissement["P2_NOM"] . " " . $aEtablissement["P2_PRENOM"], "related_establishment_id" => $aEtablissement["R_UAIRNE"], ]; $aRne[] = $aEtablissement["UAIRNE"]; } return $aData; } } ``` ## API iTop dispose d'une API REST. Toutes les requêtes doivent être en POST sur l'URL `webservices/rest.php?version=1.3` Chaque requête doit avoir trois paramètres : * auth_user : login * auth_pwd : mot de passe * json_data : contenu de la requête :::info <i class="fa fa-2x fa-info"></i> **Basic auth** Il est également possible de remplacer auth_user et auth_pwd par un header `Authentication: basic ...` ::: `json_data` contient l'opération souhaitée parmis les suivantes : ### list_operations json_data : ```json= { "operation": "list_operations" } ``` réponse : ```json= { "version": "1.2", "operations": [ { "verb": "core/create", "description": "Create an object", "extension": "CoreServices" }, { "verb": "core/update", "description": "Update an object", "extension": "CoreServices" }, { "verb": "core/get", "description": "Search for objects", "extension": "CoreServices" } ], "code": 0, "message": "Operations: 3" } ``` ### core/get json_data : ```json= { "operation": "core/get", "class": "Person", "key": "SELECT Person WHERE email LIKE '%.com'", "output_fields": "friendlyname, email" } ``` ### core/create json_data : ```json= { "operation": "core/create", "comment": "Synchronization from blah...", "class": "UserRequest", "output_fields": "id, friendlyname", "fields": { "org_id": "SELECT Organization WHERE name = \"Demo\"", "caller_id": { "name": "monet", "first_name": "claude", }, "title": "Houston, got a problem!", "description": "The fridge is empty" } } ``` réponse : ```json= { "code": 0, "message": "", "objects": { "UserRequest::123": { "code": 0, "message": "created", "class": "UserRequest", "key": 29, "fields": { "id": 29, "friendlyname": "R-000029" } } } } ``` ### core/update json_data : ```json= { "operation": "core/update", "comment": "Synchronization from blah...", "class": "UserRequest", "key": { "description": "The fridge is empty" }, "output_fields": "friendlyname, title, contact_list", "fields": { "contacts_list": [ { "role": "pizza delivery", "contact_id": { "finalclass": "Person", "name": "monet", "first_name": "claude" } } ] } } ``` ### core/apply_stimulus json_data : ```json= { "operation": "core/apply_stimulus", "comment": "Synchronization from blah...", "class": "UserRequest", "key": 15, "stimulus": "ev_assign", "output_fields": "friendlyname, title, status, contact_list", "fields": { "team_id": 18, "agent_id": 57 } } ``` ### core/delete json_data : ```json= { "operation": "core/delete", "comment": "Cleanup for customer Demo", "class": "UserRequest", "key": { "org_id": 2 }, "simulate": false } ``` ### core/get_related json_data : ```json= { "operation": "core/get_related", "class": "Server", "key": 1, "relation": "impacts", "depth": 4, "redundancy": true, "direction": "down" } ``` réponse : ```json= { "objects": { "objectclass::objectkey": { ... } }, "relations": { "origin-class::origin-key": [ { "key": "destination-class::destination-key" } ] }, "code": 0, "message": "Scope: 1; Found: Server=4, VirtualMachine=3, Farm=1" } ``` ### core/check_credentials json_data : ```json= { "operation": "core/check_credentials", "user": "john", "password": "abc123", } ``` réponse : ```json= { "code": 0, "message": "", "authorized": true, } ``` ### Exemple de requête ```bash= curl -X POST \ 'http://itop.xxx.net/webservices/rest.php?version=1.3' \ -H 'cache-control: no-cache' \ -H 'content-type: multipart/form-data' \ -F 'json_data={ "operation": "core/create", "comment": "Création ..., "class": "UserRequest", "output_fields": "id", "fields": { "org_id": 1, "caller_id": { "email": "xxx.yyy@zzz.fr" }, "title": "Titre de la demande", "description": "Description de la demande", "organizationlinked_id": { "rne": "000000A" }, "service_id": 2, "servicesubcategory_id": 17, "familyproduct_id": 562, "product_id": 2567, } }' \ -F auth_user=API_USER \ -F auth_pwd=******* ```