FusionDirectory
class_management.inc
1 <?php
2 /*
3  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
4  Copyright (C) 2017-2020 FusionDirectory
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with this program; if not, write to the Free Software
18  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20 
26 {
27  /* Object types we are currently managing */
28  public $objectTypes;
29 
30  /* managementListing instance which manages the entries */
31  public $listing;
32 
33  /* managementFilter instance which manages the filters */
34  public $filter;
35 
36  /* Copy&Paste */
37  protected $cpHandler = NULL;
38  protected $cpPastingStarted = FALSE;
39  protected $skipCpHandler = FALSE;
40 
41  /* Snapshots */
42  protected $snapHandler = NULL;
43  public static $skipSnapshots = FALSE;
44 
45  // The currently used object(s) (e.g. in edit, removal)
46  protected $currentDn = '';
47  protected $currentDns = [];
48 
49  // The last used object(s).
50  protected $previousDn = '';
51  protected $previousDns = [];
52 
53  // The opened object.
57  protected $tabObject = NULL;
58  protected $dialogObject = NULL;
59 
60  // The last opened object.
61  protected $last_tabObject = NULL;
62  protected $last_dialogObject = NULL;
63 
64  protected $renderCache;
65 
66  public $headline;
67  public $title;
68  public $icon;
69 
70  protected $actions = [];
71  protected $actionHandlers = [];
72 
73  protected $exporters = [];
74 
75  public $neededAttrs = [];
76 
77  public static $skipTemplates = TRUE;
78 
79  /* Disable and hide configuration system */
80  protected $skipConfiguration = FALSE;
81 
82  protected $columnConfiguration;
83 
84  /* Default columns */
85  public static $columns = [
86  ['ObjectTypeColumn', []],
87  ['LinkColumn', ['attributes' => 'nameAttr', 'label' => 'Name']],
88  ['LinkColumn', ['attributes' => 'description', 'label' => 'Description']],
89  ['ActionsColumn', ['label' => 'Actions']],
90  ];
91 
92  function __construct (
93  $objectTypes = FALSE,
94  array $filterElementDefinitions = [
95  ['TabFilterElement', []],
96  ]
97  )
98  {
99  global $config, $class_mapping;
100 
101  if ($objectTypes === FALSE) {
102  $plInfos = pluglist::pluginInfos(get_class($this));
103  $objectTypes = $plInfos['plManages'];
104  }
105 
106  if (!preg_match('/^geticon/', $this->icon)) {
107  $this->icon = get_template_path($this->icon);
108  }
109 
110  /* Ignore non existing objectTypes. This happens when an optional plugin is missing. */
111  foreach ($objectTypes as $key => $type) {
112  try {
113  objects::infos($type);
114  $objectTypes[$key] = strtoupper($type);
115  } catch (NonExistingObjectTypeException $e) {
116  unset($objectTypes[$key]);
117  }
118  }
119 
120  $this->objectTypes = array_values($objectTypes);
121 
122  $this->setUpHeadline();
123  $this->setUpListing();
124  $this->setUpFilter($filterElementDefinitions);
125 
126  // Add copy&paste and snapshot handler.
127  if (!$this->skipCpHandler) {
128  $this->cpHandler = new CopyPasteHandler();
129  }
130  if (!static::$skipSnapshots && ($config->get_cfg_value('enableSnapshots') == 'TRUE')) {
131  $this->snapHandler = new SnapshotHandler();
132  }
133 
134  // Load exporters
135  foreach (array_keys($class_mapping) as $class) {
136  if (preg_match('/Exporter$/', $class)) {
137  $info = call_user_func([$class, 'getInfo']);
138  if ($info != NULL) {
139  $this->exporters = array_merge($this->exporters, $info);
140  }
141  }
142  }
143 
144  $this->configureActions();
145  }
146 
147  protected function setUpListing ()
148  {
149  $this->listing = new managementListing($this);
150  }
151 
152  protected function setUpFilter (array $filterElementDefinitions)
153  {
154  $this->filter = new managementFilter($this, NULL, $filterElementDefinitions);
155  }
156 
157  protected function setUpHeadline ()
158  {
159  $plInfos = pluglist::pluginInfos(get_class($this));
160 
161  $this->headline = $plInfos['plShortName'];
162  $this->title = $plInfos['plTitle'];
163  $this->icon = $plInfos['plIcon'];
164  }
165 
166  protected function configureActions ()
167  {
168  global $config;
169 
170  // Register default actions
171  $createMenu = [];
172 
173  if (!static::$skipTemplates) {
174  $templateMenu = [];
175  $fromTemplateMenu = [];
176  }
177 
178  foreach ($this->objectTypes as $type) {
179  $infos = objects::infos($type);
180  $img = 'geticon.php?context=actions&icon=document-new&size=16';
181  if (isset($infos['icon'])) {
182  $img = $infos['icon'];
183  }
184  $createMenu[] = new Action(
185  'new_' . $type, $infos['name'], $img,
186  '0', 'newEntry',
187  [$infos['aclCategory'] . '/' . $infos['mainTab'] . '/c']
188  );
189  if (!static::$skipTemplates) {
190  $templateMenu[] = new Action(
191  'new_template_' . $type, $infos['name'], $img,
192  '0', 'newEntryTemplate',
193  [$infos['aclCategory'] . '/template/c']
194  );
195  $fromTemplateMenu[] = new Action(
196  'template_apply_' . $type, $infos['name'], $img,
197  '0', 'newEntryFromTemplate',
198  [$infos['aclCategory'] . '/template/r', $infos['aclCategory'] . '/' . $infos['mainTab'] . '/c']
199  );
200  }
201  }
202 
203  if (!static::$skipTemplates) {
204  $createMenu =
205  array_merge(
206  [
207  new SubMenuAction(
208  'template', _('Template'), 'geticon.php?context=devices&icon=template&size=16',
209  $templateMenu
210  ),
211  new SubMenuAction(
212  'fromtemplate', _('From template'), 'geticon.php?context=actions&icon=document-new&size=16',
213  $fromTemplateMenu
214  ),
215  ],
216  $createMenu
217  );
218  }
219 
220  $this->registerAction(
221  new SubMenuAction(
222  'new', _('Create'), 'geticon.php?context=actions&icon=document-new&size=16',
223  $createMenu
224  )
225  );
226 
227  // Add export actions
228  $exportMenu = [];
229  foreach ($this->exporters as $action => $exporter) {
230  $exportMenu[] = new Action(
231  $action, $exporter['label'], $exporter['image'],
232  '0', 'export'
233  );
234  }
235  $this->registerAction(
236  new SubMenuAction(
237  'export', _('Export list'), 'geticon.php?context=actions&icon=document-export&size=16',
238  $exportMenu
239  )
240  );
241 
242  $this->registerAction(
243  new Action(
244  'edit', _('Edit'), 'geticon.php?context=actions&icon=document-edit&size=16',
245  '+', 'editEntry'
246  )
247  );
248  $this->actions['edit']->setSeparator(TRUE);
249 
250  if (!$this->skipCpHandler) {
251  $this->registerAction(
252  new Action(
253  'cut', _('Cut'), 'geticon.php?context=actions&icon=edit-cut&size=16',
254  '+', 'copyPasteHandler',
255  ['dr']
256  )
257  );
258  $this->registerAction(
259  new Action(
260  'copy', _('Copy'), 'geticon.php?context=actions&icon=edit-copy&size=16',
261  '+', 'copyPasteHandler',
262  ['r']
263  )
264  );
265  $this->registerAction(
266  new Action(
267  'paste', _('Paste'), 'geticon.php?context=actions&icon=edit-paste&size=16',
268  '0', 'copyPasteHandler',
269  ['w']
270  )
271  );
272  $this->actions['paste']->setEnableFunction([$this, 'enablePaste']);
273  }
274 
275  if (!static::$skipTemplates) {
276  $this->registerAction(
277  new Action(
278  'template_apply_to', _('Apply template'), 'geticon.php?context=actions&icon=tools-wizard&size=16',
279  '+', 'applyTemplateToEntry',
280  ['/template/r', 'c'],
281  TRUE,
282  FALSE
283  )
284  );
285  }
286 
287  if (class_available('archivedObject')) {
288  $action = archivedObject::getManagementAction($this->objectTypes, 'archiveRequested');
289  if ($action !== NULL) {
290  $this->registerAction($action);
291  $this->registerAction(new HiddenAction('archiveConfirmed', 'archiveConfirmed'));
292  $this->registerAction(new HiddenAction('archiveCancel', 'cancelEdit'));
293  }
294  }
295 
296  $this->registerAction(
297  new Action(
298  'remove', _('Remove'), 'geticon.php?context=actions&icon=edit-delete&size=16',
299  '+', 'removeRequested',
300  ['d']
301  )
302  );
303 
304  if (!static::$skipSnapshots && ($config->get_cfg_value('enableSnapshots') == 'TRUE')) {
305  $this->registerAction(
306  new Action(
307  'snapshot', _('Create snapshot'), 'geticon.php?context=actions&icon=snapshot&size=16',
308  '1', 'createSnapshotDialog',
309  ['/SnapshotHandler/c']
310  )
311  );
312  $this->registerAction(
313  new Action(
314  'restore', _('Restore snapshot'), 'geticon.php?context=actions&icon=document-restore&size=16',
315  '*', 'restoreSnapshotDialog',
316  ['w', '/SnapshotHandler/r']
317  )
318  );
319  $this->actions['snapshot']->setSeparator(TRUE);
320  $this->actions['restore']->setEnableFunction([$this, 'enableSnapshotRestore']);
321  }
322 
323  if (!static::$skipTemplates) {
324  $this->registerAction(
325  new Action(
326  'template_apply', _('Create an object from this template'), 'geticon.php?context=actions&icon=document-new&size=16',
327  '1', 'newEntryFromTemplate',
328  ['/template/r', 'c'],
329  FALSE,
330  TRUE,
331  ['template']
332  )
333  );
334  }
335 
336  /* Actions from footer are not in any menus and do not need a label */
337  $this->registerAction(new HiddenAction('apply', 'applyChanges'));
338  $this->registerAction(new HiddenAction('save', 'saveChanges'));
339  $this->registerAction(new HiddenAction('cancel', 'cancelEdit'));
340  $this->registerAction(new HiddenAction('cancelDelete', 'cancelEdit'));
341  $this->registerAction(new HiddenAction('removeConfirmed', 'removeConfirmed'));
342  if (!$this->skipConfiguration) {
343  $this->registerAction(new HiddenAction('configure', 'configureDialog'));
344  }
345  }
346 
350  function registerAction (Action $action)
351  {
352  $action->setParent($this);
353  $this->actions[$action->getName()] = $action;
354  foreach ($action->listActions() as $actionName) {
355  $this->actionHandlers[$actionName] = $action;
356  }
357  }
358 
359  public function getColumnConfiguration (): array
360  {
361  global $config;
362 
363  if (!isset($this->columnConfiguration)) {
364  // LDAP configuration
365  $this->columnConfiguration = $config->getManagementConfig(get_class($this));
366  }
367 
368  if (!isset($this->columnConfiguration)) {
369  // Default configuration
370  $this->columnConfiguration = static::$columns;
371  }
372 
373  // Session configuration
374  return $this->columnConfiguration;
375  }
376 
377  public function setColumnConfiguration ($columns)
378  {
379  $this->columnConfiguration = $columns;
380  $this->listing->reloadColumns();
381  }
382 
387  function detectPostActions (): array
388  {
389  if (!is_object($this->listing)) {
390  throw new FusionDirectoryException('No valid listing object');
391  }
392  $action = ['targets' => [], 'action' => '', 'subaction' => NULL];
393  if ($this->showTabFooter()) {
394  if (isset($_POST['edit_cancel'])) {
395  $action['action'] = 'cancel';
396  } elseif (isset($_POST['edit_finish'])) {
397  $action['action'] = 'save';
398  } elseif (isset($_POST['edit_apply'])) {
399  $action['action'] = 'apply';
400  }
401  } elseif (!$this->dialogOpened()) {
402  if (isset($_POST['delete_confirmed'])) {
403  $action['action'] = 'removeConfirmed';
404  } elseif (isset($_POST['delete_cancel'])) {
405  $action['action'] = 'cancelDelete';
406  } elseif (isset($_POST['archive_confirmed'])) {
407  $action['action'] = 'archiveConfirmed';
408  } elseif (isset($_POST['archive_cancel'])) {
409  $action['action'] = 'archiveCancel';
410  } else {
411  $action = $this->listing->getAction();
412  }
413  }
414 
415  return $action;
416  }
417 
421  function handleAction (array $action)
422  {
423  // Start action
424  if (isset($action['subaction']) && isset($this->actionHandlers[$action['action'] . '_' . $action['subaction']])) {
425  return $this->actionHandlers[$action['action'] . '_' . $action['subaction']]->execute($this, $action);
426  } elseif (isset($this->actionHandlers[$action['action']])) {
427  return $this->actionHandlers[$action['action']]->execute($this, $action);
428  }
429  }
430 
431  protected function handleSubAction (array $action): bool
432  {
433  if (preg_match('/^tab_/', $action['subaction'])) {
434  $tab = preg_replace('/^tab_/', '', $action['subaction']);
435  if (isset($this->tabObject->by_object[$tab])) {
436  $this->tabObject->current = $tab;
437  } else {
438  trigger_error('Unknown tab: ' . $tab);
439  }
440  return TRUE;
441  }
442  return FALSE;
443  }
444 
445  /* For management we have to render directly in readPost in some cases */
446  public function readPost ()
447  {
448  $this->renderCache = $this->execute();
449  }
450 
451  public function update (): bool
452  {
453  if ($this->renderCache === NULL) {
454  if (!$this->dialogOpened()) {
455  // Update list
456  $this->listing->update();
457 
458  // Init snapshot list for renderSnapshotActions
459  if (is_object($this->snapHandler)) {
460  $this->snapHandler->initSnapshotCache($this->listing->getBase());
461  }
462  }
463  }
464  return TRUE;
465  }
466 
467  public function render (): string
468  {
469  if ($this->renderCache === NULL) {
470  if ($this->tabObject instanceof simpleTabs) {
471  /* Display tab object */
472  $display = $this->tabObject->render();
473  $display .= $this->getTabFooter();
474  $this->renderCache = $this->getHeader() . $display;
475  } elseif (is_object($this->dialogObject)) {
476  /* Display dialog object */
477  $display = $this->dialogObject->render();
478  $display .= $this->getTabFooter();
479  $this->renderCache = $this->getHeader() . $display;
480  } else {
481  /* Display list */
482  $this->renderCache = $this->renderList();
483  }
484  }
485  return $this->renderCache;
486  }
487 
492  protected function execute ()
493  {
494  // Ensure that html posts and gets are kept even if we see a 'Entry islocked' dialog.
495  session::set('LOCK_VARS_TO_USE', ['/^act$/', '/^listing/', '/^PID$/']);
496 
497  /* Display the copy & paste dialog, if it is currently open */
498  $ret = $this->copyPasteHandler();
499  if ($ret) {
500  return $this->getHeader() . $ret;
501  }
502 
503  // Handle actions (POSTs and GETs)
504  $action = $this->detectPostActions();
505  if (!empty($action['action'])) {
506  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $action, 'Action');
507  try {
508  $str = $this->handleAction($action);
509  if (!empty($str)) {
510  return $this->getHeader() . $str;
511  }
512  } catch (FusionDirectoryException $e) {
513  $error = new FusionDirectoryError(htmlescape($e->getMessage()), 0, $e);
514  $error->display();
515  }
516  }
517 
518  /* Save tab or dialog object */
519  if ($this->tabObject instanceof simpleTabs) {
520  $this->tabObject->readPost();
521  $this->tabObject->update();
522  } elseif (is_object($this->dialogObject)) {
523  try {
524  $this->dialogObject->readPost();
525  if (is_object($this->dialogObject)) {
526  /* Check again as readPost might close it */
527  if (!$this->dialogObject->update()) {
528  $this->closeDialogs();
529  }
530  }
531  } catch (FusionDirectoryException $e) {
532  $error = new FusionDirectoryError(htmlescape($e->getMessage()), 0, $e);
533  $error->display();
534  $this->closeDialogs();
535  }
536  }
537 
538  return NULL;
539  }
540 
541  function renderList (): string
542  {
543  global $config, $ui;
544 
545  // Rendering things using smarty themselves first
546  $listRender = $this->listing->render();
547  $filterRender = $this->renderFilter();
548  $actionMenu = $this->renderActionMenu();
549 
550  $smarty = get_smarty();
551  $smarty->assign('usePrototype', 'true');
552  $smarty->assign('LIST', $listRender);
553  $smarty->assign('FILTER', $filterRender);
554  $smarty->assign('ACTIONS', $actionMenu);
555  $smarty->assign('SIZELIMIT', $ui->getSizeLimitHandler()->renderWarning());
556  $smarty->assign('NAVIGATION', $this->listing->renderNavigation($this->skipConfiguration));
557  $smarty->assign('BASE', $this->listing->renderBase());
558  $smarty->assign('HEADLINE', $this->headline);
559 
560  return $this->getHeader() . $smarty->fetch(get_template_path('management/management.tpl'));
561  }
562 
563  protected function renderFilter (): string
564  {
565  return $this->filter->render();
566  }
567 
568  protected function renderActionMenu (): string
569  {
570  $menuActions = [];
571  foreach ($this->actions as $action) {
572  // Build ul/li list
573  $action->fillMenuItems($menuActions);
574  }
575 
576  if (empty($menuActions)) {
577  return '';
578  }
579 
580  $smarty = get_smarty();
581  $smarty->assign('actions', $menuActions);
582  return $smarty->fetch(get_template_path('management/actionmenu.tpl'));
583  }
584 
585  function renderActionColumn (ListingEntry $entry): string
586  {
587  // Go thru all actions
588  $result = '';
589  foreach ($this->actions as $action) {
590  $result .= $action->renderColumnIcons($entry);
591  }
592 
593  return $result;
594  }
595 
596  function fillActionRowClasses (&$classes, ListingEntry $entry)
597  {
598  foreach ($this->actions as $action) {
599  $action->fillRowClasses($classes, $entry);
600  }
601  }
602 
609  public function removeLocks ()
610  {
611  if (!empty($this->currentDn) && ($this->currentDn != 'new')) {
612  Lock::deleteByObject($this->currentDn);
613  }
614  if (count($this->currentDns)) {
615  Lock::deleteByObject($this->currentDns);
616  }
617  }
618 
619  function dialogOpened (): bool
620  {
621  return (is_object($this->tabObject) || is_object($this->dialogObject));
622  }
623 
627  protected function getHeader (): string
628  {
629  global $smarty;
630 
631  $smarty->assign('headline', $this->title);
632  $smarty->assign('headline_image', $this->icon);
633 
634  if (is_object($this->tabObject) && ($this->currentDn != '')) {
635  return '<div class="pluginfo">' . $this->currentDn . "</div>\n";
636  }
637  return '';
638  }
639 
640  function openTabObject ($object)
641  {
642  $this->tabObject = $object;
643  $this->tabObject->parent = &$this;
644  }
645 
650  public function closeDialogs ()
651  {
652  $this->previousDn = $this->currentDn;
653  $this->currentDn = '';
654  $this->previousDns = $this->currentDns;
655  $this->currentDns = [];
656 
657  $this->last_tabObject = $this->tabObject;
658  $this->tabObject = NULL;
659  $this->last_dialogObject = $this->dialogObject;
660  $this->dialogObject = NULL;
661  }
662 
663  protected function listAclCategories (): array
664  {
665  $cat = [];
666  foreach ($this->objectTypes as $type) {
667  $infos = objects::infos($type);
668  $cat[] = $infos['aclCategory'];
669  }
670  return array_unique($cat);
671  }
672 
676  protected function showTabFooter (): bool
677  {
678  // Do not display tab footer for non tab objects
679  if (!($this->tabObject instanceof simpleTabs)) {
680  return FALSE;
681  }
682 
683  // Check if there is a dialog opened - We don't need any buttons in this case.
684  if ($this->tabObject->dialogOpened()) {
685  return FALSE;
686  }
687 
688  return TRUE;
689  }
690 
694  protected function getTabFooter (): string
695  {
696  // Do not display tab footer for non tab objects
697  if (!$this->showTabFooter()) {
698  return '';
699  }
700 
701  $smarty = get_smarty();
702  $smarty->assign('readOnly', $this->tabObject->readOnly());
703  $smarty->assign('showApply', ($this->currentDn != 'new'));
704  return $smarty->fetch(get_template_path('management/tabfooter.tpl'));
705  }
706 
707  function handleTemplateApply ($cancel = FALSE)
708  {
709  if (static::$skipTemplates) {
710  return;
711  }
712  if ($cancel) {
713  $msgs = [];
714  } else {
715  $msgs = $this->tabObject->save();
716  }
717  if (count($msgs)) {
718  msg_dialog::displayChecks($msgs);
719  return;
720  } else {
721  if (!$cancel) {
722  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->currentDn, 'Template applied!');
723  }
724  Lock::deleteByObject($this->currentDn);
725  if (empty($this->currentDns)) {
726  $this->closeDialogs();
727  } else {
728  $this->last_tabObject = $this->tabObject;
729  $this->tabObject = NULL;
730  $this->currentDn = array_shift($this->currentDns);
731  $this->dialogObject->setNextTarget($this->currentDn);
732  $this->dialogObject->readPost();
733  }
734  }
735  }
736 
737  function enablePaste ($action, ListingEntry $entry = NULL): bool
738  {
739  if ($entry === NULL) {
740  return $this->cpHandler->entries_queued();
741  } else {
742  return FALSE;
743  }
744  }
745 
746  /* Action handlers */
747 
755  function newEntry (array $action)
756  {
757  $type = $action['subaction'];
758 
759  $this->currentDn = 'new';
760 
761  // Open object
762  $this->openTabObject(objects::create($type));
763  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->currentDn, 'Create entry initiated');
764  }
765 
766  function newEntryTemplate (array $action)
767  {
768  if (static::$skipTemplates) {
769  return;
770  }
771  $type = preg_replace('/^template_/', '', $action['subaction']);
772 
773  $this->currentDn = 'new';
774 
775  // Open object
776  $this->openTabObject(objects::createTemplate($type));
777  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->currentDn, 'Create template entry initiated');
778  }
779 
780  function newEntryFromTemplate (array $action)
781  {
782  if (static::$skipTemplates) {
783  return;
784  }
785  if (isset($action['targets'][0])) {
786  $dn = $action['targets'][0];
787  } else {
788  $dn = NULL;
789  }
790  if ($action['subaction'] == 'apply') {
791  if ($dn === NULL) {
792  return;
793  }
794  $type = $this->listing->getEntry($dn)->getTemplatedType();
795  } else {
796  $type = preg_replace('/^apply_/', '', $action['subaction']);
797  }
798  $this->dialogObject = new templateDialog($this, $type, $dn);
799  }
800 
801  function applyTemplateToEntry (array $action)
802  {
803  global $ui;
804  if (static::$skipTemplates) {
805  return;
806  }
807  if (empty($action['targets'])) {
808  return;
809  }
810  $this->currentDns = $action['targets'];
811 
812  // check locks
813  if ($locks = Lock::get($this->currentDns)) {
814  return Lock::genLockedMessage($locks, FALSE, _('Apply anyway'));
815  }
816 
817  // Add locks
818  Lock::add($this->currentDns);
819 
820  // Detect type and check that all targets share the same type
821  $type = NULL;
822 
823  foreach ($this->currentDns as $dn) {
824  $entry = $this->listing->getEntry($dn);
825  if ($entry === NULL) {
826  trigger_error('Could not find ' . $dn . ', action canceled');
827  $this->currentDns = [];
828  return;
829  }
830 
831  if ($entry->isTemplate()) {
832  $error = new FusionDirectoryError(htmlescape(_('Applying a template to a template is not possible')));
833  $error->display();
834  $this->currentDns = [];
835  return;
836  }
837 
838  if (!isset($type)) {
839  $type = $entry->type;
840  } elseif ($entry->type != $type) {
841  $error = new FusionDirectoryError(htmlescape(_('All selected entries need to share the same type to be able to apply a template to them')));
842  $error->display();
843  $this->currentDns = [];
844  return;
845  }
846  }
847 
848  $this->currentDn = array_shift($this->currentDns);
849 
850  $this->dialogObject = new templateDialog($this, $type, NULL, $this->currentDn);
851  }
852 
856  public function archiveRequested (array $action)
857  {
858  global $ui;
859 
860  if (empty($action['targets'])) {
861  return;
862  }
863  $this->currentDns = $action['targets'];
864 
865  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $action['targets'], 'Entry archive requested');
866 
867  // Check locks
868  if ($locks = Lock::get($this->currentDns)) {
869  return Lock::genLockedMessage($locks, FALSE, _('Archive anyway'));
870  }
871 
872  // Add locks
873  Lock::add($this->currentDns);
874 
875  $objects = [];
876  foreach ($this->currentDns as $dn) {
877  $entry = $this->listing->getEntry($dn);
878  if ($entry->isTemplate()) {
879  $error = new FusionDirectoryError(htmlescape(_('Archiving a template is not possible')));
880  $error->display();
881  $this->removeLocks();
882  $this->currentDns = [];
883  return;
884  }
885  $infos = objects::infos($entry->getTemplatedType());
886  $objects[] = [
887  'name' => $entry[$infos['nameAttr']][0],
888  'dn' => $dn,
889  'icon' => $infos['icon'],
890  'type' => $infos['name']
891  ];
892  }
893 
894  $smarty = get_smarty();
895  $smarty->assign('objects', $objects);
896  return $smarty->fetch(get_template_path('simple-archive.tpl'));
897  }
898 
899  public function archiveConfirmed (array $action)
900  {
901  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->currentDns, 'Archiving');
902 
903  $success = 0;
904  foreach ($this->currentDns as $dn) {
905  $entry = $this->listing->getEntry($dn);
906 
907  $errors = archivedObject::archiveObject($entry->type, $dn);
908  if (empty($errors)) {
909  $success++;
910  } else {
911  msg_dialog::displayChecks($errors);
912  }
914  }
915 
916  if ($success > 0) {
918  _('Archive success'),
919  htmlescape(sprintf(_('%d entries were successfully archived'), $success)),
920  INFO_DIALOG
921  );
922  }
923 
924  $this->currentDns = [];
925  }
926 
934  function editEntry (array $action)
935  {
936  global $ui;
937 
938  // Do not create a new tabObject while there is already one opened,
939  // the user may have just pressed F5 to reload the page.
940  if (is_object($this->tabObject)) {
941  return;
942  }
943 
944  $target = array_pop($action['targets']);
945 
946  $entry = $this->listing->getEntry($target);
947  if ($entry === NULL) {
948  trigger_error('Could not find ' . $target . ', open canceled');
949  return;
950  }
951 
952  // Get the dn of the object and create lock
953  $this->currentDn = $target;
954  if ($locks = Lock::get($this->currentDn, TRUE)) {
955  return Lock::genLockedMessage($locks, TRUE);
956  }
957  Lock::add($this->currentDn);
958 
959  // Open object
960  $this->openTabObject(objects::open($this->currentDn, $entry->getTemplatedType()));
961  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->currentDn, 'Edit entry initiated');
962  if (isset($action['subaction'])
963  && ($this->handleSubAction($action) === FALSE)) {
964  trigger_error('Was not able to handle subaction: ' . $action['subaction']);
965  }
966  }
967 
972  function cancelEdit ()
973  {
974  if (($this->tabObject instanceof simpleTabs) && ($this->dialogObject instanceof templateDialog)) {
975  $this->handleTemplateApply(TRUE);
976  return;
977  }
978  $this->removeLocks();
979  $this->closeDialogs();
980  }
981 
987  function saveChanges ()
988  {
989  if ($this->tabObject instanceof simpleTabs) {
990  $this->tabObject->readPost();
991  $this->tabObject->update();
992  if ($this->dialogObject instanceof templateDialog) {
993  $this->handleTemplateApply();
994  } else {
995  $msgs = $this->tabObject->save();
996  if (count($msgs)) {
997  msg_dialog::displayChecks($msgs);
998  } else {
999  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->currentDns, 'Entry saved');
1000  $this->removeLocks();
1001  $this->closeDialogs();
1002  }
1003  }
1004  }
1005  }
1006 
1010  function applyChanges ()
1011  {
1012  if ($this->tabObject instanceof simpleTabs) {
1013  $this->tabObject->readPost();
1014  $this->tabObject->update();
1015  $msgs = $this->tabObject->save();
1016  if (count($msgs)) {
1017  msg_dialog::displayChecks($msgs);
1018  } else {
1019  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->currentDns, 'Modifications applied');
1020  $this->tabObject->re_init();
1021  /* Avoid applying the POST a second time */
1022  $_POST = [];
1023  }
1024  }
1025  }
1026 
1030  function removeRequested (array $action)
1031  {
1032  global $ui;
1033  $disallowed = [];
1034  $this->currentDns = [];
1035 
1036  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $action['targets'], 'Entry deletion requested');
1037 
1038  // Check permissons for each target
1039  foreach ($action['targets'] as $dn) {
1040  $entry = $this->listing->getEntry($dn);
1041  try {
1042  if ($entry->checkAcl('d')) {
1043  $this->currentDns[] = $dn;
1044  } else {
1045  $disallowed[] = $dn;
1046  }
1047  } catch (NonExistingObjectTypeException $e) {
1048  trigger_error('Unknown object type received :' . $e->getMessage());
1049  }
1050  }
1051  if (count($disallowed)) {
1052  $error = new FusionDirectoryPermissionError(msgPool::permDelete($disallowed));
1053  $error->display();
1054  }
1055 
1056  // We've at least one entry to delete.
1057  if (count($this->currentDns)) {
1058  // Check locks
1059  if ($locks = Lock::get($this->currentDns)) {
1060  return Lock::genLockedMessage($locks, FALSE, _('Delete anyway'));
1061  }
1062 
1063  // Add locks
1064  Lock::add($this->currentDns);
1065 
1066  $objects = [];
1067  foreach ($this->currentDns as $dn) {
1068  $entry = $this->listing->getEntry($dn);
1069  $infos = objects::infos($entry->getTemplatedType());
1070  if ($entry->isTemplate()) {
1071  $infos['nameAttr'] = 'cn';
1072  }
1073  $objects[] = [
1074  'name' => $entry[$infos['nameAttr']][0],
1075  'dn' => $dn,
1076  'icon' => $infos['icon'],
1077  'type' => $infos['name']
1078  ];
1079  }
1080 
1081  return $this->removeConfirmationDialog($objects);
1082  }
1083  }
1084 
1087  protected function removeConfirmationDialog (array $objects)
1088  {
1089  $smarty = get_smarty();
1090  $smarty->assign('objects', $objects);
1091  $smarty->assign('multiple', TRUE);
1092  return $smarty->fetch(get_template_path('simple-remove.tpl'));
1093  }
1094 
1098  function removeConfirmed (array $action)
1099  {
1100  global $ui;
1101  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->currentDns, 'Entry deletion confirmed');
1102 
1103  $snapshotHandler = new SnapshotHandler();
1104  foreach ($this->currentDns as $dn) {
1105  $entry = $this->listing->getEntry($dn);
1106  if (empty($entry)) {
1107  continue;
1108  }
1109  if ($entry->checkAcl('d')) {
1110  // Delete the object
1111  $this->currentDn = $dn;
1112  $this->openTabObject(objects::open($this->currentDn, $entry->getTemplatedType()));
1113  $errors = $this->tabObject->delete();
1114  msg_dialog::displayChecks($errors);
1115 
1116  // Remove the lock for the current object.
1117  Lock::deleteByObject($this->currentDn);
1118 
1119  // Remove related snapshots
1120  $dnSnapshotsList = $snapshotHandler->getSnapshots($this->currentDn, TRUE);
1121  foreach ($dnSnapshotsList as $snap) {
1122  $snapshotHandler->removeSnapshot($snap['dn']);
1123  }
1124  } else {
1126  $error->display();
1127  logging::log('security', 'management/' . get_class($this), $dn, [], 'Tried to trick deletion.');
1128  }
1129  }
1130 
1131  // Cleanup
1132  $this->removeLocks();
1133  $this->closeDialogs();
1134  }
1135 
1136  function configureDialog (array $action)
1137  {
1138  if (!$this->skipConfiguration) {
1139  $this->dialogObject = new ManagementConfigurationDialog($this);
1140  }
1141  }
1142 
1146  function copyPasteHandler (array $action = ['action' => ''])
1147  {
1148  global $ui;
1149 
1150  // Exit if copy&paste handler is disabled.
1151  if (!is_object($this->cpHandler)) {
1152  return FALSE;
1153  }
1154 
1155  // Save user input
1156  $this->cpHandler->readPost();
1157 
1158  // Add entries to queue
1159  if (($action['action'] == 'copy') || ($action['action'] == 'cut')) {
1160  $this->cpHandler->cleanup_queue();
1161  foreach ($action['targets'] as $dn) {
1162  $entry = $this->listing->getEntry($dn);
1163  if (($action['action'] == 'copy') && $entry->checkAcl('r')) {
1164  $this->cpHandler->add_to_queue($dn, 'copy', $entry->getTemplatedType());
1165  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Entry copied!');
1166  }
1167  if (($action['action'] == 'cut') && $entry->checkAcl('rd')) {
1168  $this->cpHandler->add_to_queue($dn, 'cut', $entry->getTemplatedType());
1169  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Entry cut!');
1170  }
1171  }
1172  }
1173 
1174  // Initiate pasting
1175  if ($action['action'] == 'paste') {
1176  $this->cpPastingStarted = TRUE;
1177  }
1178 
1179  // Display any c&p dialogs, eg. object modifications required before pasting.
1180  if ($this->cpPastingStarted && $this->cpHandler->entries_queued()) {
1181  $this->cpHandler->update();
1182  $data = $this->cpHandler->render();
1183  if (!empty($data)) {
1184  return $data;
1185  }
1186  }
1187 
1188  // Automatically disable pasting process since there is no entry left to paste.
1189  if (!$this->cpHandler->entries_queued()) {
1190  $this->cpPastingStarted = FALSE;
1191  $this->cpHandler->resetPaste();
1192  }
1193 
1194  return '';
1195  }
1196 
1200  function createSnapshotDialog (array $action)
1201  {
1202  global $config, $ui;
1203  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $action['targets'], 'Snapshot creation initiated!');
1204 
1205  $this->currentDn = array_pop($action['targets']);
1206  if (empty($this->currentDn)) {
1207  return;
1208  }
1209  $entry = $this->listing->getEntry($this->currentDn);
1210  if ($entry->snapshotCreationAllowed()) {
1211  $this->dialogObject = new SnapshotCreateDialog($this->currentDn, $this, '');
1212  } else {
1213  $error = new FusionDirectoryError(
1214  htmlescape(sprintf(
1215  _('You are not allowed to create a snapshot for %s.'),
1216  $this->currentDn
1217  ))
1218  );
1219  $error->display();
1220  }
1221  }
1222 
1227  function restoreSnapshotDialog (array $action)
1228  {
1229  global $config, $ui;
1230 
1231  if (empty($action['targets'])) {
1232  // No target, open the restore removed object dialog.
1233  $this->currentDn = $this->listing->getBase();
1234  $aclCategories = $this->listAclCategories();
1235  } else {
1236  // Display the restore points for a given object.
1237  $this->currentDn = $action['targets'][0];
1238  if (empty($this->currentDn)) {
1239  return;
1240  }
1241  $aclCategories = [objects::infos($this->listing->getEntry($this->currentDn)->getTemplatedType())['aclCategory']];
1242  }
1243 
1244  if ($ui->allow_snapshot_restore($this->currentDn, $aclCategories, empty($action['targets']))) {
1245  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->currentDn, 'Snapshot restoring initiated!');
1246  $this->dialogObject = new SnapshotRestoreDialog($this->currentDn, $this, empty($action['targets']), $aclCategories);
1247  } else {
1248  $error = new FusionDirectoryError(
1249  htmlescape(sprintf(
1250  _('You are not allowed to restore a snapshot for %s.'),
1251  $this->currentDn
1252  ))
1253  );
1254  $error->display();
1255  }
1256  }
1257 
1258 
1259  function export (array $action)
1260  {
1261  if (!isset($this->exporters[$action['action']])) {
1262  trigger_error('Unknown exporter ' . $action['action']);
1263  return;
1264  }
1265  $exporter = $this->exporters[$action['action']];
1266  $file = $exporter['class']::export($this->listing);
1267  send_binary_content($file, $exporter['filename'], $exporter['mime']);
1268  }
1269 
1270  /* End of action handlers */
1271 
1272  /* Methods related to Snapshots */
1273 
1274  function getSnapshotBases (): array
1275  {
1276  $bases = [];
1277  foreach ($this->objectTypes as $type) {
1278  $infos = objects::infos($type);
1279  $bases[] = $infos['ou'] . $this->listing->getBase();
1280  }
1281 
1282  // No bases specified? Try base
1283  if (!count($bases)) {
1284  $bases[] = $this->listing->getBase();
1285  }
1286 
1287  return array_unique($bases);
1288  }
1289 
1293  function getAllDeletedSnapshots (): array
1294  {
1295  $bases = $this->getSnapshotBases();
1296  $tmp = [];
1297  foreach ($bases as $base) {
1298  $tmp = array_merge($tmp, $this->snapHandler->getAllDeletedSnapshots($base));
1299  }
1300  return $tmp;
1301  }
1302 
1303  /*
1304  * \brief Return available snapshots for the given base
1305  *
1306  * \param string $dn The DN
1307  */
1308  function getAvailableSnapsShots (string $dn): array
1309  {
1310  return $this->snapHandler->getAvailableSnapsShots($dn);
1311  }
1312 
1313  /*
1314  * \brief Whether snapshot restore action should be enabled for an entry
1315  */
1316  function enableSnapshotRestore ($action, ListingEntry $entry = NULL): bool
1317  {
1318  if ($entry !== NULL) {
1319  /* For entries */
1320  return $this->snapHandler->hasSnapshots($entry->dn);
1321  } else {
1322  /* For action menu */
1323  return $this->snapHandler->hasDeletedSnapshots($this->getSnapshotBases());
1324  }
1325  }
1326 
1331  function createSnapshot (string $dn, string $description, string $snapshotSource = 'FD')
1332  {
1333  global $ui;
1334 
1335  if (empty($dn) || ($this->currentDn !== $dn)) {
1336  trigger_error('There was a problem with the snapshot workflow');
1337  return;
1338  }
1339  $entry = $this->listing->getEntry($dn);
1340  if ($entry->snapshotCreationAllowed()) {
1341  $this->snapHandler->createSnapshot($dn, $description, $entry->type, $snapshotSource);
1342  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snapshot created!');
1343  } else {
1344  $error = new FusionDirectoryPermissionError(htmlescape(sprintf(_('You are not allowed to restore a snapshot for %s.'), $dn)));
1345  $error->display();
1346  }
1347  }
1348 
1354  function restoreSnapshot (string $dn)
1355  {
1356  global $ui;
1357  if (!empty($dn) && $ui->allow_snapshot_restore($dn, $this->dialogObject->aclCategory, $this->dialogObject->global)) {
1358  $dn = $this->snapHandler->restoreSnapshot($dn);
1359  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snapshot restored');
1360  $this->closeDialogs();
1361  if ($dn !== FALSE) {
1362  $this->listing->focusDn($dn);
1363  $entry = $this->listing->getEntry($dn);
1364  $this->currentDn = $dn;
1365  Lock::add($this->currentDn);
1366 
1367  // Open object
1368  $this->openTabObject(objects::open($this->currentDn, $entry->getTemplatedType()));
1369  $this->saveChanges();
1370  }
1371  } else {
1372  $error = new FusionDirectoryPermissionError(htmlescape(sprintf(_('You are not allowed to restore a snapshot for %s.'), $dn)));
1373  $error->display();
1374  }
1375  }
1376 
1382  function removeSnapshot (string $dn)
1383  {
1384  global $ui;
1385  if (!empty($dn) && $ui->allow_snapshot_delete($dn, $this->dialogObject->aclCategory)) {
1386  $this->snapHandler->removeSnapshot($dn);
1387  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snapshot deleted');
1388  } else {
1389  $error = new FusionDirectoryPermissionError(htmlescape(sprintf(_('You are not allowed to delete a snapshot for %s.'), $dn)));
1390  $error->display();
1391  }
1392  }
1393 
1394  static function mainInc ($classname = NULL, $objectTypes = FALSE)
1395  {
1396  global $remove_lock, $cleanup, $display;
1397 
1398  if ($classname === NULL) {
1399  $classname = get_called_class();
1400  }
1401 
1402  /* Remove locks */
1403  if ($remove_lock && session::is_set($classname)) {
1404  $macl = session::get($classname);
1405  $macl->removeLocks();
1406  }
1407 
1408  if ($cleanup) {
1409  /* Clean up */
1410  session::un_set($classname);
1411  } else {
1412  if (!session::is_set($classname) || (isset($_GET['reset']) && $_GET['reset'] == 1)) {
1413  /* Create the object if missing or reset requested */
1414  $managementObject = new $classname($objectTypes);
1415  } else {
1416  /* Retrieve the object from session */
1417  $managementObject = session::get($classname);
1418  }
1419  /* Execute and display */
1420  $managementObject->readPost();
1421  $managementObject->update();
1422  $display = $managementObject->render();
1423 
1424  /* Store the object in the session */
1425  session::set($classname, $managementObject);
1426  }
1427  }
1428 }
archiveRequested(array $action)
Queue selected objects to be archived. Checks Locks and ask for confirmation.
Snapshot creation dialog.
htmlescape(string $str)
Escape string for HTML output.
Definition: php_setup.inc:32
showTabFooter()
Whether footer buttons should appear.
getHeader()
Sets smarty headline and returns the plugin header which is displayed whenever a tab object is opened...
copyPasteHandler(array $action=['action'=> ''])
This method is used to queue and process copy&paste actions. Allows to copy, cut and paste mutliple e...
get_template_path($filename='', $plugin=FALSE, $path='')
Return themed path for specified base file.
Definition: functions.inc:174
static get($name)
Accessor of a session var.
This interface should be implemented by all dialog classes in FusionDirectory.
send_binary_content($data, $name, $type="application/octet-stream")
Initialize a file download with given content, name and data type.
Definition: functions.inc:1363
render()
Render the dialog and returns the HTML code.
This class contains all function to manage tabs classes.
restoreSnapshotDialog(array $action)
Displays the "Restore snapshot dialog" for a given target. If no target is specified, open the restore removed object dialog.
static genLockedMessage(array $locks, bool $allowReadonly=FALSE, string $action=NULL)
Generate a lock message.
Definition: class_Lock.inc:331
This class contains all function to copy and paste.
readPost()
Interpret POST content.
static get($objects, bool $allow_readonly=FALSE)
Get locks for objects.
Definition: class_Lock.inc:211
static log(string $action, string $objecttype, string $object, array $changes=[], string $result='')
logging method
Action base class.
static display($title, string $message, int $type=INFO_DIALOG, array $trace=[])
Display a message dialog.
static set($name, $value)
Set a value in a session.
newEntry(array $action)
This method intiates the object creation.
Action which unfold a submenu.
registerAction(Action $action)
Register an action to show in the action menu and/or the action column.
removeSnapshot(string $dn)
Delete a snapshot.
static add($object, string $user=NULL)
Add a lock for object(s)
Definition: class_Lock.inc:47
Snapshot restoration dialog.
& get_smarty()
Get global smarty object.
Definition: functions.inc:324
removeConfirmed(array $action)
Deletion was confirmed, delete the objects queued. Checks ACLs just in case.
This class handles the entries list for a management instance.
removeConfirmationDialog(array $objects)
Display confirmation dialog.
This class handles the management filter box.
static open(string $dn, string $type)
Create the tab object for the given dn.
static debug(int $level, int $line, string $function, string $file, $data, string $info='')
Debug output method.
cancelEdit()
Editing an object was canceled. Close dialogs/tabs and remove locks.
editEntry(array $action)
This method opens an existing object to be edited.
Parent class for all exceptions thrown in FusionDirectory.
static permDelete($name='')
Display that we have no permission to delete an object.
createSnapshot(string $dn, string $description, string $snapshotSource='FD')
Creates a new snapshot entry If source arg is not set, default to &#39;FD&#39;.
static deleteByObject($object)
Remove a lock for object(s)
Definition: class_Lock.inc:135
Action hidden from both column and menu.
removeRequested(array $action)
Queue selected objects to be removed. Checks ACLs, Locks and ask for confirmation.
static un_set($name)
Unset a session.
saveChanges()
Save object modifications and closes dialogs (returns to object listing).
createSnapshotDialog(array $action)
Opens the snapshot creation dialog for the given target.
This class contains all the function needed to handle the snapshot functionality. ...
applyChanges()
Save object modifications and keep dialogs opened.
execute()
Execute this plugin Handle actions/events, locking, snapshots, dialogs, tabs,...
update()
Update state and return FALSE if the dialog was closed.
handleAction(array $action)
Calls the registered method for a given action/event.
Parent class for all errors in FusionDirectory.
removeLocks()
Removes ldap object locks created by this class. Whenever an object is edited, we create locks to avo...
Template dialog handling.
detectPostActions()
Detects actions/events send by the ui and the corresponding targets.
Management base class.
restoreSnapshot(string $dn)
Restores a snapshot object.
getAllDeletedSnapshots()
Get all deleted snapshots.
getTabFooter()
Generates the footer which is used whenever a tab object is displayed.
static is_set($name)
Check if the name of the session is set.
closeDialogs()
This method closes dialogs and cleans up the cached object info and the ui.
class_available($name)
Checks if a class is available.
Definition: functions.inc:92