api.class.php 99 KB
Newer Older
Alexandre Delaunay's avatar
Alexandre Delaunay committed
1
<?php
2

3
4
5
/**
 * ---------------------------------------------------------------------
 * GLPI - Gestionnaire Libre de Parc Informatique
6
 * Copyright (C) 2015-2021 Teclib' and contributors.
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 *
 * 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/>.
 * ---------------------------------------------------------------------
Alexandre Delaunay's avatar
Alexandre Delaunay committed
32
33
 */

Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
34
/**
Anael Mobilia's avatar
Anael Mobilia committed
35
 * @since 9.1
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
36
 */
Alexandre Delaunay's avatar
Alexandre Delaunay committed
37

38
39
40
41
42
43
namespace Glpi\Api;

use APIClient;
use Auth;
use Change;
use CommonDevice;
44
use CommonITILObject;
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
use Config;
use Contract;
use Document;
use Dropdown;
use Html;
use Infocom;
use Item_Devices;
use Log;
use Michelf\MarkdownExtra;
use NetworkEquipment;
use NetworkPort;
use Notepad;
use Problem;
use QueryExpression;
use SavedSearch;
use Search;
use Session;
use Software;
use Ticket;
use Toolbox;
use User;
66

67
abstract class API {
Alexandre Delaunay's avatar
Alexandre Delaunay committed
68
69
70
71
72

   // permit writing to $_SESSION
   protected $session_write = false;

   static $api_url = "";
73
   static $content_type = "application/json";
Alexandre Delaunay's avatar
Alexandre Delaunay committed
74
75
76
   protected $format;
   protected $iptxt         = "";
   protected $ipnum         = "";
77
   protected $app_tokens    = [];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
78
   protected $apiclients_id = 0;
79
   protected $deprecated_item = null;
Alexandre Delaunay's avatar
Alexandre Delaunay committed
80

Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
81
82
83
84
85
86
   /**
    * First function used on api call
    * Parse sended query/parameters and call the corresponding API::method
    *
    * @return void self::returnResponse called for output
    */
Alexandre Delaunay's avatar
Alexandre Delaunay committed
87
88
   abstract public function call();

Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
89
90
91
92
93
   /**
    * Needed to transform params of called api in $this->parameters attribute
    *
    * @return string endpoint called
    */
Alexandre Delaunay's avatar
Alexandre Delaunay committed
94
95
   abstract protected function parseIncomingParams();

Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
96
97
98
   /**
    * Generic messages
    *
99
100
    * @since 9.1
    *
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
101
    * @param mixed   $response          string message or array of data to send
102
    * @param integer $httpcode          http code (see : https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
103
104
105
106
    * @param array   $additionalheaders headers to send with http response (must be an array(key => value))
    *
    * @return void
    */
107
   abstract protected function returnResponse($response, $httpcode = 200, $additionalheaders = []);
Alexandre Delaunay's avatar
Alexandre Delaunay committed
108

109
110
111
112
113
114
   /**
    * Upload and validate files from request and append to $this->parameters['input']
    *
    * @return void
    */
   abstract protected function manageUploadedFiles();
Alexandre Delaunay's avatar
Alexandre Delaunay committed
115

Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
116
117
118
119
120
121
122
123
   /**
    * Constructor
    *
    * @var array $CFG_GLPI
    * @var DBmysql $DB
    *
    * @return void
    */
124
   public function initApi() {
Thierry Bugier's avatar
Thierry Bugier committed
125
      global $CFG_GLPI;
Alexandre Delaunay's avatar
Alexandre Delaunay committed
126

127
128
129
130
131
132
133
134
135
      // Load GLPI configuration
      include_once (GLPI_ROOT . '/inc/includes.php');
      $variables = get_defined_vars();
      foreach ($variables as $var => $value) {
         if ($var === strtoupper($var)) {
            $GLOBALS[$var] = $value;
         }
      }

Alexandre Delaunay's avatar
Alexandre Delaunay committed
136
137
138
139
140
141
142
      // construct api url
      self::$api_url = trim($CFG_GLPI['url_base_api'], "/");

      // Don't display error in result
      ini_set('display_errors', 'Off');

      // Avoid keeping messages between api calls
btry's avatar
btry committed
143
      $_SESSION["MESSAGE_AFTER_REDIRECT"] = [];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
144
145
146
147
148
149
150
151

      // check if api is enabled
      if (!$CFG_GLPI['enable_api']) {
         $this->returnError(__("API disabled"), "", "", false);
         exit;
      }

      // retrieve ip of client
btry's avatar
btry committed
152
      $this->iptxt = Toolbox::getRemoteIpAddress();
Alexandre Delaunay's avatar
Alexandre Delaunay committed
153
154
155
156
      $this->ipnum = (strstr($this->iptxt, ':')===false ? ip2long($this->iptxt) : '');

      // check ip access
      $apiclient = new APIClient;
157
      $where_ip = [];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
158
      if ($this->ipnum) {
159
160
161
162
         $where_ip = [
            'OR' => [
               'ipv4_range_start' => null,
               [
Cédric Anne's avatar
Cédric Anne committed
163
164
                  'ipv4_range_start'   => ['<=', $this->ipnum],
                  'ipv4_range_end'     => ['>=', $this->ipnum]
165
166
167
               ]
            ]
         ];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
168
      } else {
169
170
171
172
173
174
         $where_ip = [
            'OR' => [
               ['ipv6'  => null],
               ['ipv6'  => $this->iptxt]
            ]
         ];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
175
      }
176
      $found_clients = $apiclient->find(['is_active' => 1] + $where_ip);
Alexandre Delaunay's avatar
Alexandre Delaunay committed
177
      if (count($found_clients) <= 0) {
Johan Cwiklinski's avatar
Johan Cwiklinski committed
178
         $this->returnError(__("There isn't an active API client matching your IP address in the configuration").
Alexandre Delaunay's avatar
Alexandre Delaunay committed
179
180
181
182
183
184
185
186
                            " (".$this->iptxt.")",
                            "", "ERROR_NOT_ALLOWED_IP", false);
      }
      $app_tokens = array_column($found_clients, 'app_token');
      $apiclients_id = array_column($found_clients, 'id');
      $this->app_tokens = array_combine($apiclients_id, $app_tokens);
   }

Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
187
188
189
190
191
192
193
   /**
    * Set headers according to cross origin ressource sharing
    *
    * @param string $verb Http verb (GET, POST, PUT, DELETE, OPTIONS)
    *
    * @return void
    */
Alexandre Delaunay's avatar
Alexandre Delaunay committed
194
195
196
197
198
   protected function cors($verb = 'GET') {
      if (isset($_SERVER['HTTP_ORIGIN'])) {
         header("Access-Control-Allow-Origin: *");
      }

199
      if ($this->verb == 'GET' || $this->verb == 'OPTIONS') {
Alexandre Delaunay's avatar
Alexandre Delaunay committed
200
         header("Access-Control-Expose-Headers: content-type, content-range, accept-range");
201
      }
Alexandre Delaunay's avatar
Alexandre Delaunay committed
202

203
      if ($this->verb == "OPTIONS") {
Alexandre Delaunay's avatar
Alexandre Delaunay committed
204
205
206
207
208
209
         if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
            header("Access-Control-Allow-Methods: PUT, GET, POST, DELETE, OPTIONS");
         }

         if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
            header("Access-Control-Allow-Headers: ".
210
                   "origin, content-type, accept, session-token, authorization");
Alexandre Delaunay's avatar
Alexandre Delaunay committed
211
212
213
214
215
216
217
218
219
         }
         exit(0);
      }
   }


   /**
    * Init GLPI Session
    *
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
220
    * @param array $params array with theses options :
221
    *    - a couple 'name' & 'password' : 2 parameters to login with user authentication
Alexandre Delaunay's avatar
Alexandre Delaunay committed
222
223
224
225
    *         OR
    *    - an 'user_token' defined in User Configuration
    *
    * @return array with session_token
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
226
    */
