This extensions aims at improving the end-users productivity. It adds a menu to create an item prefilled with information coming from an existing item.
Amongst the possible usages, you can:
Let's use the last example to illustrate the possibilities of the module:
Users have a menu in the Other Actions drop-down menu of a User Request:
Clicking on the menu, opens the standard creation form with prepopulated data:
The user can adjust the values, then create the item. In addition to the standard report, a message indicates that something has been retrofitted back to the source item.
A shortcut is governed by a rule definition.
The menu is visible if a series of conditions are met:
Several rules can coexist for a single source object: there will be as many menus as there are rules that match the conditions.
A rule defines which kind of object to create:
A rule specifies what has to be done to preset or prepopulate the form. This is done by the mean of actions. The following kind of actions are available:
Additionaly, the rule specifies what has to be done to retrofit some information from the created object to the source object.
The following types of attributes are currently not handled and therefore cannot be preset (and no error message is given):
If apply_stimulus()
is used in preset
, the form is shown with the object id and the relevant buttons depending on the new state but then it fails to “create” the object because it was created already during the stimuli processing
Release Date | Version | Comments |
---|---|---|
2020-12-15 | 1.3.5 | Restore copy of caselog and 1:n relationships, broken by 1.3.2 |
2020-03-06 | 1.3.4 | - Update DE translations |
2019-06-12 | 1.3.3 | - copy_scalar now correctly copy lifecycle's state attribute - Object copier compatibility with iTop before 2.6 |
2019-03-26 | 1.3.2 | Fix Issue when copying list with object copier Fix Custom Date format in Stencils actions |
2019-01-16 | 1.3.1 | Security hardening |
2018-12-19 | 1.3.0 | New feature enabled: copy of Attachments (require iTop 2.6.0 or above) |
2018-06-26 | 1.2.1 | ES translation |
2018-01-26 | 1.2.0 | Add copy_head verb for CaseLog attributes - allow creation of a new ticket from the latest log |
2017-11-13 | 1.1.9 | Fix error displayed in error message or tooltip, about read-only attributes being set. |
2017-09-29 | 1.1.8 | Showing action in object details only when target class is writable. |
2017-04-04 | 1.1.7 | Case logs : when using set on a case log, the entry was set twice, the HTML formatting was lost, and if the log was first copied from the source, it was broken. |
2017-03-27 | 1.1.6 | With some customizations (preset on ticket dates), the refresh of dependent fields was broken. Regression introduced in iTop 2.3 |
2017-03-23 | 1.1.5 | Fix for XSS vulnerability. |
2016-08-09 | 1.1.4 | Fix for compatibility with iTop 2.3.0 (still backward compatible): properly handle the breadcrumb + bug fix when using hidden fields and case logs. |
2015-10-05 | 1.1.3 | Fix for compatibility with iTop 2.2.0 (still backward compatible) |
2015-09-30 | 1.1.2 | New verb: nullify. Useful for leaving a date or datetime undefined (differs from the verb reset because the default value for such attributes is “now”) |
2015-07-01 | 1.1.1 | New verb: call_method. Allows any kind of modification on the target object (and/or the source object) |
2015-02-03 | 1.1.0 | Added placeholders $current_date$ and $current_time$. Added the verb apply_stimulus. Exposed some APIs to allow the reuse of actions in other modules like itop-stencils |
2014-12-18 | 1.0.3 | Translated the default configuration in french (while keeping the engish version as the default) |
2014-07-18 | 1.0.2 | Added placeholders for set() and append(): $current_contact_id$ (already documented) and $current_contact_friendlyname$ (new!) |
2014-04-03 | 1.0.1 | Fixes an issue with linksets: the links were correctly created into the clone, but they were deleted from the source object |
2014-03-04 | 1.0.0 | First officialy released version |
Copy the folder itop-object-copier
into the extensions
folder of iTop and launch the setup again.
extensions
folder and all its sub-folders before launching the setup
When prompted to select the extensions to install, check “Object copier” in the list of available extensions.
rules
related to that class.
Setting name | Description | Example |
---|---|---|
source_scope | The OQL to define the source objects. The only parameter available is current_contact_id | “SELECT UserRequest WHERE status = 'assigned'” |
allowed_profiles | CSV list of profiles allowing the shortcut. The user must have at least one profile to have the shortcut available. Wrong profiles names are ignored. Set as an empty string to allow the shortcut to anybody. | “Administrator,Support Agent” |
menu_label | Optional: Label or dictionary entry for the new menu entry. It is optional and defaults to “Clone…” | |
form_label | Optional: Label or dictionary entry for the form header. It is optional and defaults to “Cloning %1$s” | |
report_label | Optional: Label or dictionary entry for the report once the object has been created. It is optional and defaults to “Cloned from %1$s” | |
dest_class | Class of the object to create. If empty, it defaults to the class of the source object | “Change” |
preset | Array of actions to preset the object in the creation form. More information below. | array() |
retrofit | Array of actions to retrofit some information from the created object to the source object. More information below. | array() |
An action is specified as a string formatted as verb(arg1[,arg2[…]])
The same action can be requested multiple times.
The following verbs are available:
Verb | Parameters | Description |
---|---|---|
clone_scalars | <none> | Copy all the scalar attributes |
clone | attcode1,attcode2, … | Copy the given attributes |
reset | attcode | Reset the attribute to its default value |
nullify | attcode | New in 1.1.2 - Reset the attribute to its null value (will appear to be undefined from the end users perspective) |
copy | att_to_read,att_to_write | Copy from att_to_read to att_to_write |
copy_head | att_to_read,att_to_write | New in 1.1.11 - Copy last entry in the att_to_read caselog attribute to att_to_write. An exception (non blocking) is thrown if att_to_read is not a caselog |
append | attcode,string | Append the string to the attribute. The string can contain placeholder like $this->attcode$ (or $current_contact_id$, $current_contact_friendlyname$, $current_date$, $current_time$). Commas must be escaped with a backslash. Newlines (\n) are allowed. The character set must be utf-8. |
set | attcode,value | Set a value. If the value is a string, it can then contain placeholder like $this->attcode$ (or $current_contact_id$, $current_contact_friendlyname$, $current_date$, $current_time$). Commas must be escaped with a backslash. Newlines (\n) are allowed. The character set must be utf-8. |
add_to_list | attRead,attWrite,attLink,value | attRead is an external key on the read object, attWrite is a N-N link set on the written object, attLink is an attribute on the link class that will be set to <value> |
apply_stimulus | stimulus code | Applies the given stimulus (saves the object). To be used in retrofit ONLY. Best practice: It is strongly recommanded to set transition mandatory fields as well, otherwise they will stay empty and could break reporting. |
call_method | function name | New in 1.1.1 - Calls the provided method on the written object. Its prototype must be “public function xxxx($oSource)”. The function can send exceptions in case of failure. In such a case, the error message gets displayed in the log/error.log file |
clone_attachments | <none> | New in 1.3.0 - Requires iTop 2.6+ - Copy all the attachments from source to destination |
'itop-object-copier' => array( 'rules' => array( 0 => array( 'source_scope' => 'SELECT UserRequest WHERE status IN ("assigned", "pending")', 'allowed_profiles' => 'Administrator,Support Agent', 'menu_label' => 'Issue a change ticket...', 'form_label' => 'Issue a change from request %1$s [...]', 'report_label' => 'Issued from the request %1$s. The request has been updated.', 'dest_class' => 'Change', 'preset' => array( 'clone(contacts_list,functionalcis_list,org_id,title,caller_id)', 'set(description,Original description:\n\$this->description\$)', ), 'retrofit' => array( 'copy(id, parent_change_id)', 'set(private_log,Issued change $this->ref$)', ), ), 1 => array( 'source_scope' => 'SELECT FunctionalCI', 'allowed_profiles' => '', 'dest_class' => '', 'preset' => array( 'clone_scalars(*)', 'reset(name)', ), 'retrofit' => array( ), ), ), ),
In the above examples, 0 and 1 are the identifier of the rules.
Here we want to copy a n:n relation, which would be of a similar nature but from 2 different Classes of links, for example you want to create a Change Ticket from a FunctionalCI and automatically retrieve Contacts associated to CI and link them to the Change.
public function CopyContactsFromCI($oSource) { // This method should be called from an object copier action triggered from a FunctionalCI. $sSourceClass = get_class($oSource); if (!MetaModel::IsParentClass('FunctionalCI', $sChildClass)) { throw new Exception("Wrong source class '$sSourceClass' : CopyContactsFromCI method should be called by a user action associated to a FunctionalCI!"); } // Problem: method is called twice, when new CreatedObject is displayed and when it is saved, // resulting in executing the code twice. // Work around: don't perform operation when CreatedObject is being saved $sOperation = utils::ReadPostedParam('operation'); if ($sOperation == 'apply_new') { return; } $id = $oSource->GetKey(); // get the ids of Contacts linked to the SourceObject (a FunctionalCI) $sOQL = "SELECT lnkContactToFunctionalCI AS l WHERE l.functionalci_id = :id "; $oContactSetSrc = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('id' => $id)); // ensure you get also the obsolete Contacts objects, in case the user preference hide them $oContactSetSrc->SetShowObsoleteData(utils::ShowObsoleteData()); $oContactSetDst = $this->Get('contacts_list'); // Copy the Contact linked objects one by one while ($oLnkSrc = $oContactSetSrc->Fetch()) { $oLnkDst = MetaModel::NewObject('lnkContactToTicket'); $oLnkDst->Set('contact_id', $oLnkSrc->Get('contact_id')); $oContactSetDst->AddItem($oLnkDst); } $this->Set('contacts_list', $oContactSetDst); }
3 => array ( 'source_scope' => 'SELECT FunctionalCI', 'allowed_profiles' => 'Support Agent,Administrator', 'menu_label' => 'Create a change...', 'form_label' => 'Create a user request from %1$s', 'report_label' => 'Created from %1$s', 'dest_class' => 'Change', 'preset' => array ( 0 => 'copy(org_id,org_id)', 1 => 'add_to_list(id,functionalcis_list,impact,Impacted CI)', 2 => 'call_method(CopyContactsFromCI)', ), 'retrofit' => array (), ),
Other actions
menu, use the shortcut_actions
parameter in the Configuration file.
Example: to display the Clone…
menu (rule id=1) in the toolbar after the New…
and Modify..
buttons, set this:
'shortcut_actions' => 'UI:Menu:Modify,UI:Menu:New,object_copier_1',
The result will look like this:
If the specified menu is not available for a given class, it's silently not displayed
All errors are logged to the file log/error.log
The behavior from the end-user perspective is:
Q: Can I copy the Request Template (service_details
) while cloning a UserRequest?
A: It is currently not working correctly.
Q: Can I copy the Caselogs while cloning a Ticket?
A: It is covered by this extension, except for versions between 1.3.2 and 1.3.4. In 1.3.5, there is an known issue with duplication of the last entry.
Q: Can I copy the WorkOrders while cloning a Ticket?
A: It is covered by this extension, except for versions between 1.3.2 and 1.3.4.
So here is a workaround: you can write a method to do it.
public function CloneWorkorders($oSource) { // Method called by Object-Copier // Change those 2 strings to match your needs $sAllowedSourceClass = 'Ticket'; // Class of the SourceObject $sSetAttCode = 'workorders_list'; // Attribute code of the n-1 relation to copy if (!MetaModel::IsValidAttCode(get_class($this), $sSetAttCode)) { throw new Exception(__METHOD__.":Unknown attribute ".get_class($this)."::".$sSetAttCode); } if (!MetaModel::IsParentClass($sAllowedSourceClass, get_class($oSource))) { throw new Exception(__METHOD__.":Wrong source class 'get_class($oSource)' : This method should be called by a user action associated to a '$sAllowedSourceClass'"); } // Check that AttributeDef is an AttributeLinkedSet $oSourceAttDef = MetaModel::GetAttributeDef(get_class($oSource), $sSetAttCode); if (is_object($oSourceAttDef) && !$oSourceAttDef->IsLinkSet()) { throw new Exception(__METHOD__.":Attribute ".$sSetAttCode ." is not a LinkSet on class ".get_class($this)); } // To avoid confusing the user, we apply this only on submission // Otherwise the copied objects seems to appear on the form, but adding or removing some is ignored. $sOperation = utils::ReadPostedParam('operation'); if ($sOperation != 'apply_new') { return; } $oDestSet = $this->Get($sSetAttCode); $oSet = $oSource->Get($sSetAttCode); while($oSourceObject = $oSet->Fetch()) { $sClass = get_class($oSourceObject); $oClone = MetaModel::NewObject($sClass); foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) { // As of now, ignore other attribute than scalars if ($oAttDef->IsScalar() && $oAttDef->IsWritable()) { $oClone->Set($sAttCode, $oSourceObject->Get($sAttCode)); } } $oDestSet->AddItem($oClone); } $this->Set($sSetAttCode,$oDestSet); }