Unverified Commit 00bc1bec authored by Curtis Conard's avatar Curtis Conard Committed by GitHub
Browse files

Complete Clonable trait (#7816)



* Move clone methods to Clonable trait

* Fix lint + try rerun tests

* Revert return type. Use Toolbox::deprecated

* Fixes

Deprecate
DBChild and DBRelations should be clonable by nature
Fix parent calls

* Factorize trait imports

* Changes/fixes:

- Simplify trait usage
- NetworkPort is clonable
- CommonDBConnexity is clonable
- Clean input properties in dedicated method

* Networkport inherits clonable from CommonDBConnexity
Co-authored-by: default avatarJohan Cwiklinski <jcwiklinski@teclib.com>
Co-authored-by: default avatarCédric Anne <cedric.anne@gmail.com>
parent ef7b6dea
......@@ -26,7 +26,12 @@ The present file will list all changes made to the project; according to the
#### Deprecated
- Usage of `GLPI_FORCE_EMPTY_SQL_MODE` constant
- Usage of `CommonDBTM::notificationqueueonaction` property
- `RuleImportComputer` and `RuleImportComputerCollection`
- `Calendar::duplicate()`
- `CommonDBTM::clone()`
- `CommonDBTM::prepareInputForClone()`
- `CommonDBTM::post_clone()`
- `RuleImportComputer` class
- `RuleImportComputerCollection` class
#### Removed
- `Update::declareOldItems()`
......
......@@ -121,7 +121,7 @@ class Calendar extends CommonDropdown {
switch ($ma->getAction()) {
case 'duplicate' : // For calendar duplicate in another entity
if (method_exists($item, 'duplicate')) {
if (Toolbox::hasTrait($item, \Glpi\Features\Clonable::class)) {
$input = $ma->getInput();
$options = [];
if ($item->isEntityAssign()) {
......@@ -132,7 +132,7 @@ class Calendar extends CommonDropdown {
if (!$item->isEntityAssign()
|| ($input['entities_id'] != $item->getEntityID())) {
if ($item->can(-1, CREATE, $options)) {
if ($item->duplicate($options)) {
if ($item->clone($options)) {
$ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
} else {
$ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
......@@ -196,11 +196,13 @@ class Calendar extends CommonDropdown {
/**
* Clone a calendar to another entity : name is updated
*
* @param $options array of new values to set
* @return boolean True on success
* @param array $options Array of new values to set
* @return boolean True on success or false on failure
* @deprecated x.x.x Use the {@link \Glpi\Features\Clonable} trait instead
*/
function duplicate($options = []) {
Toolbox::deprecated('Use clone()');
$input = Toolbox::addslashes_deep($this->fields);
unset($input['id']);
......@@ -212,12 +214,14 @@ class Calendar extends CommonDropdown {
}
}
if ($newID = $this->clone($input)) {
$this->updateDurationCache($newID);
return true;
}
return (bool) $this->clone($input);
}
return false;
/**
* @see Glpi\Features\Clonable::post_clone
*/
public function post_clone($source, $history) {
$this->updateDurationCache($this->getID());
}
......
......@@ -81,6 +81,8 @@ class CommonDBConnexityItemNotFound extends \Exception {
**/
abstract class CommonDBConnexity extends CommonDBTM {
use Glpi\Features\Clonable;
const DONT_CHECK_ITEM_RIGHTS = 1; // Don't check the parent => always can*Child
const HAVE_VIEW_RIGHT_ON_ITEM = 2; // canXXXChild = true if parent::canView == true
const HAVE_SAME_RIGHT_ON_ITEM = 3; // canXXXChild = true if parent::canXXX == true
......@@ -90,6 +92,11 @@ abstract class CommonDBConnexity extends CommonDBTM {
static public $disableAutoEntityForwarding = false;
public function getCloneRelations() :array {
return [
];
}
/**
* Return the SQL request to get all the connexities corresponding to $itemtype[$items_id]
* That is used by cleanDBOnItem : the only interesting field is static::getIndexName()
......@@ -171,7 +178,7 @@ abstract class CommonDBConnexity extends CommonDBTM {
while ($row = $iterator->next()) {
$input = Toolbox::addslashes_deep($row);
$item = new static();
$item->getFromDB($input['id']);
$item->getFromDB($input[static::getIndexName()]);
$res[] = $item;
}
return $res;
......
......@@ -1076,7 +1076,7 @@ class CommonDBTM extends CommonGLPI {
}
// This means we are not adding a cloned object
if (!isset($input['clone'])) {
if (!Toolbox::hasTrait($this, \Glpi\Features\Clonable::class) || !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'])) {
......@@ -1224,10 +1224,12 @@ class CommonDBTM extends CommonGLPI {
* @param boolean $history do history log ? (true by default)
*
* @return integer the new ID of the clone (or false if fail)
* @deprecated x.x.x Use the {@link \Glpi\Features\Clonable} trait instead
*/
function clone(array $override_input = [], bool $history = true) {
global $DB, $CFG_GLPI;
\Toolbox::deprecated();
if ($DB->isSlave()) {
return false;
}
......@@ -1397,8 +1399,10 @@ class CommonDBTM extends CommonGLPI {
* @param array $input datas used to add the item
*
* @return array the modified $input array
* @deprecated x.x.x Use the {@link \Glpi\Features\Clonable} trait
**/
function prepareInputForClone($input) {
\Toolbox::deprecated();
unset($input['id']);
unset($input['date_mod']);
unset($input['date_creation']);
......@@ -1423,8 +1427,10 @@ class CommonDBTM extends CommonGLPI {
* @param $history do history log ?
*
* @return void
* @deprecated x.x.x Use the {@link \Glpi\Features\Clonable} trait
**/
function post_clone($source, $history) {
\Toolbox::deprecated();
}
......
......@@ -38,6 +38,7 @@ if (!defined('GLPI_ROOT')) {
* CommonITILObject Class
**/
abstract class CommonITILObject extends CommonDBTM {
use \Glpi\Features\Clonable;
/// Users by type
protected $users = [];
......@@ -2024,9 +2025,9 @@ abstract class CommonITILObject extends CommonDBTM {
}
/**
* @see CommonDBTM::post_clone
*/
function post_clone($source, $history) {
* @see Glpi\Features\Clonable::post_clone
*/
public function post_clone($source, $history) {
global $DB;
$update = [];
if (isset($source->fields['users_id_lastupdater'])) {
......@@ -2040,9 +2041,11 @@ abstract class CommonITILObject extends CommonDBTM {
$update,
['id' => $this->getID()]
);
}
public function getCloneRelations(): array {
return [];
}
/**
* @since 0.84
......
......@@ -37,22 +37,58 @@ if (!defined('GLPI_ROOT')) {
}
use CommonDBConnexity;
use CommonDBTM;
use Toolbox;
/**
* Clonable objects
**/
trait Clonable {
/**
* Get relations class to clone along with current eleemnt
* Get relations class to clone along with current element.
*
* @return CommonDBTM::class[]
*/
abstract public function getCloneRelations() :array;
public function post_clone($source, $history) {
parent::post_clone($source, $history);
/**
* Clean input used to clone.
*
* @param array $input
*
* @return array
*
* @since x.x.x
*/
private function cleanCloneInput(array $input): array {
$properties_to_clean = [
'id',
'date_mod',
'date_creation',
'template_name',
'is_template'
];
foreach ($properties_to_clean as $property) {
if (array_key_exists($property, $input)) {
unset($input[$property]);
}
}
return $input;
}
/**
* Clone the item's relations.
*
* @param CommonDBTM $source
* @param bool $history
*
* @return void
*
* @since x.x.x
*/
private function cloneRelations(CommonDBTM $source, bool $history): void {
$clone_relations = $this->getCloneRelations();
foreach ($clone_relations as $classname) {
if (!is_a($classname, CommonDBConnexity::class, true)) {
......@@ -72,4 +108,63 @@ trait Clonable {
}
}
}
/**
* Prepare input datas for cloning the item.
* This empty method is meant to be redefined in objects that need a specific prepareInputForClone logic.
*
* @since x.x.x
*
* @param array $input datas used to add the item
*
* @return array the modified $input array
*/
public function prepareInputForClone($input) {
return $input;
}
/**
* Clones the current item
*
* @since x.x.x
*
* @param array $override_input custom input to override
* @param boolean $history do history log ?
*
* @return integer The new ID of the clone (or false if fail)
*/
public function clone(array $override_input = [], bool $history = true) {
global $DB;
if ($DB->isSlave()) {
return false;
}
$new_item = new static();
$input = Toolbox::addslashes_deep($this->fields);
foreach ($override_input as $key => $value) {
$input[$key] = $value;
}
$input = $new_item->cleanCloneInput($input);
$input = $new_item->prepareInputForClone($input);
$input['clone'] = true;
$newID = $new_item->add($input, [], $history);
if ($newID !== false) {
$new_item->cloneRelations($this, $history);
$new_item->post_clone($this, $history);
}
return $newID;
}
/**
* Post clone logic.
* This empty method is meant to be redefined in objects that need a specific post_clone logic.
*
* @param $source
* @param $history
*/
public function post_clone($source, $history) {
}
}
......@@ -30,6 +30,8 @@
* ---------------------------------------------------------------------
*/
use Glpi\Features\Clonable;
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
......@@ -543,7 +545,9 @@ class MassiveAction {
//TRANS: select action 'update' (before doing it)
$actions[$self_pref.'update'] = _x('button', 'Update');
$actions[$self_pref.'clone'] = _x('button', 'Clone');
if (Toolbox::hasTrait($itemtype, Clonable::class)) {
$actions[$self_pref . 'clone'] = _x('button', 'Clone');
}
}
Infocom::getMassiveActionsForItemtype($actions, $itemtype, $is_deleted, $checkitem);
......
......@@ -223,7 +223,6 @@ class NetworkPort extends CommonDBChild {
}
function post_clone($source, $history) {
parent::post_clone($source, $history);
$instantiation = $source->getInstantiation();
if ($instantiation !== false) {
$instantiation->fields[$instantiation->getIndexName()] = $this->getID();
......@@ -231,6 +230,7 @@ class NetworkPort extends CommonDBChild {
}
}
/**
* \brief split input fields when validating a port
*
......
......@@ -2040,6 +2040,6 @@ class ProjectTask extends CommonDBChild implements CalDAVCompatibleItemInterface
public function prepareInputForClone($input) {
$input['uuid'] = \Ramsey\Uuid\Uuid::uuid4();
return parent::prepareInputForClone($input);
return $input;
}
}
......@@ -3227,6 +3227,6 @@ class Rule extends CommonDBTM {
$input = Toolbox::addslashes_deep($input);
return parent::prepareInputForClone($input);
return $input;
}
}
......@@ -3309,4 +3309,24 @@ HTML;
}, $string);
return $string;
}
/**
* Checks if the given class or object has the specified trait.
* This function checks the class itself and all parent classes for the trait.
* @since x.x.x
* @param string|object $class The class or object
* @param string $trait The trait
* @return bool True if the class or its parents have the specified trait
*/
public static function hasTrait($class, string $trait): bool {
// Get traits of all parent classes
do {
$traits = class_uses($class, true);
if (in_array($trait, $traits, true)) {
return true;
}
} while ($class = get_parent_class($class));
return false;
}
}
......@@ -38,6 +38,7 @@ use Glpi\Exception\ForgetPasswordException;
use Sabre\VObject;
class User extends CommonDBTM {
use Glpi\Features\Clonable;
// From CommonDBTM
public $dohistory = true;
......@@ -63,6 +64,16 @@ class User extends CommonDBTM {
private $entities = null;
public function getCloneRelations() :array {
return [
Profile_User::class,
Group_User::class
];
}
public function post_clone($source, $history) {
//FIXME? clone config
}
static function getTypeName($nb = 0) {
return _n('User', 'Users', $nb);
......
......@@ -261,10 +261,7 @@ class Calendar extends DbTestCase {
$calendar = new \Calendar();
$this->boolean($calendar->getFromDB($id))->isTrue();
$this->boolean($calendar->duplicate())->isTrue();
$other_id = $calendar->fields['id'];
$this->integer($other_id)->isGreaterThan($id);
$this->boolean($calendar->getFromDB($other_id))->isTrue();
$this->integer($calendar->clone())->isGreaterThan($id);
//should have been duplicated too.
$this->checkXmas($calendar);
......
......@@ -1391,7 +1391,6 @@ class Ticket extends DbTestCase {
public function testClone() {
$this->login();
$this->setEntity('Root entity', true);
$ticket = new \Ticket();
$ticket = getItemByTypeName('Ticket', '_ticket01');
$date = date('Y-m-d H:i:s');
......
<?php
/**
* ---------------------------------------------------------------------
* GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2015-2021 Teclib' and contributors.
*
* http://glpi-project.org
*
* based on GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* GLPI is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* GLPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------
*/
namespace tests\units\Glpi\Features;
/**
* Test for the {@link \Glpi\Features\Clonable} feature
*/
class Clonable extends \DbTestCase {
public function massiveActionTargetingProvider() {
return [
[\Computer::class, true],
[\Monitor::class, true],
[\Software::class, true],
[\Ticket::class, true],
[\Plugin::class, false],
[\Config::class, false]
];
}
/**
* @param $class
* @param $result
* @dataProvider massiveActionTargetingProvider
*/
public function testMassiveActionTargeting($class, $result) {
$this->login();
$ma_prefix = 'MassiveAction' . \MassiveAction::CLASS_ACTION_SEPARATOR;
$actions = \MassiveAction::getAllMassiveActions($class);
$this->boolean(array_key_exists($ma_prefix . 'clone', $actions))->isIdenticalTo($result);
}
}
\ No newline at end of file
......@@ -33,6 +33,10 @@
namespace tests\units;
use Glpi\Api\Deprecated\TicketFollowup;
use Glpi\Features\Clonable;
use Glpi\Features\DCBreadcrumb;
use Glpi\Features\Kanban;
use Glpi\Features\PlanningEvent;
use ITILFollowup;
use Ticket;
......@@ -988,4 +992,27 @@ class Toolbox extends \GLPITestCase {
->withMessage('Calling this function is deprecated')
->exists();
}
public function hasTraitProvider() {
return [
[\Computer::class, Clonable::class, true],
[\Monitor::class, Clonable::class, true],
[\CommonITILObject::class, Clonable::class, true],
[\Ticket::class, Clonable::class, true],
[\Plugin::class, Clonable::class, false],
[\Project::class, Kanban::class, true],
[\Computer::class, Kanban::class, false],
[\Computer::class, DCBreadcrumb::class, true],
[\Ticket::class, DCBreadcrumb::class, false],
[\CommonITILTask::class, PlanningEvent::class, true],
[\Computer::class, PlanningEvent::class, false],
];
}
/**
* @dataProvider hasTraitProvider
*/
public function testHasTrait($class, $trait, $result) {
$this->boolean(\Toolbox::hasTrait($class, $trait))->isIdenticalTo((bool)$result);
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment