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

use Glpi\Event;

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

//!  Consumable Class
/**
  This class is used to manage the consumables.
  @see ConsumableItem
  @author Julien Dombre
**/
class Consumable extends CommonDBChild {
   use Glpi\Features\Clonable;

   // From CommonDBTM
   static protected $forward_entity_to = ['Infocom'];
   public $no_form_page                = true;

   static $rightname                   = 'consumable';

   // From CommonDBChild
   static public $itemtype             = 'ConsumableItem';
   static public $items_id             = 'consumableitems_id';

   public function getCloneRelations() :array {
      return [
         Infocom::class
      ];
   }

   function getForbiddenStandardMassiveAction() {

      $forbidden   = parent::getForbiddenStandardMassiveAction();
      $forbidden[] = 'update';
      return $forbidden;
   }


   static function getNameField() {
      return 'id';
   }


   static function getTypeName($nb = 0) {
      return _n('Consumable', 'Consumables', $nb);
   }


   function cleanDBonPurge() {

      $this->deleteChildrenAndRelationsFromDb(
         [
            Infocom::class,
         ]
      );
   }


   function prepareInputForAdd($input) {

      $item = new ConsumableItem();
      if ($item->getFromDB($input["consumableitems_id"])) {
         return ["consumableitems_id" => $item->fields["id"],
                      "entities_id"        => $item->getEntityID(),
                      "date_in"            => date("Y-m-d")];
      }
      return [];
   }


   /**
    * send back to stock
    *
    * @param array $input Array of item fields. Only the ID field is used here.
    * @param int $history Not used
    *
    * @return bool
    */
   function backToStock(array $input, $history = 1) {
      global $DB;

      $result = $DB->update(
         $this->getTable(), [
            'date_out' => 'NULL'
         ], [
            'id' => $input['id']
         ]
      );
      if ($result) {
         return true;
      }
      return false;
   }


   function getPreAdditionalInfosForName() {

      $ci = new ConsumableItem();
      if ($ci->getFromDB($this->fields['consumableitems_id'])) {
         return $ci->getName();
      }
      return '';
   }


   /**
    * UnLink a consumable linked to a printer
    *
    * UnLink the consumable identified by $ID
    *
    * @param integer $ID       consumable identifier
    * @param string  $itemtype itemtype of who we give the consumable
    * @param integer $items_id ID of the item giving the consumable
    *
    * @return boolean
   **/
   function out($ID, $itemtype = '', $items_id = 0) {
      global $DB;

      if (!empty($itemtype)
          && ($items_id > 0)) {

         $result = $DB->update(
            $this->getTable(), [
               'date_out'  => date('Y-m-d'),
               'itemtype'  => $itemtype,
               'items_id'  => $items_id
            ], [
               'id' => $ID
            ]
         );
         if ($result) {
            return true;
         }
      }
      return false;
   }


   static function showMassiveActionsSubForm(MassiveAction $ma) {
      global $CFG_GLPI;

      $input = $ma->getInput();
      switch ($ma->getAction()) {
         case 'give' :
            if (isset($input["entities_id"])) {
               Dropdown::showSelectItemFromItemtypes(['itemtype_name'
                                                              => 'give_itemtype',
                                                           'items_id_name'
                                                              => 'give_items_id',
                                                           'entity_restrict'
                                                              => $input["entities_id"],
                                                           'itemtypes'
                                                              => $CFG_GLPI["consumables_types"]]);
               echo "<br><br>".Html::submit(_x('button', 'Give'),
                                            ['name' => 'massiveaction']);
               return true;
            }
      }
      return parent::showMassiveActionsSubForm($ma);
   }


