<?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");
}

/**
 * FieldUnicity Class
**/
class FieldUnicity extends CommonDropdown {

   // From CommonDBTM
   public $dohistory          = true;

   public $second_level_menu  = "control";
   public $can_be_translated  = false;

   static $rightname          = 'config';


   static function getTypeName($nb = 0) {
      return __('Fields unicity');
   }


   static function canCreate() {
      return static::canUpdate();
   }


   /**
    * @since 0.85
   **/
   static function canPurge() {
      return static::canUpdate();
   }


   function displayHeader() {
      Html::header(FieldUnicity::getTypeName(Session::getPluralNumber()), $_SERVER['PHP_SELF'], "config", "fieldunicity");
   }

   function getAdditionalFields() {

      return [['name'  => 'is_active',
                         'label' => __('Active'),
                         'type'  => 'bool'],
                   ['name'  => 'itemtype',
                         'label' => _n('Type', 'Types', 1),
                         'type'  => 'unicity_itemtype'],
                   ['name'  => 'fields',
                         'label' => __('Unique fields'),
                         'type'  => 'unicity_fields'],
                   ['name'  => 'action_refuse',
                         'label' => __('Record into the database denied'),
                         'type'  => 'bool'],
                   ['name'  => 'action_notify',
                         'label' => __('Send a notification'),
                         'type'  => 'bool']];
   }


   /**
    * Define tabs to display
    *
    * @param $options array
   **/
   function defineTabs($options = []) {

      $ong          = [];
      $this->addDefaultFormTab($ong);
      $this->addStandardTab(__CLASS__, $ong, $options);
      $this->addStandardTab('Log', $ong, $options);

      return $ong;
   }