227
   protected function initSession($params = []) {
Alexandre Delaunay's avatar
Alexandre Delaunay committed
228
      global $CFG_GLPI;
yllen's avatar
CS    
yllen committed
229

Alexandre Delaunay's avatar
Alexandre Delaunay committed
230
231
232
233
234
235
236
237
238
      $this->checkAppToken();
      $this->logEndpointUsage(__FUNCTION__);

      if ((!isset($params['login'])
           || empty($params['login'])
           || !isset($params['password'])
           || empty($params['password']))
         && (!isset($params['user_token'])
             || empty($params['user_token']))) {
Alexandre Delaunay's avatar
Alexandre Delaunay committed
239
         $this->returnError(__("parameter(s) login, password or user_token are missing"), 400,
Alexandre Delaunay's avatar
Alexandre Delaunay committed
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
                            "ERROR_LOGIN_PARAMETERS_MISSING");
      }

      $auth = new Auth();

      // fill missing params (in case of user_token)
      if (!isset($params['login'])) {
         $params['login'] = '';
      }
      if (!isset($params['password'])) {
         $params['password'] = '';
      }

      $noAuto = true;
      if (isset($params['user_token']) && !empty($params['user_token'])) {
         $_REQUEST['user_token'] = $params['user_token'];
         $noAuto = false;

      } else if (!$CFG_GLPI['enable_api_login_credentials']) {
259
         $this->returnError(__("usage of initSession resource with credentials is disabled"), 400,
260
                            "ERROR_LOGIN_WITH_CREDENTIALS_DISABLED", false);
Alexandre Delaunay's avatar
Alexandre Delaunay committed
261
262
      }

263
264
265
266
      if (!isset($params['auth'])) {
         $params['auth'] = '';
      }

Alexandre Delaunay's avatar
Alexandre Delaunay committed
267
      // login on glpi
268
      if (!$auth->login($params['login'], $params['password'], $noAuto, false, $params['auth'])) {
Alexandre Delaunay's avatar
Alexandre Delaunay committed
269
270
271
         $err = Html::clean($auth->getErr());
         if (isset($params['user_token'])
             && !empty($params['user_token'])) {
272
            return $this->returnError(__("parameter user_token seems invalid"), 401, "ERROR_GLPI_LOGIN_USER_TOKEN", false);
Alexandre Delaunay's avatar
Alexandre Delaunay committed
273
         }
274
         return $this->returnError($err, 401, "ERROR_GLPI_LOGIN", false);
Alexandre Delaunay's avatar
Alexandre Delaunay committed
275
276
277
278
      }

      // stop session and return session key
      session_write_close();
279
280
281
282
283
284
285
286
287
      $data = ['session_token' => $_SESSION['valid_id']];

      // Insert session data if requested
      $get_full_session = $params['get_full_session'] ?? false;
      if ($get_full_session) {
         $data['session'] = $_SESSION;
      }

      return $data;
Alexandre Delaunay's avatar
Alexandre Delaunay committed
288
289
290
291
292
293
294
295
   }


   /**
    * Kill GLPI Session
    * Use 'session_token' param in $this->parameters
    *
    * @return boolean
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
296
    */
