<?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/>.
 * ---------------------------------------------------------------------
 */

if (!defined('GLPI_ROOT')) {
   die("Sorry. You can't access this file directly");
}


/**
 * @since 0.84
**/
class CommonDBConnexityItemNotFound extends Exception {

}





/**
 * Common DataBase Connexity Table Manager Class
 * This class factorize code for CommonDBChild and CommonDBRelation. Both classes themselves
 * factorize and normalize the behaviour of all Child and Relations.
 * As such, several elements are directly managed by these two classes since 0.84 :
 * - Check:  all can* methods (canCreate, canUpdate, canViewItem, canDeleteItem ...) are
 *           defined.
 * - Update: when we try to update an attached element, we check if we change its parent item(s).
 *           If we change its parent(s), then we check if we can delete the item with previous
 *           parent(s) (cf. "check" before) AND we can create the item with the new parent(s).
 * - Entity: Entity is automatically setted or updated when setting or changing an attached item.
 *           Thus, you don't have any more to worry about entities.
 *            (May be disable using $disableAutoEntityForwarding)
 * - Log:    when we create, update or delete an item, we update its parent(s)'s histories to
 *           notify them of the creation, update or deletion
 * - Flying items : some items can be on the stock. For instance, before beeing plugged inside a
 *                  computer, an Item_DeviceProcessor can be without any parent. It is now possible
 *                  to define such items and transfer them from parent to parent.
 *
 * The aim of the new check is that the rights on a Child or a Relation are driven by the
 * parent(s): you can create, delete or update the item if and only if you can update its parent(s);
 * you can view the item if and only if you can view its parent(s). Beware that it differs from the
 * default behaviour of CommonDBTM: if you don't define canUpdate or canDelete, then it checks
 * canCreate and by default canCreate returns false (thus, if you don't do anything, you don't have
 * any right). A side effect is that if you define specific rights (see NetworkName::canCreate())
 * for your classes you must define all rights (canCreate, canView, canUpdate and canDelete).
 *
 * @warning You have to care of calling CommonDBChild or CommonDBRelation methods if you override
 * their methods (for instance: call parent::prepareInputForAdd($input) if you define
 * prepareInputForAdd). You can find an example with UserEmail::prepareInputForAdd($input).
 *
 * @since 0.84
**/
abstract class CommonDBConnexity extends CommonDBTM {

   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

   static public $canDeleteOnItemClean          = true;
   /// Disable auto forwarding information about entities ?
   static public $disableAutoEntityForwarding   = false;


   /**
    * 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()
    * But CommonDBRelation also use it to get more complex result
    *
    * @since 9.4
    *
    * @param string  $itemtype the type of the item to look for
    * @param integer $items_id the id of the item to look for
    *
    * @return array|null
    */
   static function getSQLCriteriaToSearchForItem($itemtype, $items_id) {
      return null;
   }


   /**
    * Clean the Connecity Table when item of the relation is deleted
    * To be call from the cleanDBonPurge of each Item class
    *
    * @param string  $itemtype  type of the item
    * @param integer $items_id  id of the item
   **/
   function cleanDBonItemDelete ($itemtype, $items_id) {
      global $DB;

      $criteria = static::getSQLCriteriaToSearchForItem($itemtype, $items_id);
      if ($criteria !== null) {
         $input = [
            '_no_history'     => true,
            '_no_notif'       => true
         ];

         $iterator = $DB->request($criteria);
         while ($data = $iterator->next()) {
            $input[$this->getIndexName()] = $data[$this->getIndexName()];
            $this->delete($input, 1);
         }
      }
   }


