FusionDirectory
class_managementListing.inc
Go to the documentation of this file.
1 <?php
2 /*
3  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
4  Copyright (C) 2003-2010 Cajus Pollmeier
5  Copyright (C) 2011-2018 FusionDirectory
6 
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21 
31 {
32  public $pid;
33 
34  protected $entries = [];
35  protected $entriesIndex = [];
36  protected $base;
37 
38  protected $sortDirection = NULL;
39  protected $sortColumn = NULL;
40 
41  protected $baseMode = TRUE;
42  protected $multiSelect = TRUE;
43  protected $bases = [];
44  protected $header = [];
45  protected $objectTypeCount = [];
46  protected $baseSelector;
47 
48  /* The management class */
49  public $parent;
50 
51  protected $columns;
52 
53  protected $showFooter;
54 
60  function __construct (management $parent, bool $baseMode = TRUE, bool $multiSelect = TRUE)
61  {
62  global $config;
63 
64  $this->parent = $parent;
65  $this->baseMode = $baseMode;
66  $this->multiSelect = $multiSelect;
67 
68  // Initialize pid
69  $this->pid = preg_replace('/[^0-9]/', '', microtime(TRUE));
70 
71  $this->setUpBaseSelector();
72 
73  // Move footer information
74  $this->showFooter = ($config->get_cfg_value('listSummary') == 'TRUE');
75 
76  $this->reloadColumns();
77  }
78 
79  function setUpBaseSelector ()
80  {
81  global $config, $ui;
82 
83  // Set base for filter
84  if ($this->baseMode) {
85  $this->base = $ui->getCurrentBase();
86  $this->refreshBasesList();
87 
88  // Instanciate base selector
89  $this->baseSelector = new baseSelector($this->bases, $this->base);
90  } else {
91  $this->base = $config->current['BASE'];
92  }
93  }
94 
95  function getBaseMode (): bool
96  {
97  return $this->baseMode;
98  }
99 
100  function getMultiSelect (): bool
101  {
102  return $this->multiSelect;
103  }
104 
105  function reloadColumns ()
106  {
107  $columnInfos = $this->parent->getColumnConfiguration();
108 
109  $this->columns = [];
110  $first = TRUE;
111  foreach ($columnInfos as $columnInfo) {
112  $column = Column::build($this, $columnInfo[0], $columnInfo[1]);
113  if ($first && !empty($columnInfo[1]['attributes'])) {
114  $column->setTemplateAttributes(['cn']);
115  $first = FALSE;
116  }
117  $this->columns[] = $column;
118  }
119  }
120 
121  function getColumns (): array
122  {
123  return $this->columns;
124  }
125 
126  function renderHeader ()
127  {
128  $this->header = [];
129 
130  // Initialize sort?
131  $sortInit = FALSE;
132  if (!isset($this->sortDirection)) {
133  $this->sortColumn = 0;
134  $this->sortDirection = [];
135  $sortInit = TRUE;
136  }
137 
138  foreach ($this->columns as $index => $column) {
139  // Initialize everything to one direction
140  if ($sortInit) {
141  $this->sortDirection[$index] = FALSE;
142  }
143 
144  $this->header[$index] = [
145  'props' => $column->getHtmlProps(),
146  'sortable' => $column->isSortable(),
147  'label' => $column->getLabel(),
148  ];
149  if ($index == $this->sortColumn) {
150  if ($column->isSortable()) {
151  $this->header[$index]['sortdirection'] = $this->sortDirection[$index];
152  } elseif ($sortInit) {
153  /* sortColumn is not sortable, try next one */
154  $this->sortColumn++;
155  }
156  }
157  }
158  }
159 
163  function render (): string
164  {
165  global $ui;
166 
167  // Check for exeeded sizelimit
168  if (($message = $ui->getSizeLimitHandler()->check()) != '') {
169  return $message;
170  }
171 
172  $this->renderHeader();
173 
174  $smarty = get_smarty();
175  $smarty->assign('PID', $this->pid);
176  $smarty->assign('PLUG', $_GET['plug']);
177  $smarty->assign('multiSelect', $this->multiSelect);
178  $smarty->assign('showFooter', $this->showFooter);
179  $smarty->assign('headers', $this->header);
180 
181  $smarty->assign('columnCount', (count($this->columns) + ($this->multiSelect ? 1 : 0)));
182 
183  // Complete list by sorting entries and appending them to the output
184  $entryIterator = $this->getIterator();
185 
186  $rows = [];
187  foreach ($entryIterator as $entry) {
188  $row = [
189  'cells' => [],
190  'classes' => [],
191  ];
192  foreach ($this->columns as $column) {
193  $row['cells'][] = [
194  'props' => $column->getHtmlCellProps(),
195  'render' => $column->renderCell($entry)
196  ];
197  $column->fillRowClasses($row['classes'], $entry);
198  }
199 
200  $row['index'] = $entry->row;
201 
202  $rows[] = $row;
203  }
204  $smarty->assign('rows', $rows);
205 
206  // Add the footer if requested
207  if ($this->showFooter) {
208  $types = [];
209  foreach ($this->parent->objectTypes as $type) {
210  if (isset($this->objectTypeCount[$type])) {
211  $infos = objects::infos($type);
212  $types[] = [
213  'name' => $infos['name'],
214  'icon' => $infos['icon'],
215  'count' => $this->objectTypeCount[$type],
216  ];
217  }
218  if (isset($this->objectTypeCount['template_'.$type])) {
219  $infos = objects::infos($type);
220  $types[] = [
221  'name' => sprintf(_('%s template'), $infos['name']),
222  'icon' => 'geticon.php?context=devices&icon=template&size=16',
223  'count' => $this->objectTypeCount['template_'.$type],
224  ];
225  }
226  }
227  $smarty->assign('objectCounts', $types);
228  }
229 
230  /* If the user ignored the sizelimit warning he may get more entries than what PHP can handle */
232  count($this->entries),
233  _('The number of listed entries (%d) is greater than or too close to configured max_input_vars PHP ini setting (%d). Please change max_input_vars ini setting to a higher value.')
234  );
235  if ($error !== FALSE) {
236  $error->display();
237  }
238 
239  return $smarty->fetch(get_template_path('management/list.tpl'));
240  }
241 
242  function getIterator (): Iterator
243  {
244  return new EntrySortIterator($this->entries, $this->columns[$this->sortColumn] ?? NULL, $this->sortDirection[$this->sortColumn] ?? FALSE);
245  }
246 
250  function updateBase ()
251  {
252  global $ui;
253 
254  // Take care of base selector
255  if ($this->baseMode) {
256  $this->baseSelector->update();
257 
258  // Check if a wrong base was supplied
259  if (!$this->baseSelector->checkLastBaseUpdate()) {
261  $error->display();
262  }
263 
264  // Save base
265  $this->base = $this->baseSelector->getBase();
266  $ui->setCurrentBase($this->base);
267  }
268 
269  // Do not do anything if this is not our PID
270  if ($this->baseMode || !(isset($_REQUEST['PID']) && ($_REQUEST['PID'] != $this->pid))) {
271  // Filter GET with "act" attributes
272  if (isset($_GET['act'])) {
273  $key = validate($_GET['act']);
274  if (preg_match('/^SORT_([0-9]+)$/', $key, $match)) {
275  $this->setSortColumn($match[1]);
276 
277  // Allow header to update itself according to the new sort settings
278  $this->renderHeader();
279  }
280  }
281 
282  if ($this->baseMode) {
283  // Override base if we got signals from the navigation elements
284  $action = '';
285  foreach (array_keys($_POST) as $key) {
286  if (preg_match('/^(ROOT|BACK|HOME)_x$/', $key, $match)) {
287  $action = $match[1];
288  break;
289  }
290  }
291 
292  // Navigation handling
293  if ($action == 'ROOT') {
294  $this->setBase(key($this->bases));
295  } elseif ($action == 'BACK') {
296  $parentBase = preg_replace('/^[^,]+,/', '', $this->base);
297  $this->tryAndSetBase($parentBase);
298  } elseif ($action == 'HOME') {
299  $ui = get_userinfo();
300  $this->tryAndSetBase($ui->getBase());
301  }
302  }
303  }
304  }
305 
309  function update (string $dn = NULL)
310  {
311  if ($dn === NULL) {
312  $this->updateBase();
313  }
314 
315  // Update filter
316  $this->parent->filter->update($this->base);
317 
318  // Update filter and refresh entries
319  $attrs = $this->parent->neededAttrs;
320  foreach ($this->columns as $column) {
321  $column->fillNeededAttributes($attrs);
322  }
323  if ($dn !== NULL) {
324  $this->parent->filter->setScope('base');
325  list($this->entries, $this->objectTypeCount) = $this->parent->filter->query($attrs, $dn);
326  $this->parent->filter->setScope('one');
327  } else {
328  list($this->entries, $this->objectTypeCount) = $this->parent->filter->query($attrs, $this->base);
329  }
330  /* Store the order of the entries to access them by index later */
331  $this->entriesIndex = array_keys($this->entries);
332  }
333 
339  function setBase (string $base)
340  {
341  global $ui;
342  $this->base = $base;
343  if ($this->baseMode) {
344  $this->baseSelector->setBase($this->base);
345  $ui->setCurrentBase($this->base);
346  }
347  }
348 
349  function tryAndSetBase ($base)
350  {
351  if (isset($this->bases[$base])) {
352  $this->setBase($base);
353  }
354  }
355 
361  function getBase (): string
362  {
363  return $this->base;
364  }
365 
366  function renderBase (): string
367  {
368  if (!$this->baseMode) {
369  return '';
370  }
371 
372  return $this->baseSelector->render();
373  }
374 
375  function renderNavigation (bool $skipConfiguration = FALSE): array
376  {
377  global $ui;
378 
379  if ($this->baseMode) {
380  $enableBack = TRUE;
381  $enableRoot = TRUE;
382  $enableHome = TRUE;
383 
384  /* Check if base = first available base */
385  $deps = array_keys($this->bases);
386 
387  if (!count($deps) || $deps[0] == $this->base) {
388  $enableBack = FALSE;
389  $enableRoot = FALSE;
390  }
391 
392  /* Check if we are in users home department */
393  if (!count($deps) || ($this->base == $ui->getBase()) || !in_array_ics($ui->getBase(), $deps)) {
394  $enableHome = FALSE;
395  }
396 
397  $actions = [
398  [
399  'id' => 'ROOT',
400  'desc' => _('Go to root department'),
401  'name' => _('Root'),
402  'icon' => 'geticon.php?context=actions&icon=go-first&size=16',
403  'enabled' => $enableRoot,
404  'class' => '',
405  ],
406  [
407  'id' => 'BACK',
408  'desc' => _('Go up one department'),
409  'name' => _('Up'),
410  'icon' => 'geticon.php?context=actions&icon=go-up&size=16',
411  'enabled' => $enableBack,
412  'class' => '',
413  ],
414  [
415  'id' => 'HOME',
416  'desc' => _('Go to user\'s department'),
417  'name' => _('Home'),
418  'icon' => 'geticon.php?context=actions&icon=go-home&size=16',
419  'enabled' => $enableHome,
420  'class' => '',
421  ],
422  ];
423  } else {
424  $actions = [];
425  }
426 
427  $actions[] = [
428  'id' => 'REFRESH',
429  'desc' => _('Reload list'),
430  'name' => _('Reload'),
431  'icon' => 'geticon.php?context=actions&icon=view-refresh&size=16',
432  'enabled' => TRUE,
433  'class' => 'optional',
434  ];
435 
436  if (!$skipConfiguration) {
437  $actions[] = [
438  'id' => 'listing_configure',
439  'desc' => _('Configure this management list'),
440  'name' => _('Configure'),
441  'icon' => 'geticon.php?context=categories&icon=settings&size=16',
442  'enabled' => TRUE,
443  'class' => '',
444  ];
445  }
446 
447  return $actions;
448  }
449 
453  function getAction (): array
454  {
455  global $config;
456 
457  $result = ['targets' => [], 'action' => '', 'subaction' => NULL];
458 
459  // Do not do anything if this is not our PID, or there's even no PID available...
460  if (!isset($_REQUEST['dn']) && (!isset($_REQUEST['PID']) || $_REQUEST['PID'] != $this->pid)) {
461  return $result;
462  }
463 
464  if (isset($_GET['act'])) {
465  // Filter GET with "act" attributes
466  $key = validate($_GET['act']);
467  if (preg_match('/^listing_([[:alnum:]_\.]+)_([0-9]+)$/', $key, $m)) {
468  $target = $m[2];
469  if (isset($this->entriesIndex[$target])) {
470  $result['action'] = $m[1];
471  $result['targets'][] = $this->entriesIndex[$target];
472  }
473  } elseif (isset($_REQUEST['dn']) && preg_match('/^listing_([[:alnum:]_\.]+)$/', $key, $m)) {
474  /* Pre-render list to init things if a dn is gonna be opened on first load */
475  $dn = urldecode($_REQUEST['dn']);
476  $this->focusDn($dn);
477  $this->render();
478 
479  $result['action'] = $m[1];
480  $result['targets'][] = $dn;
481  // Make sure no other management class intercept the same dn
482  unset($_REQUEST['dn']);
483  }
484  } else {
485  // Filter POST with "act" attributes -> posted from action menu
486  if (isset($_POST['act']) && ($_POST['act'] != '')) {
487  $result['action'] = validate($_POST['act']);
488  }
489 
490  // Filter POST with "listing_" attributes
491  foreach (array_keys($_POST) as $key) {
492  // Capture selections
493  if (preg_match('/^listing_selected_([0-9]+)$/', $key, $m)) {
494  $target = $m[1];
495  if (isset($this->entriesIndex[$target])) {
496  $result['targets'][] = $this->entriesIndex[$target];
497  }
498  continue;
499  }
500 
501  // Capture action with target - this is a one shot
502  if (preg_match('/^listing_([[:alnum:]_\.]+)_([0-9]+)(|_x)$/', $key, $m)) {
503  $target = $m[2];
504  if (isset($this->entriesIndex[$target])) {
505  $result['action'] = $m[1];
506  $result['targets'] = [$this->entriesIndex[$target]];
507  }
508  break;
509  }
510 
511  // Capture action without target
512  if (preg_match('/^listing_([[:alnum:]_\.]+)(|_x)$/', $key, $m)) {
513  $result['action'] = $m[1];
514  continue;
515  }
516  }
517  }
518 
519  if (strpos($result['action'], '_') !== FALSE) {
520  list($result['action'], $result['subaction']) = explode('_', $result['action'], 2);
521  }
522  return $result;
523  }
524 
528  function focusDn (string $dn)
529  {
530  /* Detect the longer base valid for this dn */
531  $longerBase = '';
532  foreach (array_keys($this->bases) as $base) {
533  if (preg_match('/'.preg_quote($base, '/').'$/i', $dn)
534  && (strlen($base) > strlen($longerBase))) {
535  $longerBase = $base;
536  }
537  }
538  $this->setBase($longerBase);
539  $this->update($dn);
540  }
541 
545  function refreshBasesList ()
546  {
547  global $config;
548  $ui = get_userinfo();
549 
550  // Fill internal bases list
551  $this->bases = [];
552 
553  $categories = [];
554  foreach ($this->parent->objectTypes as $otype) {
555  $i = objects::infos($otype);
556  $categories[$i['aclCategory']] = $i['aclCategory'];
557  }
558 
559  $deps = $ui->get_module_departments(array_values($categories));
560  $departmentTree = $config->getDepartmentTree();
561  foreach ($departmentTree as $key => $dep) {
562  if (in_array_ics($key, $deps)) {
563  $this->bases[$key] = $dep;
564  }
565  }
566 
567  if (!empty($this->bases) && !isset($this->bases[$this->base])) {
568  $this->base = key($this->bases);
569  }
570 
571  // Populate base selector if already present
572  if ($this->baseSelector && $this->baseMode) {
573  $this->baseSelector->setBases($this->bases);
574  $this->baseSelector->setBase($this->base);
575  $this->baseSelector->update(TRUE);
576  }
577  }
578 
584  function getEntry (string $dn)
585  {
586  if (isset($this->entries[$dn])) {
587  return $this->entries[$dn];
588  }
589  return NULL;
590  }
591 
598  public function setSortColumn (int $column, bool $direction = NULL)
599  {
600  if ($direction === NULL) {
601  // Switch to new column or invert search order?
602  $direction = (($this->sortColumn == $column) && !$this->sortDirection[$column]);
603  }
604  $this->sortColumn = $column;
605  $this->sortDirection[$column] = $direction;
606  }
607 
608  function fillSearchedAttributes (string $type, array &$attrs)
609  {
610  global $ui;
611 
612  $searchedAttributes = [];
613  foreach ($this->columns as $column) {
614  $column->fillSearchedAttributes($searchedAttributes);
615  }
616 
617  $searchedAttributes = array_unique($searchedAttributes);
618 
619  foreach ($searchedAttributes as $attr) {
620  if (!isset($attrs[$attr])) {
621  $category = $ui->getAttributeCategory($type, $attr);
622  if ($category !== FALSE) {
623  $attrs[$attr] = $category;
624  }
625  }
626  }
627  }
628 }
render()
Accessor of the member tree.
in_array_ics($value, array $items)
Check if a value exists in an array (case-insensitive)
Definition: functions.inc:814
get_template_path($filename='', $plugin=FALSE, $path='')
Return themed path for specified base file.
Definition: functions.inc:174
setBase(string $base)
Set a new base valor.
getEntry(string $dn)
Get entry.
This class contains all the function needed to sort list go up, go down , back , next. etc...
update(bool $force=FALSE)
Update the base.
& get_userinfo()
Return the current userinfo object.
Definition: functions.inc:312
updateBase()
Updates base and sorting according to POST and GET infos.
static build(managementListing $parent, string $type, array $data)
Builds a column object from given data.
update(string $dn=NULL)
Update a listing.
Class Base Selector.
checkLastBaseUpdate()
Check the last base value updated.
__construct(management $parent, bool $baseMode=TRUE, bool $multiSelect=TRUE)
Create a listing.
& get_smarty()
Get global smarty object.
Definition: functions.inc:324
This class handles the entries list for a management instance.
static checkMaxInputVars(int $newLimit, string $messageTemplate=NULL)
static check_base()
Display error when checking the base.
getBase()
Accessor of the base.
focusDn(string $dn)
Set base close to this dn and load only him.
validate($string)
Removes malicious characters from a (POST) string.
Definition: functions.inc:826
getBase()
Accessor of the base.
refreshBasesList()
Refresh the bases list.
Parent class for all errors in FusionDirectory.
setSortColumn(int $column, bool $direction=NULL)
Set sort column.
Management base class.
setBases(array $bases)
Set new bases.
setBase(string $base)
Set a new value of the member base.