Alexandre Delaunay's avatar
Alexandre Delaunay committed
297
   protected function killSession() {
yllen's avatar
CS    
yllen committed
298

Alexandre Delaunay's avatar
Alexandre Delaunay committed
299
300
301
302
303
304
305
306
      $this->initEndpoint(false, __FUNCTION__);
      return Session::destroy();
   }


   /**
    * Retrieve GLPI Session initialised by initSession function
    * Use 'session_token' param in $this->parameters
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
307
308
309
    *
    * @return void
    */
Alexandre Delaunay's avatar
Alexandre Delaunay committed
310
   protected function retrieveSession() {
yllen's avatar
CS    
yllen committed
311

Alexandre Delaunay's avatar
Alexandre Delaunay committed
312
313
314
315
316
      if (isset($this->parameters['session_token'])
          && !empty($this->parameters['session_token'])) {
         $current = session_id();
         $session = trim($this->parameters['session_token']);

317
318
         if (file_exists(GLPI_ROOT . '/inc/downstream.php')) {
            include_once (GLPI_ROOT . '/inc/downstream.php');
Alexandre Delaunay's avatar
Alexandre Delaunay committed
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
         }

         if ($session!=$current && !empty($current)) {
            session_destroy();
         }
         if ($session!=$current && !empty($session)) {
            session_id($session);
         }
      }
   }


   /**
    * Change active entity to the entities_id one.
    *
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
334
    * @param array $params array with theses options :
Alexandre Delaunay's avatar
Alexandre Delaunay committed
335
336
    *   - 'entities_id': (default 'all') ID of the new active entity ("all" = load all possible entities). Optionnal
    *   - 'is_recursive': (default false) Also display sub entities of the active entity.  Optionnal
yllen's avatar
CS    
yllen committed
337
    *
338
    * @return array|bool
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
339
    */
340
   protected function changeActiveEntities($params = []) {
yllen's avatar
CS    
yllen committed
341

Alexandre Delaunay's avatar
Alexandre Delaunay committed
342
343
344
      $this->initEndpoint();

      if (!isset($params['entities_id'])) {
345
346
347
         $entities_id = 'all';
      } else {
         $entities_id = intval($params['entities_id']);
Alexandre Delaunay's avatar
Alexandre Delaunay committed
348
349
350
351
      }

      if (!isset($params['is_recursive'])) {
         $params['is_recursive'] = false;
352
353
354
355
356
357
      } else if (!is_bool($params['is_recursive'])) {
         return $this->returnError();
      }

      if (!Session::changeActiveEntities($entities_id, $params['is_recursive'])) {
         return $this->returnError();
Alexandre Delaunay's avatar
Alexandre Delaunay committed
358
359
      }

360
      return true;
Alexandre Delaunay's avatar
Alexandre Delaunay committed
361
362
363
364
   }


   /**
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
365
    * Return all the possible entity of the current logged user (and for current active profile)
Alexandre Delaunay's avatar
Alexandre Delaunay committed
366
    *
367
368
369
    * @param array $params array with theses options :
    *   - 'is_recursive': (default false) Also display sub entities of the active entity. Optionnal
    *
Alexandre Delaunay's avatar
Alexandre Delaunay committed
370
    * @return array of entities (with id and name)
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
371
    */
372
   protected function getMyEntities($params = []) {
yllen's avatar
CS    
yllen committed
373

Alexandre Delaunay's avatar
Alexandre Delaunay committed
374
375
      $this->initEndpoint();

376
377
378
379
      if (!isset($params['is_recursive'])) {
         $params['is_recursive'] = false;
      }

380
      $myentities = [];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
381
      foreach ($_SESSION['glpiactiveprofile']['entities'] as $entity) {
382
383
384
385
386
387
388
389
390
391
392
393
394
         if ($entity['is_recursive'] == 1 && $params['is_recursive'] == 1) {
            $sons = getSonsOf('glpi_entities', $entity['id']);
            foreach ($sons as $entity_id) {
               if ($entity_id != $entity['id']) {
                  $myentities[] = ['id'   => $entity_id,
                                   'name' => Dropdown::getDropdownName("glpi_entities",
                                                                       $entity_id)];
               }
            }
         }
         $myentities[] = ['id' => $entity['id'],
                          'name' => Dropdown::getDropdownName("glpi_entities",
                                                                   $entity['id'])];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
395
      }
396
      return ['myentities' => $myentities];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
397
398
399
   }


yllen's avatar
CS    
yllen committed
400
401


Alexandre Delaunay's avatar
Alexandre Delaunay committed
402
403
404
405
406
407
408
   /**
    * return active entities of current logged user
    *
    * @return array with 3 keys :
    *  - active_entity : current set entity
    *  - active_entity_recursive : boolean, if we see sons of this entity
    *  - active_entities : array all active entities (active_entity and its sons)
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
409
    */
Alexandre Delaunay's avatar
Alexandre Delaunay committed
410
   protected function getActiveEntities() {
yllen's avatar
CS    
yllen committed
411

Alexandre Delaunay's avatar
Alexandre Delaunay committed
412
413
      $this->initEndpoint();

414
415
416
417
418
      $actives_entities = [];
      foreach (array_values($_SESSION['glpiactiveentities']) as $active_entity) {
         $actives_entities[] = ['id' => $active_entity];
      }

419
      return ["active_entity" => [
420
421
                     "id"                      => $_SESSION['glpiactive_entity'],
                     "active_entity_recursive" => $_SESSION['glpiactive_entity_recursive'],
422
                     "active_entities"         => $actives_entities]];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
423
424
425
426

   }