   /**
    * get associated item (defined by $itemtype and $items_id)
    *
    * @see CommonDBConnexity::getItemFromArray()
    *
    * @param string  $itemtype          the name of the field of the type of the item to get
    * @param string  $items_id          the name of the field of the id of the item to get
    * @param boolean $getFromDB         do we have to load the item from the DB ?
    * @param boolean $getEmpty          else : do we have to load an empty item ?
    * @param boolean $getFromDBOrEmpty  get from DB if possible, else, getEmpty
    *
    * @return CommonDBTM|boolean the item or false if we cannot load the item
   **/
   function getConnexityItem($itemtype, $items_id, $getFromDB = true, $getEmpty = true,
                             $getFromDBOrEmpty = false) {

      return static::getItemFromArray($itemtype, $items_id, $this->fields, $getFromDB,
                                      $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(static::getSQLCriteriaToSearchForItem($itemtype, $items_id));
   }

   /**
    * Get item field name for cloning
    *
    * @param string $itemtype Item type
    *
    * @return string
    */
   abstract public static function getItemField($itemtype): string;

   /**
    * get associated item (defined by $itemtype and $items_id)
    *
    * @param string  $itemtype          the name of the field of the type of the item to get
    * @param string  $items_id          the name of the field of the id of the item to get
    * @param array   $array             the array in we have to search ($input, $this->fields ...)
    * @param boolean $getFromDB         do we have to load the item from the DB ?
    * @param boolean $getEmpty          else : do we have to load an empty item ?
    * @param boolean $getFromDBOrEmpty  get from DB if possible, else, getEmpty
    *
    * @return CommonDBTM|boolean the item or false if we cannot load the item
   **/
   static function getItemFromArray($itemtype, $items_id, array $array, $getFromDB = true,
                                    $getEmpty = true, $getFromDBOrEmpty = false) {

      if (preg_match('/^itemtype/', $itemtype)) {
         if (isset($array[$itemtype])) {
            $type = $array[$itemtype];
         } else {
            $type = '';
         }
      } else {
         $type = $itemtype;
      }
      $item = ($type ? getItemForItemtype($type) : false);

      if ($item !== false) {
         if ($getFromDB
             || $getFromDBOrEmpty) {
            if (isset($array[$items_id])
                && $item->getFromDB($array[$items_id])) {
               return $item;
            }
            if ($getFromDBOrEmpty) {
               if ($item->getEmpty()) {
                  return $item;
               }
            }
         } else if ($getEmpty) {
            if ($item->getEmpty()) {
               return $item;
            }
         } else {
            return $item;
         }
         unset($item);
      }

      return false;
   }


   /**
    * Factorization of prepareInputForUpdate for CommonDBRelation and CommonDBChild. Just check if
    * we can change the item
    *
    * @warning if the update is not possible (right problem), then $input become false
    *
    * @param $input   array   the new values for the current item
    * @param $fields  array   list of fields that define the attached items
    *
    * @return true if the attached item has changed, false if the attached items has not changed
    **/
   function checkAttachedItemChangesAllowed(array $input, array $fields) {

      // Merge both arrays to ensure all the fields are defined for the following checks
      $input = array_merge($this->fields, $input);

      $have_to_check = false;
      foreach ($fields as $field_name) {
         if ((isset($this->fields[$field_name]))
             && ($input[$field_name] != $this->fields[$field_name])) {

            $have_to_check = true;
            break;
         }
      }

      if ($have_to_check) {

         $new_item = clone $this;

         // Solution 1 : If we cannot create the new item or delete the old item,
         // then we cannot update the item
         unset($new_item->fields);
         if (!$new_item->can(-1, CREATE, $input)
              || !$this->can($this->getID(), DELETE)
              || !$this->can($this->getID(), PURGE)) {
            Session::addMessageAfterRedirect(__('Cannot update item: not enough right on the parent(s) item(s)'),
                                             INFO, true);
            return false;
         }

         // Solution 2 : simple check ! Can we update the item with new values ?
         // if (!$new_item->can($input['id'], 'w')) {
         //    Session::addMessageAfterRedirect(__('Cannot update item: not enough right on the parent(s) item(s)'),
         //                                     INFO, true);
         //    return false;
         // }
      }

      return true;
   }

   /**
    * Is auto entityForwarding needed ?
    *
    * @return boolean
    **/
   function tryEntityForwarding() {
      return (!static::$disableAutoEntityForwarding && $this->isEntityAssign());
   }


   /**
    * Factorization of canCreate, canView, canUpate and canDelete. It checks the ability to
    * create, view, update or delete the item if it is possible (ie : $itemtype != 'itemtype')
    *
    * The aim is that the rights are driven by the attached item : if we can do the action on the
    * item, then we can do the action of the CommonDBChild or the CommonDBRelation. Thus, it is the
    * inverse of CommonDBTM's behaviour, that, by default forbid any action (cf.
    * CommonDBTM::canCreate and CommonDBTM::canView)
    *
    * @warning By default, if the action is possible regarding the attaching item, then it is
    * possible on the CommonDBChild and the CommonDBRelation.
    *
    * @param string  $method     the method to check (canCreate, canView, canUpdate of canDelete)
    * @param integer $item_right the right to check (DONT_CHECK_ITEM_RIGHTS, HAVE_VIEW_RIGHT_ON_ITEM ...)
    * @param string  $itemtype   the name of the field of the type of the item to get
    * @param string  $items_id   the name of the field of the id of the item to get
    *
    * @return boolean true if we have absolute right to create the current connexity
   **/
   static function canConnexity($method, $item_right, $itemtype, $items_id) {

      if (($item_right != self::DONT_CHECK_ITEM_RIGHTS)
          && (!preg_match('/^itemtype/', $itemtype))) {
         if ($item_right == self::HAVE_VIEW_RIGHT_ON_ITEM) {
            $method = 'canView';
         }
         if (!$itemtype::$method()) {
            return false;
         }
      }
      return true;
   }


   /**
    * Factorization of canCreateItem, canViewItem, canUpateItem and canDeleteItem. It checks the
    * ability to create, view, update or delete the item. If we cannot check the item (none is
    * existing), then we can do the action of the current connexity
    *
    * @param string          $methodItem    the method to check (canCreateItem, canViewItem,
                                            canUpdateItem or canDeleteItem)
    * @param string          $methodNotItem the method to check (canCreate, canView, canUpdate of canDelete)
    * @param integer         $item_right    the right to check (DONT_CHECK_ITEM_RIGHTS, HAVE_VIEW_RIGHT_ON_ITEM ...)
    * @param string          $itemtype      the name of the field of the type of the item to get
    * @param string          $items_id      the name of the field of the id of the item to get
    * @param CommonDBTM|null &$item         the item concerned by the item
    *
    * @return true if we have absolute right to create the current connexity
   **/
   function canConnexityItem($methodItem, $methodNotItem, $item_right, $itemtype, $items_id,
                             CommonDBTM &$item = null) {

      // Do not get it twice
      $connexityItem = $item;
      if (is_null($connexityItem)) {
         $connexityItem = $this->getConnexityItem($itemtype, $items_id);

         // Set value in $item to reuse it on future calls
         if ($connexityItem instanceof CommonDBTM) {
            $item = $this->getConnexityItem($itemtype, $items_id);
         }
      }
      if ($item_right != self::DONT_CHECK_ITEM_RIGHTS) {
         if ($connexityItem !== false) {
            if ($item_right == self::HAVE_VIEW_RIGHT_ON_ITEM) {
               $methodNotItem = 'canView';
               $methodItem    = 'canViewItem';
            }
            // here, we can check item's global rights
            if (preg_match('/^itemtype/', $itemtype)) {
               if (!$connexityItem->$methodNotItem()) {
                  return false;
               }
            }
            return $connexityItem->$methodItem();
         } else {
            // if we cannot get the parent, then we throw an exception
            throw new CommonDBConnexityItemNotFound();
         }
      }
      return true;
   }


   /**
    * @since 0.84
    *
    * Get the change values for history when only the fields of the CommonDBChild are updated
    * @warning can be call as many time as fields are updated
    *
    * @param string $field the name of the field that has changed
    *
    * @return array as the third parameter of Log::history() method or false if we don't want to
    *         log for the given field
   **/
   function getHistoryChangeWhenUpdateField($field) {

      return ['0', addslashes($this->oldvalues[$field]), addslashes($this->fields[$field])];
   }


   /**
    * Factorized method to search difference when updating a connexity : return both previous
    * item and new item if both are different. Otherwise returns new items
    *
    * @param string $itemtype  the name of the field of the type of the item to get
    * @param string $items_id  the name of the field of the id of the item to get
    *
    * @return array containing "previous" (if exists) and "new". Beware that both can be equal
    *         to false
   **/
   function getItemsForLog($itemtype, $items_id) {

      $newItemArray = [
         $items_id => $this->fields[$items_id],
      ];
      $previousItemArray = [];

      if (isset($this->oldvalues[$items_id])) {
         $previousItemArray[$items_id] = $this->oldvalues[$items_id];
      } else {
         $previousItemArray[$items_id] = $this->fields[$items_id];
      }

      if (preg_match('/^itemtype/', $itemtype)) {
         $newItemArray[$itemtype] = $this->fields[$itemtype];
         if (isset($this->oldvalues[$itemtype])) {
            $previousItemArray[$itemtype] = $this->oldvalues[$itemtype];
         } else {
            $previousItemArray[$itemtype] = $this->fields[$itemtype];
         }
      }

      $result = ['new' => self::getItemFromArray($itemtype, $items_id, $newItemArray)];
      if ($previousItemArray !== $newItemArray) {
         $result['previous'] = self::getItemFromArray($itemtype, $items_id, $previousItemArray);
      }

      return $result;
   }

   /**
    * Get all specificities of the current itemtype concerning the massive actions
    *
    * @since 0.85
    *
    * @return array of the specificities:
    *        'reaffect'   is it possible to reaffect the connexity (1 or 2 for CommonDBRelation)
    *        'itemtypes'  the types of the item in cas of reaffectation
    *        'normalized' array('affect', 'unaffect') of arrays containing each action
    *        'button_labels'          array of the labels of the button indexed by the action name
   **/
   static function getConnexityMassiveActionsSpecificities() {

      return ['reaffect'      => false,
                   'itemtypes'     => [],
                   'normalized'    => ['affect'   => ['affect'],
                                            'unaffect' => ['unaffect']],
                   'action_name'   => ['affect'   => _x('button', 'Associate'),
                                            'unaffect' => _x('button', 'Dissociate')]];
   }


   /**
    * @since 0.85
    *
    * @see CommonDBTM::getMassiveActionsForItemtype()
   **/
   static function getMassiveActionsForItemtype(array &$actions, $itemtype, $is_deleted = 0,
                                                CommonDBTM $checkitem = null) {

      $unaffect = false;
      $affect   = false;
      if (is_a($itemtype, 'CommonDBChild', true)) {
         $specificities = $itemtype::getConnexityMassiveActionsSpecificities();
         if (!$itemtype::$mustBeAttached) {
            $unaffect = true;
            $affect   = true;
         } else if ($specificities['reaffect']) {
            $affect = true;
         }
      } else if (is_a($itemtype, 'CommonDBRelation', true)) {
         $specificities = $itemtype::getConnexityMassiveActionsSpecificities();
         if ((!$itemtype::$mustBeAttached_1) || (!$itemtype::$mustBeAttached_2)) {
            $unaffect = true;
            $affect   = true;
         } else if ($specificities['reaffect']) {
            $affect = true;
         }
      } else {
         return;
      }

      $prefix = __CLASS__.MassiveAction::CLASS_ACTION_SEPARATOR;

      if ($unaffect) {
         $actions[$prefix.'unaffect'] = $specificities['action_name']['unaffect'];
      }

      if ($affect) {
         $actions[$prefix.'affect'] = $specificities['action_name']['affect'];
      }

      parent::getMassiveActionsForItemtype($actions, $itemtype, $is_deleted, $checkitem);
   }


   /**
    * @since 0.85
    *
    * @see CommonDBTM::showMassiveActionsSubForm()
   **/
   static function showMassiveActionsSubForm(MassiveAction $ma) {

      $action = $ma->getAction();
      $items  = $ma->getItems();

      $itemtypes_affect   = [];
      $itemtypes_unaffect = [];
      foreach (array_keys($items) as $itemtype) {
         if (!is_a($itemtype, __CLASS__, true)) {
            continue;
         }
         $specificities = $itemtype::getConnexityMassiveActionsSpecificities();
         if (in_array($action, $specificities['normalized']['affect'])) {
            $itemtypes_affect[$itemtype] = $specificities;
            continue;
         }
         if (in_array($action, $specificities['normalized']['unaffect'])) {
            $itemtypes_unaffect[$itemtype] = $specificities;
            continue;
         }
      }

      if (count($itemtypes_affect) > count($itemtypes_unaffect)) {
         $normalized_action = 'affect';
         $itemtypes         = $itemtypes_affect;
      } else if (count($itemtypes_affect) < count($itemtypes_unaffect)) {
         $normalized_action = 'unaffect';
         $itemtypes         = $itemtypes_unaffect;
      } else {
         return parent::showMassiveActionsSubForm($ma);
      }

      switch ($normalized_action) {
         case 'unaffect' :
            foreach ($itemtypes as $itemtype => $specificities) {
               if (is_a($itemtype, 'CommonDBRelation', true)) {
                  $peer_field = "peer[$itemtype]";
                  if ((!$itemtype::$mustBeAttached_1) && (!$itemtype::$mustBeAttached_2)) {
                     // Should never occur ... But we must care !
                     $values = [];
                     if ((empty($itemtype::$itemtype_1))
                      || (preg_match('/^itemtype/', $itemtype::$itemtype_1))) {
                        $values[0] = __('First Item');
                     } else {
                        $itemtype_1 = $itemtype::$itemtype_1;
                        $values[0]  = $itemtype_1::getTypeName(Session::getPluralNumber());
                     }
                     if ((empty($itemtype::$itemtype_2))
                         || (preg_match('/^itemtype/', $itemtype::$itemtype_2))) {
                        $values[1] = __('Second Item');
                     } else {
                        $itemtype_2 = $itemtype::$itemtype_2;
                        $values[1]  = $itemtype_2::getTypeName(Session::getPluralNumber());
                     }
                     echo sprintf(__('Select a peer for %s:'), $itemtype::getTypeName());
                     Dropdown::showFromArray($peer_field, $values);
                     echo "<br>\n";
                  } else if (!$itemtype::$mustBeAttached_1) {
                     echo "<input type='hidden' name='$peer_field' value='0'>";
                  } else if (!$itemtype::$mustBeAttached_2) {
                     echo "<input type='hidden' name='$peer_field' value='1'>";
                  }
               }
            }
            echo "<br><br>".Html::submit(_x('button', 'Dissociate'), ['name' => 'massiveaction']);
            return true;

         case 'affect' :
            $peertypes = [];
            foreach ($itemtypes as $itemtype => $specificities) {
               if (!$specificities['reaffect']) {
                  continue;
               }
               if (is_a($itemtype, 'CommonDBRelation', true)) {
                  if ($specificities['reaffect'] == 1) {
                     $peertype = $itemtype::$itemtype_1;
                  } else {
                     $peertype = $itemtype::$itemtype_2;
                  }
               } else {
                  $peertype = $itemtype::$itemtype;
               }
               if (preg_match('/^itemtype/', $peertype)) {
                  $peertypes = array_merge($peertypes, $specificities['itemtypes']);
               } else {
                  $peertypes[] = $peertype;
               }
            }
            $peertypes = array_unique($peertypes);
            if (count($peertypes) == 0) {
               echo __('Unable to reaffect given elements !');
               exit();
            }
            $options = [];
            if (count($peertypes) == 1) {
               $options['name']   = 'peers_id';
               $type_for_dropdown = $peertypes[0];
               if (preg_match('/^itemtype/', $peertype)) {
                  echo Html::hidden('peertype', ['value' => $type_for_dropdown]);
               }
               $type_for_dropdown::dropdown($options);
            } else {
               $options['itemtype_name'] = 'peertype';
               $options['items_id_name'] = 'peers_id';
               $options['itemtypes']     = $peertypes;
               Dropdown::showSelectItemFromItemtypes($options);
            }

            echo "<br><br>".Html::submit(_x('button', 'Associate'), ['name' => 'massiveaction']);
            return true;
      }

      return parent::showMassiveActionsSubForm($ma);
   }


   /**
    * @since 0.85
    *
    * Set based array for static::add or static::update in case of massive actions are doing
    * something.
    *
    * @param string     $action  the name of the action
    * @param CommonDBTM $item    the item on which apply the massive action
    * @param integer[]  $ids     the ids of the item on which apply the action
    * @param array      $input   the input provided by the form ($_POST, $_GET ...)
    *
    * @return array containing the elements
   **/
   static function getConnexityInputForProcessingOfMassiveActions($action, CommonDBTM $item,
                                                                  array $ids, array $input) {
      return [];
   }


   /**
    * @since 0.85
    *
    * @see CommonDBTM::processMassiveActionsForOneItemtype()
   **/
   static function processMassiveActionsForOneItemtype(MassiveAction $ma, CommonDBTM $item,
                                                       array $ids) {

      if (!is_a($item, __CLASS__, true)) {
         parent::processMassiveActionsForOneItemtype($ma, $item, $ids);
         return;
      }

      $itemtype      = $item->getType();
      $specificities = $itemtype::getConnexityMassiveActionsSpecificities();

      $action        = $ma->getAction();
      $input         = $ma->getInput();

      // First, get normalized action : affect or unaffect
      if (in_array($action, $specificities['normalized']['affect'])) {
         $normalized_action = 'affect';
      } else if (in_array($action, $specificities['normalized']['unaffect'])) {
         $normalized_action = 'unaffect';
      } else {
         // If we cannot get normalized action, then, its not for this method !
         parent::processMassiveActionsForOneItemtype($ma, $item, $ids);
         return;
      }

      switch ($normalized_action) {
         case 'unaffect' :
            foreach ($ids as $key) {
               if ($item->can($key, UPDATE)) {
                  if ($item instanceof CommonDBRelation) {
                     if (isset($input['peer'][$item->getType()])) {
                        if ($item->affectRelation($key, $input['peer'][$item->getType()])) {
                           $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_OK);
                        } else {
                           $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_KO);
                           $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION));
                        }
                     } else {
                        $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_KO);
                        $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION));
                     }
                  } else if ($item instanceof CommonDBChild) {
                     if ($item->affectChild($key)) {
                        $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_OK);
                     } else {
                        $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_KO);
                        $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION));
                     }
                  }
               } else {
                  $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_NORIGHT);
                  $ma->addMessage($item->getErrorMessage(ERROR_RIGHT));
               }
            }
            return;

         case 'affect' :
            if (!$specificities['reaffect']) {
               $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_KO);
               $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION));
               return;
            }
            if (is_a($item, 'CommonDBRelation', true)) {
               if ($specificities['reaffect'] == 1) {
                  $peertype = $itemtype::$itemtype_1;
                  $peers_id = $itemtype::$items_id_1;
               } else {
                  $peertype = $itemtype::$itemtype_2;
                  $peers_id = $itemtype::$items_id_2;
               }
            } else {
               $peertype = $itemtype::$itemtype;
               $peers_id = $itemtype::$items_id;
            }
            $input2 = $itemtype::getConnexityInputForProcessingOfMassiveActions($action, $item,
                                                                                $ids, $input);
            $input2[$peers_id] = $input['peers_id'];
            if (preg_match('/^itemtype/', $peertype)) {
               if (!in_array($input['peertype'], $specificities['itemtypes'])) {
                  $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_KO);
                  $ma->addMessage($item->getErrorMessage(ERROR_NOT_FOUND));
                  return;
               }
               $input2[$peertype] = $input['peertype'];
            } else {
               if ($peertype != $input['peertype']) {
                  $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_KO);
                  $ma->addMessage($item->getErrorMessage(ERROR_NOT_FOUND));
                  return;
               }
            }
            foreach ($ids as $key) {
               if (!$item->getFromDB($key)) {
                  $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_KO);
                  $ma->addMessage($item->getErrorMessage(ERROR_NOT_FOUND));
                  continue;
               }
               if (preg_match('/^itemtype/', $peertype)) {
                  if (($input2[$peertype] == $item->fields[$peertype])
                      && ($input2[$peers_id] == $item->fields[$peers_id])) {
                     $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_KO);
                     $ma->addMessage($item->getErrorMessage(ERROR_ALREADY_DEFINED));
                     continue;
                  }
               } else {
                  if ($input2[$peers_id] == $item->fields[$peers_id]) {
                     $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_KO);
                     $ma->addMessage($item->getErrorMessage(ERROR_ALREADY_DEFINED));
                     continue;
                  }
               }
               $input2[$item->getIndexName()] = $item->getID();
               if ($item->can($item->getID(), UPDATE, $input2)) {
                  if ($item->update($input2)) {
                     $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_OK);
                  } else {
                     $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_KO);
                     $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION));
                  }
               } else {
                  $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_NORIGHT);
                  $ma->addMessage($item->getErrorMessage(ERROR_RIGHT));
               }
            }
            return;
      }

      parent::processMassiveActionsForOneItemtype($ma, $item, $ids);
   }

}