   static function processMassiveActionsForOneItemtype(MassiveAction $ma, CommonDBTM $item,
                                                       array $ids) {

      switch ($ma->getAction()) {
         case 'backtostock' :
            foreach ($ids as $id) {
               if ($item->can($id, UPDATE)) {
                  if ($item->backToStock(["id" => $id])) {
                     $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
                  } else {
                     $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
                     $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION));
                  }
               } else {
                  $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_NORIGHT);
                  $ma->addMessage($item->getErrorMessage(ERROR_RIGHT));
               }
            }
            return;
         case 'give' :
            $input = $ma->getInput();
            if (($input["give_items_id"] > 0)
                && !empty($input['give_itemtype'])) {
               foreach ($ids as $key) {
                  if ($item->can($key, UPDATE)) {
                     if ($item->out($key, $input['give_itemtype'], $input["give_items_id"])) {
                        $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));
                  }
               }
               Event::log($item->fields['consumableitems_id'], "consumableitems", 5, "inventory",
                          //TRANS: %s is the user login
                          sprintf(__('%s gives a consumable'), $_SESSION["glpiname"]));
            } else {
               $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_KO);
            }
            return;
      }
      parent::processMassiveActionsForOneItemtype($ma, $item, $ids);
   }


   /**
    * count how many consumable for the consumable item $tID
    *
    * @param integer $tID consumable item identifier.
    *
    * @return integer number of consumable counted.
    **/
   static function getTotalNumber($tID) {
      global $DB;

      $result = $DB->request([
         'COUNT'  => 'cpt',
         'FROM'   => 'glpi_consumables',
         'WHERE'  => ['consumableitems_id' => $tID]
      ])->next();
      return (int)$result['cpt'];
   }


   /**
    * count how many old consumable for the consumable item $tID
    *
    * @param integer $tID consumable item identifier.
    *
    * @return integer number of old consumable counted.
   **/
   static function getOldNumber($tID) {
      global $DB;

      $result = $DB->request([
         'COUNT'  => 'cpt',
         'FROM'   => 'glpi_consumables',
         'WHERE'  => [
            'consumableitems_id' => $tID,
            'NOT'                => ['date_out' => null]
         ]
      ])->next();
      return (int)$result['cpt'];
   }


   /**
    * count how many consumable unused for the consumable item $tID
    *
    * @param integer $tID consumable item identifier.
    *
    * @return integer number of consumable unused counted.
   **/
   static function getUnusedNumber($tID) {
      global $DB;

      $result = $DB->request([
         'COUNT'  => 'cpt',
         'FROM'   => 'glpi_consumables',
         'WHERE'  => [
            'consumableitems_id' => $tID,
            'date_out'           => null
         ]
      ])->next();
      return(int) $result['cpt'];
   }


   /**
    * Get the consumable count HTML array for a defined consumable type
    *
    * @param integer $tID             consumable item identifier.
    * @param integer $alarm_threshold threshold alarm value.
    * @param boolean $nohtml          Return value without HTML tags.
    *
    * @return string to display
   **/
   static function getCount($tID, $alarm_threshold, $nohtml = 0) {

      // Get total
      $total = self::getTotalNumber($tID);

      if ($total != 0) {
         $unused = self::getUnusedNumber($tID);
         $old    = self::getOldNumber($tID);

         $highlight = "";
         if ($unused <= $alarm_threshold) {
            $highlight = "class='tab_bg_1_2'";
         }
         //TRANS: For consumable. %1$d is total number, %2$d is unused number, %3$d is old number
         $tmptxt = sprintf(__('Total: %1$d, New: %2$d, Used: %3$d'), $total, $unused, $old);
         if ($nohtml) {
            $out = $tmptxt;
         } else {
            $out = "<div $highlight>".$tmptxt."</div>";
         }
      } else {
         if ($nohtml) {
            $out = __('No consumable');
         } else {
            $out = "<div class='tab_bg_1_2'><i>".__('No consumable')."</i></div>";
         }
      }
      return $out;
   }


   /**
    * Check if a Consumable is New (not used, in stock)
    *
    * @param integer $cID consumable ID.
    *
    * @return boolean
   **/
   static function isNew($cID) {
      global $DB;

      $result = $DB->request([
         'COUNT'  => 'cpt',
         'FROM'   => 'glpi_consumables',
         'WHERE'  => [
            'id'        => $cID,
            'date_out'  => null
         ]
      ])->next();
      return $result['cpt'] == 1;
   }


   /**
    * Check if a consumable is Old (used, not in stock)
    *
    * @param integer $cID consumable ID.
    *
    * @return boolean
   **/
   static function isOld($cID) {
      global $DB;

      $result = $DB->request([
         'COUNT'  => 'cpt',
         'FROM'   => 'glpi_consumables',
         'WHERE'  => [
            'id'     => $cID,
            'NOT'   => ['date_out' => null]
         ]
      ])->next();
      return $result['cpt'] == 1;
   }


   /**
    * Get the localized string for the status of a consumable
    *
    * @param integer $cID consumable ID.
    *
    * @return string
   **/
   static function getStatus($cID) {

      if (self::isNew($cID)) {
         return _nx('consumable', 'New', 'New', 1);

      } else if (self::isOld($cID)) {
         return _nx('consumable', 'Used', 'Used', 1);
      }
   }


   /**
    * Print out a link to add directly a new consumable from a consumable item.
    *
    * @param ConsumableItem $consitem
    *
    * @return void
   **/
   static function showAddForm(ConsumableItem $consitem) {

      $ID = $consitem->getField('id');

      if (!$consitem->can($ID, UPDATE)) {
         return;
      }

      if ($ID > 0) {
         echo "<div class='firstbloc'>";
         echo "<form method='post' action=\"".static::getFormURL()."\">";
         echo "<table class='tab_cadre_fixe'>";
         echo "<tr><td class='tab_bg_2 center'>";
         echo "<input type='hidden' name='consumableitems_id' value='$ID'>\n";
         Dropdown::showNumber('to_add', ['value' => 1,
                                              'min'   => 1,
                                              'max'   => 100]);
         echo " <input type='submit' name='add_several' value=\""._sx('button', 'Add consumables')."\"
                class='submit'>";
         echo "</td></tr>";
         echo "</table>";
         Html::closeForm();
         echo "</div>";
      }
   }


   /**
    * Print out the consumables of a defined type
    *
    * @param ConsumableItem $consitem
    * @param boolean        $show_old show old consumables or not. (default 0)
    *
    * @return void
   **/
   static function showForConsumableItem(ConsumableItem $consitem, $show_old = 0) {
      global $DB;

      $tID = $consitem->getField('id');
      if (!$consitem->can($tID, READ)) {
         return;
      }

      if (isset($_GET["start"])) {
         $start = $_GET["start"];
      } else {
         $start = 0;
      }

      $canedit = $consitem->can($tID, UPDATE);
      $rand = mt_rand();
      $where = ['consumableitems_id' => $tID];
      $order = ['date_in', 'id'];
      if (!$show_old) { // NEW
         $where += ['date_out' => 'NULL'];
      } else { //OLD
         $where += ['NOT'   => ['date_out' => 'NULL']];
         $order = ['date_out DESC'] + $order;
      }

      $number = countElementsInTable("glpi_consumables", $where);

      $iterator = $DB->request([
         'FROM'   => self::getTable(),
         'WHERE'  => $where,
         'ORDER'  => $order,
         'START'  => (int)$start,
         'LIMIT'  => (int)$_SESSION['glpilist_limit']
      ]);

      echo "<div class='spaced'>";

      // Display the pager
      Html::printAjaxPager(Consumable::getTypeName(Session::getPluralNumber()), $start, $number);

      if ($canedit && $number) {
         Html::openMassiveActionsForm('mass'.__CLASS__.$rand);
         $actions = [];
         if ($consitem->can($tID, PURGE)) {
            $actions['delete'] = _x('button', 'Delete permanently');
         }
         $actions['Infocom'.MassiveAction::CLASS_ACTION_SEPARATOR.'activate']
            = __('Enable the financial and administrative information');

         if ($show_old) {
            $actions['Consumable'.MassiveAction::CLASS_ACTION_SEPARATOR.'backtostock']
                     = __('Back to stock');
         } else {
            $actions[__CLASS__.MassiveAction::CLASS_ACTION_SEPARATOR.'give'] = _x('button', 'Give');
         }
         $entparam = ['entities_id' => $consitem->getEntityID()];
         if ($consitem->isRecursive()) {
            $entparam = ['entities_id' => getSonsOf('glpi_entities', $consitem->getEntityID())];
         }
         $massiveactionparams = ['num_displayed'    => min($_SESSION['glpilist_limit'], $number),
                           'specific_actions' => $actions,
                           'container'        => 'mass'.__CLASS__.$rand,
                           'extraparams'      => $entparam];
         Html::showMassiveActions($massiveactionparams);
         echo "<input type='hidden' name='consumableitems_id' value='$tID'>\n";
      }

      echo "<table class='tab_cadre_fixehov'>";
      if (!$show_old) {
         echo "<tr><th colspan=".($canedit?'5':'4').">";
         echo self::getCount($tID, -1);
         echo "</th></tr>";
      } else { // Old
         echo "<tr><th colspan='".($canedit?'7':'6')."'>".__('Used consumables')."</th></tr>";
      }

      if ($number) {
         $header_begin  = "<tr>";
         $header_top    = '';
         $header_bottom = '';
         $header_end    = '';
         if ($canedit) {
            $header_begin  .= "<th width='10'>";
            $header_top    .= Html::getCheckAllAsCheckbox('mass'.__CLASS__.$rand);
            $header_bottom .= Html::getCheckAllAsCheckbox('mass'.__CLASS__.$rand);
            $header_end    .= "</th>";
         }
         $header_end .= "<th>".__('ID')."</th>";
         $header_end .= "<th>"._x('item', 'State')."</th>";
         $header_end .= "<th>".__('Add date')."</th>";
         if ($show_old) {
            $header_end .= "<th>".__('Use date')."</th>";
            $header_end .= "<th>".__('Given to')."</th>";
         }
         $header_end .= "<th width='200px'>".__('Financial and administrative information')."</th>";
         $header_end .= "</tr>";
         echo $header_begin.$header_top.$header_end;

         while ($data = $iterator->next()) {
            $date_in  = Html::convDate($data["date_in"]);
            $date_out = Html::convDate($data["date_out"]);

            echo "<tr class='tab_bg_1'>";
            if ($canedit) {
               echo "<td width='10'>";
               Html::showMassiveActionCheckBox(__CLASS__, $data["id"]);
               echo "</td>";
            }
            echo "<td class='center'>".$data["id"]."</td>";
            echo "<td class='center'>".self::getStatus($data["id"])."</td>";
            echo "<td class='center'>".$date_in."</td>";
            if ($show_old) {
               echo "<td class='center'>".$date_out."</td>";
               echo "<td class='center'>";
               if ($item = getItemForItemtype($data['itemtype'])) {
                  if ($item->getFromDB($data['items_id'])) {
                     echo $item->getLink();
                  }
               }
               echo "</td>";
            }
            echo "<td class='center'>";
            Infocom::showDisplayLink('Consumable', $data["id"]);
            echo "</td>";
            echo "</tr>";
         }
         echo $header_begin.$header_bottom.$header_end;
      }
      echo "</table>";
      if ($canedit && $number) {
         $massiveactionparams['ontop'] = false;
         Html::showMassiveActions($massiveactionparams);
         Html::closeForm();
      }

      echo "</div>";
   }


   /**
    * Show the usage summary of consumables by user
    *
    * @return void
    **/
   static function showSummary() {
      global $DB;

      if (!Consumable::canView()) {
         return;
      }

      $iterator = $DB->request([
         'SELECT' => [
            'COUNT'  => ['* AS count'],
            'consumableitems_id',
            'itemtype',
            'items_id'
         ],
         'FROM'   => 'glpi_consumables',
         'WHERE'  => [
            'NOT'                => ['date_out' => null],
            'consumableitems_id' => new \QuerySubQuery([
               'SELECT' => 'id',
               'FROM'   => 'glpi_consumableitems',
               'WHERE'  => getEntitiesRestrictCriteria('glpi_consumableitems')
            ])
         ],
         'GROUP'  => ['itemtype', 'items_id', 'consumableitems_id']
      ]);
      $used = [];

      while ($data = $iterator->next()) {
         $used[$data['itemtype'].'####'.$data['items_id']][$data["consumableitems_id"]]
            = $data["count"];
      }

      $iterator = $DB->request([
         'SELECT' => [
            'COUNT'  => '* AS count',
            'consumableitems_id',
         ],
         'FROM'   => 'glpi_consumables',
         'WHERE'  => [
            'date_out'           => null,
            'consumableitems_id' => new \QuerySubQuery([
               'SELECT' => 'id',
               'FROM'   => 'glpi_consumableitems',
               'WHERE'  => getEntitiesRestrictCriteria('glpi_consumableitems')
            ])
         ],
         'GROUP'  => ['consumableitems_id']
      ]);
      $new = [];

      while ($data = $iterator->next()) {
         $new[$data["consumableitems_id"]] = $data["count"];
      }

      $iterator = $DB->request([
         'FROM'   => 'glpi_consumableitems',
         'WHERE'  => getEntitiesRestrictCriteria('glpi_consumableitems')
      ]);
      $types = [];

      while ($data = $iterator->next()) {
         $types[$data["id"]] = $data["name"];
      }

      asort($types);
      $total = [];
      if (count($types) > 0) {
         // Produce headline
         echo "<div class='center'><table class='tab_cadrehov'><tr>";

         // Type
         echo "<th>".__('Give to')."</th>";

         foreach ($types as $key => $type) {
            echo "<th>$type</th>";
            $total[$key] = 0;
         }
         echo "<th>".__('Total')."</th>";
         echo "</tr>";

         // new
         echo "<tr class='tab_bg_2'><td class='b'>".__('In stock')."</td>";
         $tot = 0;
         foreach ($types as $id_type => $type) {
            if (!isset($new[$id_type])) {
               $new[$id_type] = 0;
            }
            echo "<td class='center'>".$new[$id_type]."</td>";
            $total[$id_type] += $new[$id_type];
            $tot             += $new[$id_type];
         }
         echo "<td class='numeric'>".$tot."</td>";
         echo "</tr>";

         foreach ($used as $itemtype_items_id => $val) {
            echo "<tr class='tab_bg_2'><td>";
            list($itemtype,$items_id) = explode('####', $itemtype_items_id);
            $item = new $itemtype();
            if ($item->getFromDB($items_id)) {
               //TRANS: %1$s is a type name - %2$s is a name
               printf(__('%1$s - %2$s'), $item->getTypeName(1), $item->getNameID());
            }
            echo "</td>";
            $tot = 0;
            foreach ($types as $id_type => $type) {
               if (!isset($val[$id_type])) {
                  $val[$id_type] = 0;
               }
               echo "<td class='center'>".$val[$id_type]."</td>";
               $total[$id_type] += $val[$id_type];
               $tot             += $val[$id_type];
            }
            echo "<td class='numeric'>".$tot."</td>";
            echo "</tr>";
         }
         echo "<tr class='tab_bg_1'><td class='b'>".__('Total')."</td>";
         $tot = 0;
         foreach ($types as $id_type => $type) {
            $tot += $total[$id_type];
            echo "<td class='numeric'>".$total[$id_type]."</td>";
         }
         echo "<td class='numeric'>".$tot."</td>";
         echo "</tr>";
         echo "</table></div>";

      } else {
         echo "<div class='center b'>".__('No consumable found')."</div>";
      }
   }


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

      if (!$withtemplate && Consumable::canView()) {
         $nb = 0;
         switch ($item->getType()) {
            case 'ConsumableItem' :
               if ($_SESSION['glpishow_count_on_tabs']) {
                  $nb =  self::countForConsumableItem($item);
               }
               return self::createTabEntry(self::getTypeName(Session::getPluralNumber()), $nb);
         }
      }
      return '';
   }


   /**
    * @param ConsumableItem $item
    *
    * @return integer
   **/
   static function countForConsumableItem(ConsumableItem $item) {

      return countElementsInTable(['glpi_consumables'], ['glpi_consumables.consumableitems_id' => $item->getField('id')]);
   }


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

      switch ($item->getType()) {
         case 'ConsumableItem' :
            self::showAddForm($item);
            self::showForConsumableItem($item);
            self::showForConsumableItem($item, 1);
            return true;
      }
   }

   function getRights($interface = 'central') {
      $ci = new ConsumableItem();
      return $ci->getRights($interface);
   }


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