yllen's avatar
CS    
yllen committed
427
428


Alexandre Delaunay's avatar
Alexandre Delaunay committed
429
430
431
   /**
    * set a profile to active
    *
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
432
    * @param array $params with theses options :
Alexandre Delaunay's avatar
Alexandre Delaunay committed
433
    *    - profiles_id : identifier of profile to set
yllen's avatar
CS    
yllen committed
434
    *
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
435
436
    * @return boolean
    */
437
   protected function changeActiveProfile($params = []) {
yllen's avatar
CS    
yllen committed
438

Alexandre Delaunay's avatar
Alexandre Delaunay committed
439
440
      $this->initEndpoint();

441
442
443
444
      if (!isset($params['profiles_id'])) {
         $this->returnError();
      }

Alexandre Delaunay's avatar
Alexandre Delaunay committed
445
446
447
448
      $profiles_id = intval($params['profiles_id']);
      if (isset($_SESSION['glpiprofiles'][$profiles_id])) {
         return Session::changeProfile($profiles_id);
      }
449
450

      $this->messageNotfoundError();
Alexandre Delaunay's avatar
Alexandre Delaunay committed
451
452
453
   }


yllen's avatar
CS    
yllen committed
454
455


Alexandre Delaunay's avatar
Alexandre Delaunay committed
456
457
458
459
   /**
    * Return all the profiles associated to logged user
    *
    * @return array of profiles (with associated rights)
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
460
    */
Alexandre Delaunay's avatar
Alexandre Delaunay committed
461
462
   protected function getMyProfiles() {

yllen's avatar
CS    
yllen committed
463
      $this->initEndpoint();
464

465
      $myprofiles = [];
Anael Mobilia's avatar
Anael Mobilia committed
466
      foreach ($_SESSION['glpiprofiles'] as $profiles_id => $profile) {
467
         // append if of the profile into values
468
469
470
471
472
473
474
475
         $profile = ['id' => $profiles_id] + $profile;

         // don't keep keys for entities
         $profile['entities'] = array_values($profile['entities']);

         // don't keep keys for profiles
         $myprofiles[] = $profile;
      }
476
      return ['myprofiles' => $myprofiles];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
477
478
479
   }


yllen's avatar
CS    
yllen committed
480
481


Alexandre Delaunay's avatar
Alexandre Delaunay committed
482
   /**
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
483
    * Return the current active profile
Alexandre Delaunay's avatar
Alexandre Delaunay committed
484
485
    *
    * @return integer the profiles_id
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
486
    */
Alexandre Delaunay's avatar
Alexandre Delaunay committed
487
   protected function getActiveProfile() {
yllen's avatar
CS    
yllen committed
488

Alexandre Delaunay's avatar
Alexandre Delaunay committed
489
      $this->initEndpoint();
490
      return ["active_profile" => $_SESSION['glpiactiveprofile']];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
491
492
493
   }


yllen's avatar
CS    
yllen committed
494
495


Alexandre Delaunay's avatar
Alexandre Delaunay committed
496
   /**
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
497
    * Return the current php $_SESSION
Alexandre Delaunay's avatar
Alexandre Delaunay committed
498
499
    *
    * @return array
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
500
    */
Alexandre Delaunay's avatar
Alexandre Delaunay committed
501
   protected function getFullSession() {
yllen's avatar
CS    
yllen committed
502

Alexandre Delaunay's avatar
Alexandre Delaunay committed
503
      $this->initEndpoint();
504
      return ['session' => $_SESSION];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
505
506
   }

yllen's avatar
CS    
yllen committed
507
508


509
   /**
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
510
    * Return the current $CFG_GLPI
511
512
    *
    * @return array
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
513
     */
514
515
516
   protected function getGlpiConfig() {
      $this->initEndpoint();

517
      return ['cfg_glpi' => Config::getSafeConfig()];
518
519
520
   }


Alexandre Delaunay's avatar
Alexandre Delaunay committed
521
522
523
   /**
    * Return the instance fields of itemtype identified by id
    *
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
524
525
526
    * @param string  $itemtype itemtype (class) of object
    * @param integer $id       identifier of object
    * @param array   $params   with theses options :
527
528
529
    *    - 'expand_dropdowns': Show dropdown's names instead of id. default: false. Optionnal
    *    - 'get_hateoas':      Show relation of current item in a links attribute. default: true. Optionnal
    *    - 'get_sha1':         Get a sha1 signature instead of the full answer. default: false. Optionnal
530
    *    - 'with_devices':  Only for [Computer, NetworkEquipment, Peripheral, Phone, Printer], Optionnal.
531
532
533
    *    - 'with_disks':       Only for Computer, retrieve the associated filesystems. Optionnal.
    *    - 'with_softwares':   Only for Computer, retrieve the associated softwares installations. Optionnal.
    *    - 'with_connections': Only for Computer, retrieve the associated direct connections (like peripherals and printers) .Optionnal.
534
    *    - 'with_networkports':Retrieve all network connections and advanced informations. Optionnal.
535
536
537
538
539
540
541
542
    *    - 'with_infocoms':    Retrieve financial and administrative informations. Optionnal.
    *    - 'with_contracts':   Retrieve associated contracts. Optionnal.
    *    - 'with_documents':   Retrieve associated external documents. Optionnal.
    *    - 'with_tickets':     Retrieve associated itil tickets. Optionnal.
    *    - 'with_problems':    Retrieve associated itil problems. Optionnal.
    *    - 'with_changes':     Retrieve associated itil changes. Optionnal.
    *    - 'with_notes':       Retrieve Notes (if exists, not all itemtypes have notes). Optionnal.
    *    - 'with_logs':        Retrieve historical. Optionnal.
543
    *    - 'add_keys_names':   Get friendly names. Optionnal.
Alexandre Delaunay's avatar
Alexandre Delaunay committed
544
    *
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
545
546
    * @return array    fields of found object
    */
547
   protected function getItem($itemtype, $id, $params = []) {
Alexandre Delaunay's avatar
Alexandre Delaunay committed
548
      global $CFG_GLPI, $DB;
yllen's avatar
CS    
yllen committed
549

Alexandre Delaunay's avatar
Alexandre Delaunay committed
550
      $this->initEndpoint();
551
      $itemtype = $this->handleDepreciation($itemtype);
Alexandre Delaunay's avatar
Alexandre Delaunay committed
552
553

      // default params
554
      $default = ['expand_dropdowns'  => false,
Alexandre Delaunay's avatar
Alexandre Delaunay committed
555
                       'get_hateoas'       => true,
556
                       'get_sha1'          => false,
557
                       'with_devices'   => false,
Alexandre Delaunay's avatar
Alexandre Delaunay committed
558
559
560
561
562
563
564
565
566
567
568
                       'with_disks'        => false,
                       'with_softwares'    => false,
                       'with_connections'  => false,
                       'with_networkports' => false,
                       'with_infocoms'     => false,
                       'with_contracts'    => false,
                       'with_documents'    => false,
                       'with_tickets'      => false,
                       'with_problems'     => false,
                       'with_changes'      => false,
                       'with_notes'        => false,
569
570
571
                       'with_logs'         => false,
                       'add_keys_names'    => [],
      ];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
572
573
574
575
576
577
578
579
580
581
      $params = array_merge($default, $params);

      $item = new $itemtype;
      if (!$item->getFromDB($id)) {
         return $this->messageNotfoundError();
      }
      if (!$item->can($id, READ)) {
         return $this->messageRightError();
      }

Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
582
      $fields = $item->fields;
583

584
585
586
      // avoid disclosure of critical fields
      $item::unsetUndisclosedFields($fields);

Alexandre Delaunay's avatar
Alexandre Delaunay committed
587
588
      // retrieve devices
      if (isset($params['with_devices'])
yllen's avatar
CS    
yllen committed
589
590
          && $params['with_devices']
          && in_array($itemtype, Item_Devices::getConcernedItems())) {
591
         $all_devices = [];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
592
         foreach (Item_Devices::getItemAffinities($item->getType()) as $device_type) {
593
            $found_devices = getAllDataFromTable(
594
595
596
597
598
599
600
               $device_type::getTable(), [
                  'items_id'     => $item->getID(),
                  'itemtype'     => $item->getType(),
                  'is_deleted'   => 0
               ],
               true
            );
Alexandre Delaunay's avatar
Alexandre Delaunay committed
601

Cédric Anne's avatar
Cédric Anne committed
602
            foreach ($found_devices as &$device) {
Alexandre Delaunay's avatar
Alexandre Delaunay committed
603
604
605
606
607
608
609
610
611
612
613
614
615
616
               unset($device['items_id']);
               unset($device['itemtype']);
               unset($device['is_deleted']);
            }

            if (!empty($found_devices)) {
               $all_devices[$device_type] = $found_devices;
            }
         }
         $fields['_devices'] = $all_devices;
      }

      // retrieve computer disks
      if (isset($params['with_disks'])
yllen's avatar
CS    
yllen committed
617
          && $params['with_disks']
Johan Cwiklinski's avatar
Johan Cwiklinski committed
618
          && in_array($itemtype, $CFG_GLPI['itemdeviceharddrive_types'])) {
Alexandre Delaunay's avatar
Alexandre Delaunay committed
619
         // build query to retrive filesystems
Johan Cwiklinski's avatar
Johan Cwiklinski committed
620
621
622
623
624
625
         $fs_iterator = $DB->request([
            'SELECT'    => [
               'glpi_filesystems.name AS fsname',
               'glpi_items_disks.*'
            ],
            'FROM'      => 'glpi_items_disks',
cconard96's avatar
cconard96 committed
626
            'LEFT JOIN'  => [
Johan Cwiklinski's avatar
Johan Cwiklinski committed
627
628
629
630
631
632
633
634
635
636
637
638
639
               'glpi_filesystems' => [
                  'ON' => [
                     'glpi_items_disks'   => 'filesystems_id',
                     'glpi_filesystems'   => 'id'
                  ]
               ]
            ],
            'WHERE'     => [
               'items_id'     => $id,
               'itemtype'     => $itemtype,
               'is_deleted'   => 0
            ]
         ]);
640
         $fields['_disks'] = [];
Johan Cwiklinski's avatar
Johan Cwiklinski committed
641
642
643
644
         while ($data = $fs_iterator->next()) {
            unset($data['items_id']);
            unset($data['is_deleted']);
            $fields['_disks'][] = ['name' => $data];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
645
646
647
648
649
         }
      }

      // retrieve computer softwares
      if (isset($params['with_softwares'])
cconard96's avatar
cconard96 committed
650
651
            && $params['with_softwares']
            && in_array($itemtype, $CFG_GLPI['software_types'])) {
652
         $fields['_softwares'] = [];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
653
         if (!Software::canView()) {
654
            $fields['_softwares'] = $this->arrayRightError();
Alexandre Delaunay's avatar
Alexandre Delaunay committed
655
         } else {
Johan Cwiklinski's avatar
Johan Cwiklinski committed
656
657
658
659
660
            $soft_iterator = $DB->request([
               'SELECT'    => [
                  'glpi_softwares.softwarecategories_id',
                  'glpi_softwares.id AS softwares_id',
                  'glpi_softwareversions.id AS softwareversions_id',
cconard96's avatar
cconard96 committed
661
                  'glpi_items_softwareversions.is_dynamic',
Johan Cwiklinski's avatar
Johan Cwiklinski committed
662
663
664
                  'glpi_softwareversions.states_id',
                  'glpi_softwares.is_valid'
               ],
cconard96's avatar
cconard96 committed
665
               'FROM'      => 'glpi_items_softwareversions',
Johan Cwiklinski's avatar
Johan Cwiklinski committed
666
667
668
               'LEFT JOIN' => [
                  'glpi_softwareversions' => [
                     'ON' => [
cconard96's avatar
cconard96 committed
669
670
                        'glpi_items_softwareversions' => 'softwareversions_id',
                        'glpi_softwareversions'       => 'id'
Johan Cwiklinski's avatar
Johan Cwiklinski committed
671
672
673
674
675
676
677
678
679
680
                     ]
                  ],
                  'glpi_softwares'        => [
                     'ON' => [
                        'glpi_softwareversions' => 'softwares_id',
                        'glpi_softwares'        => 'id'
                     ]
                  ]
               ],
               'WHERE'     => [
cconard96's avatar
cconard96 committed
681
682
683
                  'glpi_items_softwareversions.items_id'   => $id,
                  'glpi_items_softwareversions.itemtype'   => $itemtype,
                  'glpi_items_softwareversions.is_deleted' => 0
Johan Cwiklinski's avatar
Johan Cwiklinski committed
684
685
686
687
688
689
690
691
               ],
               'ORDERBY'   => [
                  'glpi_softwares.name',
                  'glpi_softwareversions.name'
               ]
            ]);
            while ($data = $soft_iterator->next()) {
               $fields['_softwares'][] = $data;
Alexandre Delaunay's avatar
Alexandre Delaunay committed
692
693
694
695
696
697
            }
         }
      }

      // retrieve item connections
      if (isset($params['with_connections'])
yllen's avatar
CS    
yllen committed
698
699
          && $params['with_connections']
          && $itemtype == "Computer") {
700
         $fields['_connections'] = [];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
701
702
703
         foreach ($CFG_GLPI["directconnect_types"] as $connect_type) {
            $connect_item = new $connect_type();
            if ($connect_item->canView()) {
Johan Cwiklinski's avatar
Johan Cwiklinski committed
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
               $connect_table = getTableForItemType($connect_type);
               $iterator = $DB->request([
                  'SELECT'    => [
                     'glpi_computers_items.id AS assoc_id',
                     'glpi_computers_items.computers_id AS assoc_computers_id',
                     'glpi_computers_items.itemtype AS assoc_itemtype',
                     'glpi_computers_items.items_id AS assoc_items_id',
                     'glpi_computers_items.is_dynamic AS assoc_is_dynamic',
                     "$connect_table.*"
                  ],
                  'FROM'      => 'glpi_computers_items',
                  'LEFT JOIN' => [
                     $connect_table => [
                        'ON' => [
                           'glpi_computers_items'  => 'items_id',
                           $connect_table          => 'id'
                        ]
                     ]
                  ],
                  'WHERE'     => [
                     'computers_id'                      => $id,
                     'itemtype'                          => $connect_type,
                     'glpi_computers_items.is_deleted'   => 0
                  ]
               ]);
               while ($data = $iterator->next()) {
                  $fields['_connections'][$connect_type][] = $data;
Alexandre Delaunay's avatar
Alexandre Delaunay committed
731
732
733
734
735
736
               }
            }
         }
      }

      // retrieve item networkports
737
738
      if (isset($params['with_networkports']) && $params['with_networkports']) {
         $fields['_networkports'] = $this->getNetworkPorts($id, $itemtype);
Alexandre Delaunay's avatar
Alexandre Delaunay committed
739
740
741
742
      }

      // retrieve item infocoms
      if (isset($params['with_infocoms'])
yllen's avatar
CS    
yllen committed
743
          && $params['with_infocoms']) {
744
         $fields['_infocoms'] = [];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
745
         if (!Infocom::canView()) {
746
            $fields['_infocoms'] = $this->arrayRightError();
Alexandre Delaunay's avatar
Alexandre Delaunay committed
747
748
749
750
751
752
753
754
755
756
         } else {
            $ic = new Infocom();
            if ($ic->getFromDBforDevice($itemtype, $id)) {
               $fields['_infocoms'] = $ic->fields;
            }
         }
      }

      // retrieve item contracts
      if (isset($params['with_contracts'])
yllen's avatar
CS    
yllen committed
757
          && $params['with_contracts']) {
758
         $fields['_contracts'] = [];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
759
         if (!Contract::canView()) {
760
            $fields['_contracts'] = $this->arrayRightError();
Alexandre Delaunay's avatar
Alexandre Delaunay committed
761
         } else {
Johan Cwiklinski's avatar
Johan Cwiklinski committed
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
            $iterator = $DB->request([
               'SELECT'    => ['glpi_contracts_items.*'],
               'FROM'      => 'glpi_contracts_items',
               'LEFT JOIN' => [
                  'glpi_contracts'  => [
                     'ON' => [
                        'glpi_contracts_items'  => 'contracts_id',
                        'glpi_contracts'        => 'id'
                     ]
                  ],
                  'glpi_entities'   => [
                     'ON' => [
                        'glpi_contracts_items'  => 'entities_id',
                        'glpi_entities'         => 'id'
                     ]
                  ]
               ],
               'WHERE'     => [
                  'glpi_contracts_items.items_id'  => $id,
                  'glpi_contracts_items.itemtype'  => $itemtype
               ] + getEntitiesRestrictCriteria('glpi_contracts', '', '', true),
               'ORDERBY'   => 'glpi_contracts.name'
            ]);
            while ($data = $iterator->next()) {
               $fields['_contracts'][] = $data;
Alexandre Delaunay's avatar
Alexandre Delaunay committed
787
788
789
790
            }
         }
      }

791
      // retrieve item documents
Alexandre Delaunay's avatar
Alexandre Delaunay committed
792
      if (isset($params['with_documents'])
yllen's avatar
CS    
yllen committed
793
          && $params['with_documents']) {
794
         $fields['_documents'] = [];
795
         if (!($item instanceof CommonITILObject)
Alexandre Delaunay's avatar
Alexandre Delaunay committed
796
797
798
             && $itemtype != 'KnowbaseItem'
             && $itemtype != 'Reminder'
             && !Document::canView()) {
799
            $fields['_documents'] = $this->arrayRightError();
Alexandre Delaunay's avatar
Alexandre Delaunay committed
800
         } else {
801
802
803
804
805
806
807
808
809
810
            $doc_criteria = [
               'glpi_documents_items.items_id'  => $id,
               'glpi_documents_items.itemtype'  => $itemtype
            ];
            if ($item instanceof CommonITILObject) {
               $doc_criteria = [
                  $item->getAssociatedDocumentsCriteria(),
                  'timeline_position' => ['>', CommonITILObject::NO_TIMELINE], // skip inlined images
               ];
            }
Johan Cwiklinski's avatar
Johan Cwiklinski committed
811
812
813
            $doc_iterator = $DB->request([
               'SELECT'    => [
                  'glpi_documents_items.id AS assocID',
814
                  'glpi_documents_items.date_creation AS assocdate',
Johan Cwiklinski's avatar
Johan Cwiklinski committed
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
                  'glpi_entities.id AS entityID',
                  'glpi_entities.completename AS entity',
                  'glpi_documentcategories.completename AS headings',
                  'glpi_documents.*'
               ],
               'FROM'      => 'glpi_documents_items',
               'LEFT JOIN' => [
                  'glpi_documents'           => [
                     'ON' => [
                        'glpi_documents_items'  => 'documents_id',
                        'glpi_documents'        => 'id'
                     ]
                  ],
                  'glpi_entities'            => [
                     'ON' => [
                        'glpi_documents'  => 'entities_id',
                        'glpi_entities'   => 'id'
                     ]
                  ],
                  'glpi_documentcategories'  => [
                     'ON' => [
                        'glpi_documents'           => 'documentcategories_id',
                        'glpi_documentcategories'  => 'id'
                     ]
                  ]
               ],
841
               'WHERE'     => $doc_criteria,
Johan Cwiklinski's avatar
Johan Cwiklinski committed
842
843
844
            ]);
            while ($data = $doc_iterator->next()) {
               $fields['_documents'][] = $data;
Alexandre Delaunay's avatar
Alexandre Delaunay committed
845
846
847
848
849
850
            }
         }
      }

      // retrieve item tickets
      if (isset($params['with_tickets'])
yllen's avatar
CS    
yllen committed
851
          && $params['with_tickets']) {
852
         $fields['_tickets'] = [];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
853
         if (!Ticket::canView()) {
854
            $fields['_tickets'] = $this->arrayRightError();
Alexandre Delaunay's avatar
Alexandre Delaunay committed
855
         } else {
856
857
858
859
860
861
862
863
            $criteria = Ticket::getCommonCriteria();
            $criteria['WHERE'] = [
               'glpi_items_tickets.items_id' => $id,
               'glpi_items_tickets.itemtype' => $itemtype
            ] + getEntitiesRestrictCriteria(Ticket::getTable());
            $iterator = $DB->request($criteria);
            while ($data = $iterator->next()) {
               $fields['_tickets'][] = $data;
Alexandre Delaunay's avatar
Alexandre Delaunay committed
864
865
866
867
868
869
            }
         }
      }

      // retrieve item problems
      if (isset($params['with_problems'])
yllen's avatar
CS    
yllen committed
870
          && $params['with_problems']) {
871
         $fields['_problems'] = [];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
872
         if (!Problem::canView()) {
873
            $fields['_problems'] = $this->arrayRightError();
Alexandre Delaunay's avatar
Alexandre Delaunay committed
874
         } else {
875
876
877
878
879
880
881
882
            $criteria = Problem::getCommonCriteria();
            $criteria['WHERE'] = [
               'glpi_items_problems.items_id' => $id,
               'glpi_items_problems.itemtype' => $itemtype
            ] + getEntitiesRestrictCriteria(Problem::getTable());
            $iterator = $DB->request($criteria);
            while ($data = $iterator->next()) {
               $fields['_problems'][] = $data;
Alexandre Delaunay's avatar
Alexandre Delaunay committed
883
884
885
886
887
888
            }
         }
      }

      // retrieve item changes
      if (isset($params['with_changes'])
yllen's avatar
CS    
yllen committed
889
          && $params['with_changes']) {
890
         $fields['_changes'] = [];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
891
         if (!Change::canView()) {
892
            $fields['_changes'] = $this->arrayRightError();
Alexandre Delaunay's avatar
Alexandre Delaunay committed
893
         } else {
894
895
896
897
898
899
900
901
            $criteria = Change::getCommonCriteria();
            $criteria['WHERE'] = [
               'glpi_changes_items.items_id' => $id,
               'glpi_changes_items.itemtype' => $itemtype
            ] + getEntitiesRestrictCriteria(Change::getTable());
            $iterator = $DB->request($criteria);
            while ($data = $iterator->next()) {
               $fields['_changes'][] = $data;
Alexandre Delaunay's avatar
Alexandre Delaunay committed
902
903
904
905
906
907
            }
         }
      }

      // retrieve item notes
      if (isset($params['with_notes'])
yllen's avatar
CS    
yllen committed
908
          && $params['with_notes']) {
909
         $fields['_notes'] = [];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
910
         if (!Session::haveRight($itemtype::$rightname, READNOTE)) {
911
            $fields['_notes'] = $this->arrayRightError();
Alexandre Delaunay's avatar
Alexandre Delaunay committed
912
         } else {
913
            $fields['_notes'] = Notepad::getAllForItem($item);
Alexandre Delaunay's avatar
Alexandre Delaunay committed
914
915
916
917
918
         }
      }

      // retrieve item logs
      if (isset($params['with_logs'])
yllen's avatar
CS    
yllen committed
919
          && $params['with_logs']) {
920
         $fields['_logs'] = [];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
921
         if (!Session::haveRight($itemtype::$rightname, READNOTE)) {
922
            $fields['_logs'] = $this->arrayRightError();
Alexandre Delaunay's avatar
Alexandre Delaunay committed
923
         } else {
924
            $fields['_logs'] = getAllDataFromTable(
925
926
927
928
929
               "glpi_logs", [
                  'items_id'  => $item->getID(),
                  'itemtype'  => $item->getType()
               ]
            );
Alexandre Delaunay's avatar
Alexandre Delaunay committed
930
931
932
933
934
935
936
937
938
         }
      }

      // expand dropdown (retrieve name of dropdowns) and get hateoas from foreign keys
      $fields = self::parseDropdowns($fields, $params);

      // get hateoas from children
      if ($params['get_hateoas']) {
         $hclasses = self::getHatoasClasses($itemtype);
Anael Mobilia's avatar
Anael Mobilia committed
939
         foreach ($hclasses as $hclass) {
940
941
            $fields['links'][] = ['rel'  => $hclass,
                                       'href' => self::$api_url."/$itemtype/".$item->getID()."/$hclass/"];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
942
943
944
         }
      }

945
946
      // get sha1 footprint if needed
      if ($params['get_sha1']) {
947
948
949
         $fields = sha1(json_encode($fields, JSON_UNESCAPED_UNICODE
                                             | JSON_UNESCAPED_SLASHES
                                             | JSON_NUMERIC_CHECK));
950
951
      }

952
      if (count($params['add_keys_names']) > 0) {
953
         $fields["_keys_names"] = $this->getFriendlyNames(
954
955
956
957
958
959
            $fields,
            $params,
            $itemtype
         );
      }

960
961
962
963
964
965
966
967
      // Convert fields to the format expected by the deprecated type
      if ($this->isDeprecated()) {
         $fields = $this->deprecated_item->mapCurrentToDeprecatedFields($fields);
         $fields["links"] = $this->deprecated_item->mapCurrentToDeprecatedHateoas(
            $fields["links"] ?? []
         );
      }

Alexandre Delaunay's avatar
Alexandre Delaunay committed
968
969
970
      return $fields;
   }

yllen's avatar
CS    
yllen committed
971
972


Alexandre Delaunay's avatar
Alexandre Delaunay committed
973
974
   /**
    * Fill a sub array with a right error
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
975
976
977
    *
    * @return array
    */
Alexandre Delaunay's avatar
Alexandre Delaunay committed
978
   protected function arrayRightError() {
yllen's avatar
CS    
yllen committed
979

980
981
      return ['error'   => 401,
                   'message' => __("You don't have permission to perform this action.")];
Alexandre Delaunay's avatar
Alexandre Delaunay committed
982
983
984
985
   }



yllen's avatar
CS    
yllen committed
986
987


Alexandre Delaunay's avatar
Alexandre Delaunay committed
988
989
990
   /**
    * Return a collection of rows of the desired itemtype
    *
Anael Mobilia's avatar
PHPdoc    
Anael Mobilia committed
991
992
    * @param string  $itemtype   itemtype (class) of object
    * @param array   $params     with theses options :
Alexandre Delaunay's avatar
Alexandre Delaunay committed
993
994
995
996
    * - 'expand_dropdowns' (default: false): show dropdown's names instead of id. Optionnal
    * - 'get_hateoas'      (default: true): show relations of items in a links attribute. Optionnal
    * - 'only_id'          (default: false): keep only id in fields list. Optionnal
    * - 'range'            (default: 0-50): limit the list to start-end attributes
997
998
999
    * - 'sort'             (default: id): sort by the field.
    * - 'order'            (default: ASC): ASC(ending) or DESC(ending).
    * - 'searchText'       (default: NULL): array of filters to pass on the query (with key = field and value the search)
1000
    * - 'is_deleted'       (default: false): show trashbin. Optionnal