   function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) {

      if (!$withtemplate) {
         if ($item->getType() == $this->getType()) {
            return __('Duplicates');
         }
      }
      return '';
   }


   static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) {

      if ($item->getType()==__CLASS__) {
         self::showDoubles($item);
      }
      return true;
   }


   /**
    * Display specific fields for FieldUnicity
    *
    * @param $ID
    * @param $field array
   **/
   function displaySpecificTypeField($ID, $field = []) {

      switch ($field['type']) {
         case 'unicity_itemtype' :
            $this->showItemtype($ID, $this->fields['itemtype']);
            break;

         case 'unicity_fields' :
            self::selectCriterias($this);
            break;
      }
   }


   /**
    * Display a dropdown which contains all the available itemtypes
    *
    * @param integer $ID     The field unicity item id
    * @param integer $value  The selected value (default 0)
    *
    * @return void
   **/
   function showItemtype($ID, $value = 0) {
      global $CFG_GLPI;

      //Criteria already added : only display the selected itemtype
      if ($ID > 0) {
         if ($item = getItemForItemtype($this->fields['itemtype'])) {
            echo $item->getTypeName();
         }
         echo "<input type='hidden' name='itemtype' value='".$this->fields['itemtype']."'>";

      } else {
         $options = [];
         //Add criteria : display dropdown
         foreach ($CFG_GLPI['unicity_types'] as $itemtype) {
            if ($item = getItemForItemtype($itemtype)) {
               if ($item->canCreate()) {
                  $options[$itemtype] = $item->getTypeName(1);
               }
            }
         }
         asort($options);
         $rand = Dropdown::showFromArray('itemtype', $options, ['display_emptychoice' => true]);

         $params = ['itemtype' => '__VALUE__',
                         'id'       => $ID];
         Ajax::updateItemOnSelectEvent("dropdown_itemtype$rand", "span_fields",
                                       $CFG_GLPI["root_doc"]."/ajax/dropdownUnicityFields.php",
                                       $params);
      }

   }


   /**
    * Return criteria unicity for an itemtype, in an entity
    *
    * @param string  itemtype       the itemtype for which unicity must be checked
    * @param integer entities_id    the entity for which configuration must be retrivied
    * @param boolean $check_active
    *
    * @return array an array of fields to check, or an empty array if no
   **/
   public static function getUnicityFieldsConfig($itemtype, $entities_id = 0, $check_active = true) {
      global $DB;

      //Get the first active configuration for this itemtype
      $request = [
         'FROM'   => 'glpi_fieldunicities',
         'WHERE'  => [
            'itemtype'  => $itemtype
         ] + getEntitiesRestrictCriteria('glpi_fieldunicities', '', $entities_id, true),
         'ORDER'  => ['entities_id DESC']
      ];

      if ($check_active) {
         $request['WHERE']['is_active'] = 1;
      }
      $iterator = $DB->request($request);

      $current_entity = false;
      $return         = [];
      while ($data = $iterator->next()) {
         //First row processed
         if (!$current_entity) {
            $current_entity = $data['entities_id'];
         }
         //Process only for one entity, not more
         if ($current_entity != $data['entities_id']) {
            break;
         }
         $return[] = $data;
      }
      return $return;
   }


   /**
    * Display a list of available fields for unicity checks
    *
    * @param CommonDBTM $unicity
    *
    * @return void
   **/
   static function selectCriterias(CommonDBTM $unicity) {
      echo "<span id='span_fields'>";

      if (!isset($unicity->fields['itemtype']) || !$unicity->fields['itemtype']) {
         echo  "</span>";
         return;
      }

      if (!isset($unicity->fields['entities_id'])) {
         $unicity->fields['entities_id'] = $_SESSION['glpiactive_entity'];
      }

      $unicity_fields = explode(',', $unicity->fields['fields']);

      self::dropdownFields($unicity->fields['itemtype'],
                           ['values' => $unicity_fields,
                                 'name'   => '_fields']);
      echo "</span>";
   }


   /** Dropdown fields for a specific itemtype
    *
    * @since 0.84
    *
    * @param string $itemtype
    * @param array  $options
   **/
   static function dropdownFields($itemtype, $options = []) {
      global $DB;

      $p = [
         'name'    => 'fields',
         'display' => true,
         'values'  => [],
      ];

      if (is_array($options) && count($options)) {
         foreach ($options as $key => $val) {
            $p[$key] = $val;
         }
      }

      //Search option for this type
      if ($target = getItemForItemtype($itemtype)) {
         //Do not check unicity on fields in DB with theses types
         $blacklisted_types = ['longtext', 'text'];

         //Construct list
         $values = [];
         foreach ($DB->listFields(getTableForItemType($itemtype)) as $field) {
            $searchOption = $target->getSearchOptionByField('field', $field['Field']);
            if (!empty($searchOption)
                && !in_array($field['Type'], $blacklisted_types)
                && !in_array($field['Field'], $target->getUnallowedFieldsForUnicity())) {
               $values[$field['Field']] = $searchOption['name'];
            }
         }
         $p['multiple'] = 1;
         $p['size']     = 15;

         return Dropdown::showFromArray($p['name'], $values, $p);
      }
      return false;
   }


   function rawSearchOptions() {
      $tab = [];

      $tab[] = [
         'id'                 => 'common',
         'name'               => self::getTypeName()
      ];

      $tab[] = [
         'id'                 => '1',
         'table'              => $this->getTable(),
         'field'              => 'name',
         'name'               => __('Name'),
         'datatype'           => 'itemlink',
         'massiveaction'      => false
      ];

      $tab[] = [
         'id'                 => '2',
         'table'              => $this->getTable(),
         'field'              => 'id',
         'name'               => __('ID'),
         'datatype'           => 'number',
         'massiveaction'      => false
      ];

      $tab[] = [
         'id'                 => '3',
         'table'              => $this->getTable(),
         'field'              => 'fields',
         'name'               => __('Unique fields'),
         'massiveaction'      => false,
         'datatype'           => 'specific',
         'additionalfields'   => ['itemtype']
      ];

      $tab[] = [
         'id'                 => '4',
         'table'              => $this->getTable(),
         'field'              => 'itemtype',
         'name'               => _n('Type', 'Types', 1),
         'massiveaction'      => false,
         'datatype'           => 'itemtypename',
         'itemtype_list'      => 'unicity_types'
      ];

      $tab[] = [
         'id'                 => '5',
         'table'              => $this->getTable(),
         'field'              => 'action_refuse',
         'name'               => __('Record into the database denied'),
         'datatype'           => 'bool'
      ];

      $tab[] = [
         'id'                 => '6',
         'table'              => $this->getTable(),
         'field'              => 'action_notify',
         'name'               => __('Send a notification'),
         'datatype'           => 'bool'
      ];

      $tab[] = [
         'id'                 => '86',
         'table'              => $this->getTable(),
         'field'              => 'is_recursive',
         'name'               => __('Child entities'),
         'datatype'           => 'bool'
      ];

      $tab[] = [
         'id'                 => '16',
         'table'              => $this->getTable(),
         'field'              => 'comment',
         'name'               => __('Comments'),
         'datatype'           => 'text'
      ];

      $tab[] = [
         'id'                 => '30',
         'table'              => $this->getTable(),
         'field'              => 'is_active',
         'name'               => __('Active'),
         'datatype'           => 'bool',
         'massiveaction'      => false
      ];

      $tab[] = [
         'id'                 => '80',
         'table'              => 'glpi_entities',
         'field'              => 'completename',
         'name'               => Entity::getTypeName(1),
         'datatype'           => 'dropdown'
      ];

      return $tab;
   }


   /**
    * @since 0.84
    *
    * @param $field
    * @param $values
    * @param $options   array
   **/
   static function getSpecificValueToDisplay($field, $values, array $options = []) {

      if (!is_array($values)) {
         $values = [$field => $values];
      }
      switch ($field) {
         case 'fields':
            if (isset($values['itemtype'])
                && !empty($values['itemtype'])) {
               if ($target = getItemForItemtype($values['itemtype'])) {
                  $searchOption = $target->getSearchOptionByField('field', $values[$field]);
                  $fields       = explode(',', $values[$field]);
                  $message      = [];
                  foreach ($fields as $field) {
                     $searchOption = $target->getSearchOptionByField('field', $field);

                     if (isset($searchOption['name'])) {
                        $message[] = $searchOption['name'];
                     }
                  }
                  return implode(', ', $message);
               }
            }
            break;
      }
      return parent::getSpecificValueToDisplay($field, $values, $options);
   }


   /**
    * @since 0.84
    *
    * @param $field
    * @param $name               (default '')
    * @param $values             (default '')
    * @param $options      array
   **/
   static function getSpecificValueToSelect($field, $name = '', $values = '', array $options = []) {
      if (!is_array($values)) {
         $values = [$field => $values];
      }
      $options['display'] = false;
      switch ($field) {
         case 'fields' :
            if (isset($values['itemtype'])
                && !empty($values['itemtype'])) {
               $options['values'] = explode(',', $values[$field]);
               $options['name']   = $name;
               return self::dropdownFields($values['itemtype'], $options);
            }
            break;
      }
      return parent::getSpecificValueToSelect($field, $name, $values, $options);
   }


   /**
    * Perform checks to be sure that an itemtype and at least a field are selected
    *
    * @param array $input  the values to insert in DB
    *
    * @return array the input values to insert, but modified
   **/
   static function checkBeforeInsert($input) {

      if (!$input['itemtype']
          || empty($input['_fields'])) {
         Session::addMessageAfterRedirect(__("It's mandatory to select a type and at least one field"),
                                          true, ERROR);
         $input = [];

      } else {
         $input['fields'] = implode(',', $input['_fields']);
         unset($input['_fields']);
      }
      return $input;
   }


   function prepareInputForAdd($input) {
      return self::checkBeforeInsert($input);
   }


   function prepareInputForUpdate($input) {

      $input['fields'] = implode(',', $input['_fields']);
      unset($input['_fields']);

      return $input;
   }


   /**
    * Delete all criterias for an itemtype
    *
    * @param itemtype
    *
    * @return void
   **/
   static function deleteForItemtype($itemtype) {
      global $DB;

      $DB->delete(
         self::getTable(), [
            'itemtype'  => ['LIKE', "%Plugin$itemtype%"]
         ]
      );
   }


   /**
    * List doubles
    *
    * @param FieldUnicity $unicity
   **/
   static function showDoubles(FieldUnicity $unicity) {
      global $DB;

      $fields       = [];
      $where_fields = [];
      if (!$item = getItemForItemtype($unicity->fields['itemtype'])) {
         return;
      }
      foreach (explode(',', $unicity->fields['fields']) as $field) {
         $fields[]       = $field;
         $where_fields[] = $field;
      }

      if (!empty($fields)) {
         $colspan = count($fields) + 1;
         echo "<table class='tab_cadre_fixe'>";
         echo "<tr class='tab_bg_2'><th colspan='".$colspan."'>".__('Duplicates')."</th></tr>";

         $entities = [$unicity->fields['entities_id']];
         if ($unicity->fields['is_recursive']) {
            $entities = getSonsOf('glpi_entities', $unicity->fields['entities_id']);
         }

         $where = [];
         if ($item->maybeTemplate()) {
            $where[$item->getTable() . '.is_template'] = 0;
         }

         foreach ($where_fields as $where_field) {
            if (getTableNameForForeignKeyField($where_field)) {
               $where = $where + [
                  'NOT'          => [$where_field => null],
                  $where_field   => ['<>', 0]
               ];
            } else {
               $where = $where + [
                  'NOT'          => [$where_field => null],
                  $where_field   => ['<>', '']
               ];
            }
         }

         $iterator = $DB->request([
            'SELECT'    => $fields,
            'COUNT'     => 'cpt',
            'FROM'      => $item->getTable(),
            'WHERE'     => [
               $item->getTable() . '.entities_id'  => $entities
            ] + $where,
            'GROUPBY'   => $fields,
            'ORDERBY'   => 'cpt DESC'
         ]);
         $results = [];
         while ($data = $iterator->next()) {
            if ($data['cpt'] > 1) {
               $results[] = $data;

            }
         }

         if (empty($results)) {
            echo "<tr class='tab_bg_2'>";
            echo "<td class='center' colspan='$colspan'>".__('No item to display')."</td></tr>";
         } else {
            echo "<tr class='tab_bg_2'>";
            foreach ($fields as $field) {
               $searchOption = $item->getSearchOptionByField('field', $field);
               echo "<th>".$searchOption["name"]."</th>";
            }
            echo "<th>"._x('quantity', 'Number')."</th></tr>";

            foreach ($results as $result) {
               echo "<tr class='tab_bg_2'>";
               foreach ($fields as $field) {
                  $table = getTableNameForForeignKeyField($field);
                  if ($table != '') {
                     echo "<td>".Dropdown::getDropdownName($table, $result[$field])."</td>";
                  } else {
                     echo "<td>".$result[$field]."</td>";
                  }
               }
               echo "<td class='numeric'>".$result['cpt']."</td></tr>";
            }
         }

      } else {
         echo "<tr class='tab_bg_2'>";
         echo "<td class='center' colspan='$colspan'>".__('No item to display')."</td></tr>";
      }
      echo "</table>";
   }


   /**
    * Display debug information for current object
   **/
   function showDebug() {

      $params = ['action_type' => true,
                      'action_user' => getUserName(Session::getLoginUserID()),
                      'entities_id' => $_SESSION['glpiactive_entity'],
                      'itemtype'    => get_class($this),
                      'date'        => $_SESSION['glpi_currenttime'],
                      'refuse'      => true,
                      'label'       => ['name' => 'test'],
                      'field'       => ['action_refuse' => true],
                      'double'      => []];

      NotificationEvent::debugEvent($this, $params);
   }


   static function getIcon() {
      return "fas fa-fingerprint";
   }
}
