Unverified Commit 1de713e6 authored by flonou's avatar flonou Committed by GitHub
Browse files

Clone assets (#6684)

* Add cloning capacities

First implementation of the clone functionality
Updating tests to take into account the new action
removing missed debug log
Fixing code quality issues
Fixing test case, we now have one more action possible
Simplifying the clone method
Adding test for the clone method on various types of items
error in test code
same ; missing on another line
fixing code   issues
Adding a prepareInputForClone method that's useful for some types (user for example)
Fixing the tests
typo
fixing some code quality issues and tests
date to string comparison is not working
getting a profile item was not correctly done (was trying to get a user instead)
moving prepareInputForClone before the addslashes_deep method
Trying to get the date fields comparisons right
Still working on date comparison
maybe dates can't be compared ?
turns out date is not outputing a date but a string !
Fixing the cloning issue
Removing clone capability on profiles because rights are automatically unset after add and that might be a security issue
updating test case
adding more test cases and cloning capabilities
working on tests
trying to get more information on why a test fails
updating cloning
Fixing code identation
empty line fixing
Homogenization of cloning with the new method (removing calls to cloneItem methods for templates instanciation)
Handling legacy cloning requests (calling add with id or _oldID set)
Fixing code quality
Adding @since and @deprecated markers in the phpdoc
Updating the CHANGELOG.md file
Fix cs
fix defition on Dashboard::clone
rename method
Fixing dashboard test
parent 85ed59e8
......@@ -35,6 +35,11 @@ The present file will list all changes made to the project; according to the
- Add translation functions `__()`, `_n()`, `_x()` and `_nx()` in javascript in browser context.
- `Migration::renameItemtype()` method to update of database schema/values when an itemtype class is renamed
- Menu returned by `CommonGLPI::getMenuContent()` method override may now define an icon for each menu entry.
- `CommonDBConnexity::getItemsAssociatedTo()` method to get the items associated to the given one
- `CommonDBConnexity::getItemsAssociationRequest()` method to get the DB request to use to get the items associated to the given one
- `CommonDBTM::clone()` method to clone the current item
- `CommonDBTM::prepareInputForClone()` method to modify the input data that will be used for the cloning
- `CommonDBTM::post_clone()` method to perform other steps after an item has been cloned (like clone the elements it is associated to)
#### Changes
......@@ -85,6 +90,30 @@ The present file will list all changes made to the project; according to the
- `Plugin::setUnloadedByName()`
- Usage of `$LOADED_PLUGINS` global variable
- `CommonDBTM::getRawName()` replaced by `CommonDBTM::getFriendlyName()`
- `Calendar_Holiday::cloneCalendar()`
- `CalendarSegment::cloneCalendar()`
- `Computer_Item::cloneComputer()`
- `Computer_Item::cloneItem()`
- `ComputerAntivirus::cloneComputer()`
- `Contract::cloneItem()`
- `Contract_Item::cloneItem()`
- `ContractCost::cloneContract()`
- `Document_Item::cloneItem()`
- `Infocom::cloneItem()`
- `Item_Devices::cloneItem()`
- `Item_Disk::cloneItem()`
- `Item_OperatingSystem::cloneItem()`
- `Item_SoftwareLicense::cloneComputer()`
- `Item_SoftwareLicense::cloneItem()`
- `Item_SoftwareVersion::cloneComputer()`
- `Item_SoftwareVersion::cloneItem()`
- `Itil_Project::cloneItilProject()`
- `KnowbaseItem_Item::cloneItem()`
- `NetworkPort::cloneItem()`
- `Notepad::cloneItem()`
- `ProjectCost::cloneProject()`
- `ProjectTeam::cloneProjectTask()`
- `ProjectTask::cloneProjectTeam()`
#### Removed
......
......@@ -77,7 +77,7 @@ switch ($_REQUEST['action']) {
exit;
case 'clone_dashboard':
$new_dashboard = $dashboard->clone();
$new_dashboard = $dashboard->cloneCurrent();
echo json_encode($new_dashboard);
exit;
}
......
......@@ -43,3 +43,4 @@ if (!isset($_POST["itemtype"]) || !($item = getItemForItemtype($_POST['itemtype'
$item::dropdown();
echo "<br/><input type='submit' name='update' value=\""._sx('button', 'Update')."\" class='submit'>";
echo "<br/><input type='submit' name='clone' value=\""._sx('button', 'Clone')."\" class='submit'>";
......@@ -175,16 +175,31 @@ class Budget extends CommonDropdown{
}
function post_addItem() {
function post_clone($source, $history) {
parent::post_clone($source, $history);
$relations_classes = [
Document_Item::class
];
$override_input['items_id'] = $this->getID();
foreach ($relations_classes as $classname) {
if (!is_a($classname, CommonDBConnexity::class, true)) {
Toolbox::logWarning(
sprintf(
'Unable to clone elements of class %s as it does not extends "CommonDBConnexity"',
$classname
)
);
continue;
}
// Manage add from template
if (isset($this->input["_oldID"])) {
// ADD Documents
Document_Item::cloneItem($this->getType(), $this->input["_oldID"], $this->fields['id']);
$relation_items = $classname::getItemsAssociatedTo($this->getType(), $source->getID());
foreach ($relation_items as $relation_item) {
$newId = $relation_item->clone($override_input);
}
}
}
function rawSearchOptions() {
$tab = [];
......
......@@ -181,16 +181,18 @@ class Calendar_Holiday extends CommonDBRelation {
echo "</div>";
}
/**
* Duplicate all holidays from a calendar to its clone
*
* @deprecated 9.5
*
* @param integer $oldid The ID of the calendar to copy from.
* @param integer $newid The ID of the calendar to copy to.
**/
static function cloneCalendar($oldid, $newid) {
global $DB;
Toolbox::deprecated('Use clone');
$result = $DB->request(
[
'FROM' => self::getTable(),
......
......@@ -75,16 +75,38 @@ class CalendarSegment extends CommonDBChild {
return parent::prepareInputForAdd($input);
}
/**
* get the request results to get items associated to the given one (defined by $itemtype and $items_id)
*
* @param string $itemtype the type of the item we want the resulting items to be associated to
* @param string $items_id the name of the item we want the resulting items to be associated to
*
* @return array the items associated to the given one (empty if none was found)
**/
static function getItemsAssociationRequest($itemtype, $items_id) {
global $DB;
return $DB->request([
'SELECT' => 'id',
'FROM' => static::getTable(),
'WHERE' => [
static::$items_id => $items_id
]
]);
}
/**
* Duplicate all segments from a calendar to his clone
*
* @deprecated 9.5
*
* @param $oldid
* @param $newid
**/
static function cloneCalendar($oldid, $newid) {
global $DB;
Toolbox::deprecated('Use clone');
$result = $DB->request(
[
'FROM' => self::getTable(),
......
......@@ -98,15 +98,31 @@ class Cartridge extends CommonDBChild {
"date_in" => date("Y-m-d")];
}
function post_clone($source, $history) {
parent::post_clone($source, $history);
$relations_classes = [
Infocom::class
];
function post_addItem() {
$override_input['items_id'] = $this->getID();
foreach ($relations_classes as $classname) {
if (!is_a($classname, CommonDBConnexity::class, true)) {
Toolbox::logWarning(
sprintf(
'Unable to clone elements of class %s as it does not extends "CommonDBConnexity"',
$classname
)
);
continue;
}
Infocom::cloneItem('CartridgeItem', $this->fields["cartridgeitems_id"], $this->fields['id'],
$this->getType());
parent::post_addItem();
$relation_items = $classname::getItemsAssociatedTo($this->getType(), $source->getID());
foreach ($relation_items as $relation_item) {
$newId = $relation_item->clone($override_input);
}
}
}
function post_updateItem($history = 1) {
if (in_array('pages', $this->updates)) {
......
......@@ -373,22 +373,31 @@ class Certificate extends CommonDBTM {
return $input;
}
function post_addItem() {
// Manage add from template
if (isset($this->input["_oldID"])) {
// ADD Infocoms
Infocom::cloneItem($this->getType(), $this->input["_oldID"], $this->fields['id']);
// ADD Contract
Contract_Item::cloneItem($this->getType(), $this->input["_oldID"], $this->fields['id']);
function post_clone($source, $history) {
parent::post_clone($source, $history);
$relations_classes = [
Infocom::class,
Contract_Item::class,
Document_Item::class,
KnowbaseItem_Item::class
];
// ADD Documents
Document_Item::cloneItem($this->getType(), $this->input["_oldID"], $this->fields['id']);
$override_input['items_id'] = $this->getID();
foreach ($relations_classes as $classname) {
if (!is_a($classname, CommonDBConnexity::class, true)) {
Toolbox::logWarning(
sprintf(
'Unable to clone elements of class %s as it does not extends "CommonDBConnexity"',
$classname
)
);
continue;
}
//Add KB links
KnowbaseItem_Item::cloneItem($this->getType(), $this->input["_oldID"], $this->fields['id']);
$relation_items = $classname::getItemsAssociatedTo($this->getType(), $source->getID());
foreach ($relation_items as $relation_item) {
$newId = $relation_item->clone($override_input);
}
}
}
......
......@@ -62,6 +62,32 @@ class Cluster extends CommonDBTM {
return $ong;
}
function post_clone($source, $history) {
parent::post_clone($source, $history);
$relations_classes = [
NetworkPort::class
];
$override_input['items_id'] = $this->getID();
foreach ($relations_classes as $classname) {
if (!is_a($classname, CommonDBConnexity::class, true)) {
Toolbox::logWarning(
sprintf(
'Unable to clone elements of class %s as it does not extends "CommonDBConnexity"',
$classname
)
);
continue;
}
$relation_items = $classname::getItemsAssociatedTo($this->getType(), $source->getID());
foreach ($relation_items as $relation_item) {
$newId = $relation_item->clone($override_input);
}
}
}
function showForm($ID, $options = []) {
$rand = mt_rand();
......
......@@ -94,6 +94,29 @@ abstract class CommonDBChild extends CommonDBConnexity {
return null;
}
/**
* get the request results to get items associated to the given one (defined by $itemtype and $items_id)
*
* @since 9.5
*
* @param string $itemtype the type of the item we want the resulting items to be associated to
* @param string $items_id the name of the item we want the resulting items to be associated to
*
* @return array the items associated to the given one (empty if none was found)
**/
static function getItemsAssociationRequest($itemtype, $items_id) {
global $DB;
return $DB->request([
'SELECT' => 'id',
'FROM' => static::getTable(),
'WHERE' => [
static::$itemtype => $itemtype,
static::$items_id => $items_id
]
]);
}
/**
* @since 0.84
......
......@@ -153,6 +153,52 @@ abstract class CommonDBConnexity extends CommonDBTM {
$getEmpty, $getFromDBOrEmpty);
}
/**
* get items associated to the given one (defined by $itemtype and $items_id)
*
* @see CommonDBConnexity::getItemsAssociationRequest()
* @since 9.5
*
* @param string $itemtype the type of the item we want the resulting items to be associated to
* @param string $items_id the name of the item we want the resulting items to be associated to
*
* @return array the items associated to the given one (empty if none was found)
**/
static function getItemsAssociatedTo($itemtype, $items_id) {
$res = [];
$iterator = static::getItemsAssociationRequest($itemtype, $items_id);
while ($row = $iterator->next()) {
$input = Toolbox::addslashes_deep($row);
$item = new static();
$item->getFromDB($input['id']);
$res[] = $item;
}
return $res;
}
/**
* get the request results to get items associated to the given one (defined by $itemtype and $items_id)
*
* @since 9.5
*
* @param string $itemtype the type of the item we want the resulting items to be associated to
* @param string $items_id the name of the item we want the resulting items to be associated to
*
* @return array the items associated to the given one (empty if none was found)
*/
static function getItemsAssociationRequest($itemtype, $items_id) {
global $DB;
return $DB->request([
'SELECT' => 'id',
'FROM' => static::getTable(),
'WHERE' => [
'itemtype' => $itemtype,
'items_id' => $items_id
]
]);
}
/**
* get associated item (defined by $itemtype and $items_id)
......
......@@ -1046,6 +1046,21 @@ class CommonDBTM extends CommonGLPI {
return false;
}
// This means we are not adding a cloned object
if (!isset($input['clone'])) {
// This means we are asked to clone the object (old way). This will clone the clone method
// that will set the clone parameter to true
if (isset($input['_oldID'])) {
$id_to_clone = $input['_oldID'];
}
if (isset($input['id'])) {
$id_to_clone = $input['id'];
}
if (isset($id_to_clone) && $this->getFromDB($id_to_clone)) {
return $this->clone($input, $history);
}
}
// Store input in the object to be available in all sub-method / hook
$this->input = $input;
......@@ -1164,6 +1179,49 @@ class CommonDBTM extends CommonGLPI {
return false;
}
/**
* Clones the current item
*
* @since 9.5
*
* @param array $override_input custom input to override
* @param boolean $history do history log ? (true by default)
*
* @return integer the new ID of the clone (or false if fail)
*/
function clone(array $override_input = [], bool $history = true) {
global $DB, $CFG_GLPI;
if ($DB->isSlave()) {
return false;
}
$new_item = new static();
$input = $this->fields;
foreach ($override_input as $key => $value) {
$input[$key] = $value;
}
$input = $new_item->prepareInputForClone($input);
if (isset($input['id'])) {
$input['_oldID'] = $input['id'];
unset($input['id']);
}
unset($input['date_creation']);
unset($input['date_mod']);
if (isset($input['template_name'])) {
unset($input['template_name']);
}
if (isset($input['is_template'])) {
unset($input['is_template']);
}
$input['clone'] = true;
$newID = $new_item->add($input, [], $history);
// If the item needs post clone (recursive cloning for example)
$new_item->post_clone($this, $history);
return $newID;
}
/**
* Get the link to an item
......@@ -1292,6 +1350,19 @@ class CommonDBTM extends CommonGLPI {
return $input;
}
/**
* Prepare input datas for cloning the item
*
* @since 9.5
*
* @param array $input datas used to add the item
*
* @return array the modified $input array
**/
function prepareInputForClone($input) {
return $input;
}
/**
* Actions done after the ADD of the item in the database
......@@ -1301,6 +1372,19 @@ class CommonDBTM extends CommonGLPI {
function post_addItem() {
}
/**
* Actions done after the clone of the item in the database
*
* @since 9.5
*
* @param $source the item that is being cloned
* @param $history do history log ?
*
* @return void
**/
function post_clone($source, $history) {
}
/**
* Update some elements of an item in the database.
......
......@@ -2026,6 +2026,26 @@ abstract class CommonITILObject extends CommonDBTM {
}
/**
* @see CommonDBTM::post_clone
*/
function post_clone($source, $history) {
global $DB;
$update = [];
if (isset($source->fields['users_id_lastupdater'])) {
$update['users_id_lastupdater'] = $source->fields['users_id_lastupdater'];
}
if (isset($source->fields['status'])) {
$update['status'] = $source->fields['status'];
}
$DB->update(
$this->getTable(),
$update,
['id' => $this->getID()]
);
}
/**
* @since 0.84
......
......@@ -247,49 +247,45 @@ class Computer extends CommonDBTM {
return $input;
}
/**
* @see CommonDBTM::post_clone
**/
function post_clone($source, $history) {
parent::post_clone($source, $history);
$relations_classes = [
Item_OperatingSystem::class,
Item_devices::class,
Infocom::class,
Item_Disk::class,
Item_SoftwareVersion::class,
Item_SoftwareLicense::class,
Contract_Item::class,
Document_Item::class,
NetworkPort::class,
Computer_Item::class,
Notepad::class,
KnowbaseItem_Item::class
];
function post_addItem() {
// Manage add from template
if (isset($this->input["_oldID"])) {
// ADD OS
Item_OperatingSystem::cloneItem($this->getType(), $this->input["_oldID"], $this->fields['id']);
// ADD Devices
Item_Devices::cloneItem($this->getType(), $this->input["_oldID"], $this->fields['id']);
// ADD Infocoms
Infocom::cloneItem($this->getType(), $this->input["_oldID"], $this->fields['id']);
// ADD volumes
Item_Disk::cloneItem($this->getType(), $this->input["_oldID"], $this->fields['id']);
// ADD software
Item_SoftwareVersion::cloneItem('Computer', $this->input["_oldID"], $this->fields['id']);
Item_SoftwareLicense::cloneItem('Computer', $this->input["_oldID"], $this->fields['id']);
// ADD Contract
Contract_Item::cloneItem($this->getType(), $this->input["_oldID"], $this->fields['id']);
// ADD Documents
Document_Item::cloneItem($this->getType(), $this->input["_oldID"], $this->fields['id']);
// ADD Ports
NetworkPort::cloneItem($this->getType(), $this->input["_oldID"], $this->fields['id']);
// Add connected devices
Computer_Item::cloneComputer($this->input["_oldID"], $this->fields['id']);
//Add notepad
Notepad::cloneItem($this->getType(), $this->input["_oldID"], $this->fields['id']);
$override_input['items_id'] = $this->getID();
foreach ($relations_classes as $classname) {
if (!is_a($classname, CommonDBConnexity::class, true)) {
Toolbox::logWarning(
sprintf(
'Unable to clone elements of class %s as it does not extends "CommonDBConnexity"',
$classname
)
);
continue;
}
//Add KB links
KnowbaseItem_Item::cloneItem($this->getType(), $this->input["_oldID"], $this->fields['id']);
$relation_items = $classname::getItemsAssociatedTo($this->getType(), $source->getID());
foreach ($relation_items as $relation_item) {
$newId = $relation_item->clone($override_input, $history);
}
}
}
function cleanDBonPurge() {
$this->deleteChildrenAndRelationsFromDb(
......
......@@ -746,6 +746,7 @@ class Computer_Item extends CommonDBRelation{
/**
* Duplicate connected items to computer from an item template to its clone
*
* @deprecated 9.5
* @since 0.84
*
* @param integer $oldid ID of the item to clone
......@@ -754,6 +755,7 @@ class Computer_Item extends CommonDBRelation{
static function cloneComputer($oldid, $newid) {
global $DB;
Toolbox::deprecated('Use clone');
$iterator = $DB->request([
'FROM' => self::getTable(),
'WHERE' => ['computers_id' => $oldid]
......@@ -771,6 +773,7 @@ class Computer_Item extends CommonDBRelation{
/**
* Duplicate connected items to item from an item template to its clone
*
* @deprecated 9.5
* @since 0.83.3
*
* @param string $itemtype type of the item to clone
......@@ -780,6 +783,7 @@ class Computer_Item extends CommonDBRelation{
static function cloneItem($itemtype, $oldid, $newid) {
global $DB;
Toolbox::deprecated('Use clone');
$iterator = $DB->request([
'FROM' => self::getTable(),
'WHERE' => [
......