FusionDirectory
class_simplePlugin.inc
Go to the documentation of this file.
1 <?php
2 /*
3  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
4 
5  Copyright (C) 2012-2019 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 class simplePlugin implements SimpleTab
32 {
35 
40  public $attributesAccess = [];
41  // Thisb bolean allows children class to get readOnly automatically via static state or class-level state.
42  private static $user_locked = FALSE;
43 
55  public $is_account = FALSE;
56  public $initially_was_account = FALSE;
57  protected $ignore_account = FALSE;
58 
59  public $acl_category = '';
60 
62  public $dn = '';
63 
65  public $orig_dn = '';
66 
77  public $parent = NULL;
78 
86  public $is_template = FALSE;
87 
93  public $attrs = [];
94 
96  protected $objectclasses = [];
97 
99  protected $saved_attributes = []; // Note : This is overwritten during post_save logic
100  // Requiring therefore a save to threat this during logging mechanism.
101  protected $beforeLdapChangeAttributes = [];
102 
104  protected $displayHeader = FALSE;
105 
107  protected $mainTab = FALSE;
108 
109  protected $header = "";
110 
111  protected $templatePath;
112 
113  protected $dialog = FALSE;
114 
117  protected $needEditMode = FALSE;
118 
120  protected $preInitAttributes = [];
121 
124  protected $inheritance = FALSE;
125  protected $member_of_group = FALSE;
126  protected $editing_group = NULL;
127  protected $group_attrs = [];
128 
130  protected $read_only = FALSE;
131 
133  protected $ldap_error;
134 
142  protected $entryCSN = '';
143 
144  private $hadSubobjects = FALSE;
145 
155  function __construct (string $dn = NULL, $object = NULL, $parent = NULL, bool $mainTab = FALSE, array $attributesInfo = NULL)
156  {
157  global $config;
158 
159  $this->dn = $dn;
160  $this->parent = $parent;
161  $this->mainTab = $mainTab;
162 
163  // This class-level state allows children to get readOnly automatically.
164  if (self::$user_locked) {
165  $this->read_only = TRUE;
166  }
167 
168  try {
169  $plInfo = pluglist::pluginInfos(get_class($this));
170  } catch (UnknownClassException $e) {
171  /* May happen in special cases like setup */
172  $plInfo = [];
173  }
174 
175  if (empty($this->objectclasses) && isset($plInfo['plObjectClass'])) {
176  $this->objectclasses = $plInfo['plObjectClass'];
177  }
178 
179  if ($attributesInfo === NULL) {
180  $attributesInfo = $this->getAttributesInfo();
181  }
182  if (!$this->displayHeader) {
183  // If we don't display the header to activate/deactive the plugin, that means it's always activated
184  $this->ignore_account = TRUE;
185  }
186 
187  $this->attributesInfo = [];
188  foreach ($attributesInfo as $section => $sectionInfo) {
189  $attrs = [];
190  foreach ($sectionInfo['attrs'] as $attr) {
191  $name = $attr->getLdapName();
192  if (isset($attrs[$name])) {
193  // We check that there is no duplicated attribute name
194  trigger_error("Duplicated attribute LDAP name '$name' in a simplePlugin subclass");
195  }
196  // We make so that attribute have their LDAP name as key
197  // That allow the plugin to use $this->attributesInfo[$sectionName]['attrs'][$myLdapName] to retreive the attribute info.
198  $attrs[$name] = $attr;
199  }
200  $sectionInfo['attrs'] = $attrs;
201  $this->attributesInfo[$section] = $sectionInfo;
202  foreach ($this->attributesInfo[$section]['attrs'] as $name => $attr) {
203  if (isset($this->attributesAccess[$name])) {
204  // We check that there is no duplicated attribute name
205  trigger_error("Duplicated attribute LDAP name '$name' in a simplePlugin subclass");
206  }
207  $this->attributesAccess[$name] =& $this->attributesInfo[$section]['attrs'][$name];
208  unset($this->$name);
209  }
210  }
211 
212  /* Ensure that we've a valid acl_category set */
213  if (empty($this->acl_category) && isset($plInfo['plCategory'])) {
214  $c = key($plInfo['plCategory']);
215  if (is_numeric($c)) {
216  $c = $plInfo['plCategory'][$c];
217  }
218  $this->acl_category = $c . '/';
219  }
220 
221  /* Check if this entry was opened in read only mode */
222  if (($this->dn != 'new') &&
223  isset($_POST['open_readonly']) &&
224  session::is_set('LOCK_CACHE')
225  ) {
226  $cache = session::get('LOCK_CACHE');
227  if (isset($cache['READ_ONLY'][$this->dn])) {
228  $this->read_only = TRUE;
229  }
230  }
231 
232  /* Load LDAP data */
233  if (($this->dn != 'new' && $this->dn !== NULL) || ($object !== NULL)) {
234  /* Load data to 'attrs' */
235  if ($object !== NULL) {
236  /* From object */
237  $this->attrs = $object->attrs;
238  if (isset($object->is_template)) {
239  $this->setTemplate($object->is_template);
240  }
241  } else {
242  /* From LDAP */
243  $ldap = $config->get_ldap_link();
244  $ldap->cat($this->dn);
245  $this->attrs = $ldap->fetch(TRUE);
246  if (empty($this->attrs)) {
247  throw new NonExistingLdapNodeException($this->dn);
248  }
249  if ($this->mainTab) {
250  $this->entryCSN = getEntryCSN($this->dn);
251  /* Make sure that initially_was_account is TRUE if we loaded an LDAP node,
252  * even if it’s missing an objectClass */
253  $this->is_account = TRUE;
254  }
255  }
256 
257  /* Set the template flag according to the existence of objectClass fdTemplate */
258  if (isset($this->attrs['objectClass']) && in_array_ics('fdTemplate', $this->attrs['objectClass'])) {
259  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, 'found', 'Template check');
260  $this->setTemplate(TRUE);
261  $this->templateLoadAttrs($this->attrs);
262  }
263 
264  /* Is Account? */
265  if ($this->is_this_account($this->attrs)) {
266  $this->is_account = TRUE;
267  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, get_class($this), 'Tab active');
268  }
269  }
270 
271  if (is_array($this->inheritance)) {
272  /* Check group membership */
273  $ldap = $config->get_ldap_link();
274  $ldap->cd($config->current['BASE']);
275  foreach ($this->inheritance as $oc => $at) {
276  if ($this->mainTab) {
277  $filter = '(&(objectClass=' . $oc . ')(' . $at . '=' . ldap_escape_f($this->dn) . '))';
278  } else {
279  $filter = '(&(objectClass=' . $oc . ')' . static::getLdapFilter() . '(' . $at . '=' . ldap_escape_f($this->dn) . '))';
280  }
281  $ldap->search($filter, $this->attributes);
282  if ($ldap->count() == 1) {
283  $this->member_of_group = TRUE;
284  $attrs = $ldap->fetch();
285  $this->group_attrs = $attrs;
286  break;
287  }
288  }
289  }
290 
291  /* Save initial account state */
292  $this->initially_was_account = $this->is_account;
293 
294  $this->loadAttributes();
295 
296  $this->prepareSavedAttributes();
297 
298  $this->orig_dn = $dn;
299 
300  if ($this->mainTab) {
301  $this->is_account = TRUE;
302  }
303 
304  if (!isset($this->templatePath)) {
305  $this->templatePath = get_template_path('simpleplugin.tpl');
306  }
307  }
308 
309 
310  public static function setUserLocked (bool $locked): void
311  {
312  self::$user_locked = $locked;
313  }
314 
315  protected function loadAttributes ()
316  {
317  // We load attributes values
318  // First the one flagged as preInit
319  foreach ($this->preInitAttributes as $attr) {
320  $this->attributesAccess[$attr]->setParent($this);
321  $this->attributesAccess[$attr]->loadValue($this->attrs);
322  }
323  // Then the others
324  foreach ($this->attributesInfo as &$sectionInfo) {
325  foreach ($sectionInfo['attrs'] as $name => &$attr) {
326  if (in_array($name, $this->preInitAttributes)) {
327  /* skip the preInit ones */
328  continue;
329  }
330  $attr->setParent($this);
331  $attr->loadValue($this->attrs);
332  }
333  unset($attr);
334  }
335  unset($sectionInfo);
336  }
337 
338  function is_this_account ($attrs)
339  {
340  $result = static::isAccount($attrs);
341  if ($result === NULL) {
342  if (!empty($this->objectclasses)) {
343  trigger_error('Deprecated fallback was used for ' . get_called_class() . '::is_this_account');
344  }
345  $found = TRUE;
346  foreach ($this->objectclasses as $obj) {
347  if (preg_match('/^top$/i', $obj)) {
348  continue;
349  }
350  if (!isset($attrs['objectClass']) || !in_array_ics($obj, $attrs['objectClass'])) {
351  $found = FALSE;
352  break;
353  }
354  }
355  return $found;
356  }
357  return $result;
358  }
359 
360  function setTemplate (bool $bool)
361  {
362  $this->is_template = $bool;
363  if ($this->is_template && $this->mainTab) {
364  /* Unshift special section for template infos */
365  $this->attributesInfo = array_merge(
366  [
367  '_template' => [
368  'class' => ['fullwidth'],
369  'name' => _('Template settings'),
370  'attrs' => [
371  '_template_cn' => new StringAttribute(
372  _('Template name'), _('This is the name of the template'),
373  '_template_cn', TRUE,
374  '', 'template_cn'
375  )
376  ]
377  ],
378  '_template_dummy' => [
379  'class' => ['invisible'],
380  'name' => '_template_dummy',
381  'attrs' => []
382  ]
383  ],
384  $this->attributesInfo
385  );
386  $this->attributesAccess['_template_cn'] =& $this->attributesInfo['_template']['attrs']['_template_cn'];
387  $this->attributesAccess['_template_cn']->setInLdap(FALSE);
388  $this->attributesAccess['_template_cn']->setValue($this->_template_cn);
389  $this->attributesAccess['_template_cn']->setParent($this);
390  unset($this->_template_cn);
391  }
392  }
393 
394  protected function templateLoadAttrs (array $template_attrs)
395  {
396  if ($this->mainTab) {
397  $this->_template_cn = $template_attrs['cn'][0];
398  }
399  $this->attrs = templateHandling::fieldsFromLDAP($template_attrs);
400  }
401 
402  protected function templateSaveAttrs ()
403  {
404  global $config;
405  $ldap = $config->get_ldap_link();
406  $ldap->cat($this->dn);
407  $template_attrs = $ldap->fetch(TRUE);
408  if (!$template_attrs) {
409  if (!$this->mainTab) {
410  trigger_error('It seems main tab has not been saved.');
411  }
412  $template_attrs = [
413  'objectClass' => ['fdTemplate'],
414  'fdTemplateField' => []
415  ];
416  }
417  $template_attrs = templateHandling::fieldsToLDAP($template_attrs, $this->attrs);
418  if ($this->mainTab) {
419  $template_attrs['cn'] = $this->_template_cn;
420  }
421  return $template_attrs;
422  }
423 
427  {
428  trigger_error('Deprecated');
429  return static::getLdapFilter();
430  }
431 
437  public function __get ($name)
438  {
439  if ($name == 'attributes') {
440  $plugin = $this;
441  return array_filter(array_keys($this->attributesAccess),
442  function ($a) use ($plugin) {
443  return $plugin->attributesAccess[$a]->isInLdap();
444  }
445  );
446  } elseif (isset($this->attributesAccess[$name])) {
447  return $this->attributesAccess[$name]->getValue();
448  } else {
449  /* Calling default behaviour */
450  return $this->$name;
451  }
452  }
453 
458  public function __set ($name, $value)
459  {
460  if ($name == 'attributes') {
461  trigger_error('Tried to set obsolete attribute "attributes" (it is now dynamic)');
462  } elseif (isset($this->attributesAccess[$name])) {
463  $this->attributesAccess[$name]->setValue($value);
464  } else {
465  /* Calling default behaviour */
466  $this->$name = $value;
467  }
468  }
469 
474  public function __isset ($name)
475  {
476  if ($name == 'attributes') {
477  return TRUE;
478  }
479  return isset($this->attributesAccess[$name]);
480  }
481 
484  public function compute_dn (): string
485  {
486  global $config;
487  if (!$this->mainTab) {
488  throw new FatalError(htmlescape(_('Only main tab can compute dn')));
489  }
490  if (!isset($this->parent) || !($this->parent instanceof simpleTabs)) {
491  throw new FatalError(
492  htmlescape(sprintf(
493  _('Could not compute dn: no parent tab class for "%s"'),
494  get_class($this)
495  ))
496  );
497  }
498  $infos = $this->parent->objectInfos();
499  if ($infos === FALSE) {
500  throw new FatalError(
501  htmlescape(sprintf(
502  _('Could not compute dn: could not find objectType info from tab class "%s"'),
503  get_class($this->parent)
504  ))
505  );
506  }
507  $attr = $infos['mainAttr'];
508  $ou = $infos['ou'];
509  if (isset($this->base)) {
510  $base = $this->base;
511  } else {
512  $base = $config->current['BASE'];
513  }
514  if ($this->is_template) {
515  return 'cn=' . ldap_escape_dn($this->_template_cn) . ',ou=templates,' . $ou . $base;
516  }
517  return $attr . '=' . ldap_escape_dn($this->attributesAccess[$attr]->computeLdapValue()) . ',' . $ou . $base;
518  }
519 
520  protected function addAttribute (string $section, \FusionDirectory\Core\SimplePlugin\Attribute $attr)
521  {
522  $name = $attr->getLdapName();
523  $this->attributesInfo[$section]['attrs'][$name] = $attr;
524  $this->attributesAccess[$name] =& $this->attributesInfo[$section]['attrs'][$name];
525  $this->attributesAccess[$name]->setParent($this);
526  unset($this->$name);
527  }
528 
529  protected function removeAttribute (string $section, string $id)
530  {
531  unset($this->attributesInfo[$section]['attrs'][$id]);
532  unset($this->attributesAccess[$id]);
533  }
534 
544  function get_allowed_bases (): array
545  {
546  global $config;
547  $deps = [];
548 
549  /* Is this a new object ? Or just an edited existing object */
550  $departmentTree = $config->getDepartmentTree();
551  foreach ($departmentTree as $dn => $name) {
552  if (
553  (!$this->initially_was_account && $this->acl_is_createable($dn)) ||
554  ($this->initially_was_account && $this->acl_is_moveable($dn))
555  ) {
556  $deps[$dn] = $name;
557  }
558  }
559 
560  /* Add current base */
561  if (isset($this->base) && isset($departmentTree[$this->base])) {
562  $deps[$this->base] = $departmentTree[$this->base];
563  } elseif (strtolower($this->dn) != strtolower($config->current['BASE'])) {
564  trigger_error('Cannot return list of departments, no default base found in class ' . get_class($this) . '. (base is "' . $this->base . '")');
565  }
566  return $deps;
567  }
568 
574  function set_acl_category (string $category)
575  {
576  $this->acl_category = "$category/";
577  }
578 
588  function move (string $src_dn, string $dst_dn)
589  {
590  global $config, $ui;
591 
592  /* Do not move if only case has changed */
593  if (strtolower($src_dn) == strtolower($dst_dn)) {
594  return TRUE;
595  }
596 
597  /* Try to move with ldap routines */
598  $ldap = $config->get_ldap_link();
599  $ldap->cd($config->current['BASE']);
600  try {
601  $ldap->create_missing_trees(preg_replace('/^[^,]+,/', '', $dst_dn));
602  } catch (FusionDirectoryError $error) {
603  $error->display();
604  }
605  if (!$ldap->rename_dn($src_dn, $dst_dn)) {
606  logging::log('error', 'ldap', "FROM: $src_dn -- TO: $dst_dn", [], 'Ldap Protocol v3 implementation error, ldap_rename failed: ' . $ldap->get_error());
607  logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, "Rename failed FROM: $src_dn -- TO: $dst_dn",
608  'Ldap Protocol v3 implementation error. Error:' . $ldap->get_error());
609  return $ldap->get_error();
610  }
611 
612  /* Update userinfo if necessary */
613  if (preg_match('/' . preg_quote($src_dn, '/') . '$/i', $ui->dn)) {
614  $ui->dn = preg_replace('/' . preg_quote($src_dn, '/') . '$/i', $dst_dn, $ui->dn);
615  }
616 
617  /* Check if departments were moved. If so, force the reload of $config departments cache */
618  $ldap->cd($dst_dn);
619  $ldap->search('(objectClass=gosaDepartment)', ['dn']);
620  if ($ldap->count()) {
621  $config->resetDepartmentCache();
622  $ui->reset_acl_cache();
623  }
624 
625  $this->handleForeignKeys($src_dn, $dst_dn);
626  return TRUE;
627  }
628 
629  function getRequiredAttributes (): array
630  {
631  $tmp = [];
632  foreach ($this->attributesAccess as $attr) {
633  if ($attr->isRequired()) {
634  $tmp[] = $attr->getLdapName();
635  }
636  }
637  return $tmp;
638  }
639 
640  function editing_group ()
641  {
642  if ($this->editing_group == NULL) {
643  if (isset($this->parent)) {
644  $this->editing_group = (get_class($this->parent->getBaseObject()) == 'ogroup');
645  } else {
646  return NULL;
647  }
648  }
649  return $this->editing_group;
650  }
651 
653  function readOnly ()
654  {
655  return $this->read_only;
656  }
657 
658  function execute (): string
659  {
660  trigger_error('obsolete');
661  $this->update();
662  return $this->render();
663  }
664 
665  public function update (): bool
666  {
667  if (is_object($this->dialog)) {
668  $dialogState = $this->dialog->update();
669  if ($dialogState === FALSE) {
670  $this->closeDialog();
671  }
672  }
673 
674  return TRUE;
675  }
676 
679  public function render (): string
680  {
681  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->dn, 'render');
682 
683  /* Reset Lock message POST/GET check array, to prevent preg_match errors */
684  session::set('LOCK_VARS_TO_USE', []);
685  session::set('LOCK_VARS_USED_GET', []);
686  session::set('LOCK_VARS_USED_POST', []);
687  session::set('LOCK_VARS_USED_REQUEST', []);
688 
689  $this->displayPlugin = TRUE;
690  $this->header = '';
691 
692  if (is_object($this->dialog)) {
693  $this->header = $this->dialog->render();
694  $this->displayPlugin = FALSE;
695  return $this->header;
696  }
697 
698  if ($this->displayHeader) {
699  /* Show tab dialog headers */
700  if ($this->parent !== NULL) {
701  list($disabled, $buttonHtmlText, $htmlText) = $this->getDisplayHeaderInfos();
702  $this->header = $this->show_header(
703  $buttonHtmlText,
704  $htmlText,
705  $this->is_account,
706  $disabled,
707  get_class($this) . '_modify_state'
708  );
709  if (!$this->is_account) {
710  $this->displayPlugin = FALSE;
711  return $this->header . $this->inheritanceDisplay();
712  }
713  } elseif (!$this->is_account) {
714  $plInfo = pluglist::pluginInfos(get_class($this));
715  $this->header = '<img alt="' . htmlescape(_('Error')) . '" src="geticon.php?context=status&amp;icon=dialog-error&amp;size=16" align="middle"/>&nbsp;<b>' .
716  msgPool::noValidExtension($plInfo['plShortName']) . "</b>";
717  $this->displayPlugin = FALSE;
718  return $this->header . $this->inheritanceDisplay();
719  }
720  }
721 
722  $smarty = get_smarty();
723 
724  $this->renderAttributes(FALSE);
725  $smarty->assign("hiddenPostedInput", get_class($this) . "_posted");
726  if (isset($this->focusedField)) {
727  $smarty->assign("focusedField", $this->focusedField);
728  unset($this->focusedField);
729  } else {
730  $smarty->assign("focusedField", key($this->attributesAccess));
731  }
732 
733  return $this->header . $smarty->fetch($this->templatePath);
734  }
735 
736  public function getDisplayHeaderInfos (): array
737  {
738  $plInfo = pluglist::pluginInfos(get_class($this));
739  $disabled = $this->acl_skip_write();
740  if ($this->is_account) {
741  $depends = [];
742  if (isset($plInfo['plDepending'])) {
743  foreach ($plInfo['plDepending'] as $plugin) {
744  if (isset($this->parent->by_object[$plugin]) &&
745  $this->parent->by_object[$plugin]->is_account) {
746  $disabled = TRUE;
747  $dependPlInfos = pluglist::pluginInfos($plugin);
748  $depends[] = $dependPlInfos['plShortName'];
749  }
750  }
751  }
752  $buttonHtmlText = msgPool::removeFeaturesButton($plInfo['plShortName']);
753  $htmlText = msgPool::featuresEnabled($plInfo['plShortName'], $depends);
754  } else {
755  $depends = [];
756  $conflicts = [];
757  if (isset($plInfo['plDepends'])) {
758  foreach ($plInfo['plDepends'] as $plugin) {
759  if (isset($this->parent->by_object[$plugin]) &&
760  !$this->parent->by_object[$plugin]->is_account) {
761  $disabled = TRUE;
762  $dependPlInfos = pluglist::pluginInfos($plugin);
763  $depends[] = $dependPlInfos['plShortName'];
764  }
765  }
766  }
767  if (isset($plInfo['plConflicts'])) {
768  foreach ($plInfo['plConflicts'] as $plugin) {
769  if (isset($this->parent->by_object[$plugin]) &&
770  $this->parent->by_object[$plugin]->is_account) {
771  $disabled = TRUE;
772  $conflictPlInfos = pluglist::pluginInfos($plugin);
773  $conflicts[] = $conflictPlInfos['plShortName'];
774  }
775  }
776  }
777  $buttonHtmlText = msgPool::addFeaturesButton($plInfo['plShortName']);
778  $htmlText = msgPool::featuresDisabled($plInfo['plShortName'], $depends, $conflicts);
779  }
780  return [$disabled, $buttonHtmlText, $htmlText];
781  }
782 
796  function show_header (string $buttonHtmlText, string $htmlText, bool $plugin_enabled, bool $button_disabled = FALSE, string $name = 'modify_state'): string
797  {
798  if ($button_disabled || ((!$this->acl_is_createable() && !$plugin_enabled) || (!$this->acl_is_removeable() && $plugin_enabled))) {
799  $state = 'disabled="disabled"';
800  } else {
801  $state = '';
802  }
803  $display = '<div width="100%"><p><b>' . $htmlText . '</b><br/>' . "\n";
804  $display .= '<input type="submit" formnovalidate="formnovalidate" value="' . $buttonHtmlText . '" name="' . $name . '" ' . $state . '></p></div><hr class="separator"/>';
805 
806  return $display;
807  }
808 
812  public function isActive (): bool
813  {
814  return ($this->is_account || $this->ignore_account);
815  }
816 
820  public function isActivatable (): bool
821  {
822  return $this->displayHeader;
823  }
824 
829  function attrIsReadable ($attr): bool
830  {
831  if (!is_object($attr)) {
832  $attr = $this->attributesAccess[$attr];
833  }
834  if ($attr->getLdapName() == 'base') {
835  return TRUE;
836  }
837  if ($attr->getAcl() == 'noacl') {
838  return TRUE;
839  }
840  return $this->acl_is_readable($attr->getAcl());
841  }
842 
847  function attrIsWriteable ($attr): bool
848  {
849  if (!is_object($attr)) {
850  $attr = $this->attributesAccess[$attr];
851  }
852  if ($attr->getLdapName() == 'base') {
853  return (
854  !$this->acl_skip_write() &&
855  (!$this->initially_was_account || $this->acl_is_moveable() || $this->acl_is_removeable())
856  );
857  }
858  if ($attr->getAcl() == 'noacl') {
859  return FALSE;
860  }
861  return $this->acl_is_writeable($attr->getAcl(), $this->acl_skip_write());
862  }
863 
867  function getAclBase (bool $callParent = TRUE): string
868  {
869  global $config;
870 
871  if (($this->parent instanceof simpleTabs) && $callParent) {
872  return $this->parent->getAclBase();
873  }
874  if (isset($this->dn) && ($this->dn != 'new')) {
875  return $this->dn;
876  }
877  if (isset($this->base)) {
878  return 'new,' . $this->base;
879  }
880 
881  return $config->current['BASE'];
882  }
883 
884  function renderAttributes (bool $readOnly = FALSE)
885  {
886  global $ui;
887  $smarty = get_smarty();
888 
889  if ($this->is_template) {
890  $smarty->assign('template_cnACL', $ui->get_permissions($this->getAclBase(), $this->acl_category . 'template', 'template_cn', $this->acl_skip_write()));
891  }
892 
893  /* Handle rights to modify the base */
894  if (isset($this->attributesAccess['base'])) {
895  if ($this->attrIsWriteable('base')) {
896  $smarty->assign('baseACL', 'rw');
897  } else {
898  $smarty->assign('baseACL', 'r');
899  }
900  }
901 
902  $sections = [];
903  foreach ($this->attributesInfo as $section => $sectionInfo) {
904  $smarty->assign('section', $sectionInfo['name']);
905  $smarty->assign('sectionIcon', ($sectionInfo['icon'] ?? NULL));
906  $smarty->assign('sectionId', $section);
907  $sectionClasses = '';
908  if (isset($sectionInfo['class'])) {
909  $sectionClasses .= ' ' . join(' ', $sectionInfo['class']);
910  }
911  $attributes = [];
912  $readableSection = FALSE;
913  foreach ($sectionInfo['attrs'] as $attr) {
914  if ($attr->getAclInfo() !== FALSE) {
915  // We assign ACLs so that attributes can use them in their template code
916  $smarty->assign($attr->getAcl() . 'ACL', $this->aclGetPermissions($attr->getAcl(), NULL, $this->acl_skip_write()));
917  }
918  $readable = $this->attrIsReadable($attr);
919  $writable = $this->attrIsWriteable($attr);
920  if (!$readableSection && ($readable || $writable)) {
921  $readableSection = TRUE;
922  }
923  $attr->renderAttribute($attributes, $readOnly, $readable, $writable);
924  }
925  $smarty->assign('attributes', $attributes);
926  if (!$readableSection) {
927  $sectionClasses .= ' nonreadable';
928  }
929  $smarty->assign('sectionClasses', $sectionClasses);
930  // We fetch each section with the section template
931  if (isset($sectionInfo['template'])) {
932  $displaySection = $smarty->fetch($sectionInfo['template']);
933  } else {
934  $displaySection = $smarty->fetch(get_template_path('simpleplugin_section.tpl'));
935  }
936  $sections[$section] = $displaySection;
937  }
938  $smarty->assign("sections", $sections);
939  }
940 
941  function inheritanceDisplay (): string
942  {
943  if (!$this->member_of_group) {
944  return "";
945  }
946  $class = get_class($this);
947  $attrsWrapper = new stdClass();
948  $attrsWrapper->attrs = $this->group_attrs;
949  $group = new $class($this->group_attrs['dn'], $attrsWrapper, $this->parent, $this->mainTab);
950  $smarty = get_smarty();
951 
952  $group->renderAttributes(TRUE);
953  $smarty->assign("hiddenPostedInput", get_class($this) . "_posted");
954 
955  return "<h1>Inherited information:</h1><div></div>\n" . $smarty->fetch($this->templatePath);
956  }
957 
963  {
964  $this->dialog = $dialog;
965  }
966 
969  function closeDialog ()
970  {
971  $this->dialog = NULL;
972  }
973 
974  public function setNeedEditMode (bool $bool)
975  {
976  $this->needEditMode = $bool;
977  }
978 
979  protected function acl_skip_write (): bool
980  {
981  return ($this->needEditMode && !session::is_set('edit'));
982  }
983 
985  function acl_is_writeable ($attribute, bool $skipWrite = FALSE): bool
986  {
987  return (strpos($this->aclGetPermissions($attribute, NULL, $skipWrite), 'w') !== FALSE);
988  }
989 
995  function acl_is_readable ($attribute): bool
996  {
997  return (strpos($this->aclGetPermissions($attribute), 'r') !== FALSE);
998  }
999 
1005  function acl_is_createable (string $base = NULL): bool
1006  {
1007  return (strpos($this->aclGetPermissions('0', $base), 'c') !== FALSE);
1008  }
1009 
1015  function acl_is_removeable (string $base = NULL): bool
1016  {
1017  return (strpos($this->aclGetPermissions('0', $base), 'd') !== FALSE);
1018  }
1019 
1025  function acl_is_moveable (string $base = NULL): bool
1026  {
1027  return (strpos($this->aclGetPermissions('0', $base), 'm') !== FALSE);
1028  }
1029 
1031  function aclHasPermissions (): bool
1032  {
1033  global $config;
1034 
1035  return in_array(get_class($this), $config->data['CATEGORIES'][rtrim($this->acl_category, '/')]['classes']);
1036  }
1037 
1039  function aclGetPermissions ($attribute = '0', string $base = NULL, bool $skipWrite = FALSE): string
1040  {
1041  if (isset($this->parent) && isset($this->parent->ignoreAcls) && $this->parent->ignoreAcls) {
1042  return 'cdmr' . ($skipWrite ? '' : 'w');
1043  }
1044  $ui = get_userinfo();
1045  $skipWrite |= $this->readOnly();
1046  if ($base === NULL) {
1047  $base = $this->getAclBase();
1048  }
1049  return $ui->get_permissions($base, $this->acl_category . get_class($this), $attribute, $skipWrite);
1050  }
1051 
1054  function remove (bool $fulldelete = FALSE): array
1055  {
1056  if (!$this->initially_was_account) {
1057  return [];
1058  }
1059 
1060  if (!$fulldelete && !$this->acl_is_removeable()) {
1061  trigger_error('remove was called on a tab without enough ACL rights');
1062  return [];
1063  }
1064 
1065  $this->prepare_remove();
1066  if ($this->is_template) {
1067  $this->attrs = $this->templateSaveAttrs();
1068  $this->saved_attributes = [];
1069  }
1070  /* Pre hooks */
1071  $errors = $this->pre_remove();
1072  if (!empty($errors)) {
1073  return $errors;
1074  }
1075  $errors = $this->ldap_remove();
1076  if (!empty($errors)) {
1077  return $errors;
1078  }
1079  $this->post_remove();
1080  return [];
1081  }
1082 
1083  /* Remove FusionDirectory attributes */
1084  protected function prepare_remove ()
1085  {
1086  global $config;
1087  $this->attrs = [];
1088 
1089  if (!$this->mainTab) {
1090  /* include global link_info */
1091  $ldap = $config->get_ldap_link();
1092 
1093  /* Get current objectClasses in order to add the required ones */
1094  $ldap->cat($this->dn, ['fdTemplateField', 'objectClass']);
1095  $tmp = $ldap->fetch();
1096  $oc = [];
1097  if ($this->is_template) {
1098  if (isset($tmp['fdTemplateField'])) {
1099  foreach ($tmp['fdTemplateField'] as $tpl_field) {
1100  if (preg_match('/^objectClass:(.+)$/', $tpl_field, $m)) {
1101  $oc[] = $m[1];
1102  }
1103  }
1104  }
1105  } else {
1106  if (isset($tmp['objectClass'])) {
1107  $oc = $tmp['objectClass'];
1108  unset($oc['count']);
1109  }
1110  }
1111 
1112  /* Remove objectClasses from entry */
1113  $this->attrs['objectClass'] = array_remove_entries_ics($this->objectclasses, $oc);
1114 
1115  /* Unset attributes from entry */
1116  foreach ($this->attributes as $val) {
1117  $this->attrs["$val"] = [];
1118  }
1119  }
1120  }
1121 
1122  protected function pre_remove ()
1123  {
1124  if ($this->initially_was_account) {
1125  return $this->handle_pre_events('remove', ['modifiedLdapAttrs' => array_keys($this->attrs)]);
1126  }
1127  }
1128 
1129  protected function ldap_remove (): array
1130  {
1131  global $config;
1132  $ldap = $config->get_ldap_link();
1133  if ($this->mainTab) {
1134  $ldap->rmdir_recursive($this->dn);
1135  } else {
1136  $this->cleanup();
1137  $ldap->cd($this->dn);
1138  $ldap->modify($this->attrs);
1139  }
1140  $this->ldap_error = $ldap->get_error();
1141 
1142  if ($ldap->success()) {
1143  return [];
1144  } else {
1145  return [
1147  $this,
1148  $this->dn,
1149  ($this->mainTab ? LDAP_DEL : LDAP_MOD),
1150  $ldap->get_error(),
1151  $ldap->get_errno()
1152  )
1153  ];
1154  }
1155  }
1156 
1157  protected function post_remove ()
1158  {
1159  logging::log('remove', 'plugin/' . get_class($this), $this->dn, array_keys($this->attrs), $this->ldap_error);
1160 
1161  /* Optionally execute a command after we're done */
1162  $errors = $this->handle_post_events('remove', ['modifiedLdapAttrs' => array_keys($this->attrs)]);
1163  if (!empty($errors)) {
1164  msg_dialog::displayChecks($errors);
1165  }
1166  }
1167 
1170  function save_object ()
1171  {
1172  trigger_error('obsolete');
1173  $this->readPost();
1174  }
1175 
1178  function readPost ()
1179  {
1180  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->dn, 'readPost');
1181 
1182  if ($this->displayHeader && isset($_POST[get_class($this) . '_modify_state'])) {
1183  if ($this->is_account && $this->acl_is_removeable()) {
1184  $this->is_account = FALSE;
1185  } elseif (!$this->is_account && $this->acl_is_createable()) {
1186  $this->is_account = TRUE;
1187  }
1188  }
1189  if (is_object($this->dialog)) {
1190  $this->dialog->readPost();
1191  }
1192  if (isset($_POST[get_class($this) . '_posted'])) {
1193  // If our form has been posted
1194  // A first pass that loads the post values
1195  foreach ($this->attributesInfo as $sectionInfo) {
1196  foreach ($sectionInfo['attrs'] as $attr) {
1197  if ($this->attrIsWriteable($attr)) {
1198  // Each attribute know how to read its value from POST
1199  $attr->loadPostValue();
1200  }
1201  }
1202  }
1203  // A second one that applies them. That allow complex stuff such as attribute disabling
1204  foreach ($this->attributesInfo as $sectionInfo) {
1205  foreach ($sectionInfo['attrs'] as $attr) {
1206  if ($this->attrIsWriteable($attr)) {
1207  // Each attribute know how to read its value from POST
1208  $attr->applyPostValue();
1209  }
1210  }
1211  }
1212  }
1213  }
1214 
1215  protected function prepareSavedAttributes ()
1216  {
1217  /* Prepare saved attributes */
1218  $this->saved_attributes = $this->attrs;
1219  // Fill for differenciation in the post save as saved_attributes will be modified.
1220  $this->beforeLdapChangeAttributes = $this->saved_attributes;
1221 
1222  foreach (array_keys($this->saved_attributes) as $index) {
1223  if (is_numeric($index)) {
1224  unset($this->saved_attributes[$index]);
1225  continue;
1226  }
1227 
1228  list($attribute,) = explode(';', $index, 2);
1229  if (!in_array_ics($index, $this->attributes) && !in_array_ics($attribute, $this->attributes) && strcasecmp('objectClass', $attribute)) {
1230  unset($this->saved_attributes[$index]);
1231  continue;
1232  }
1233 
1234  if (isset($this->saved_attributes[$index][0])) {
1235  if (!isset($this->saved_attributes[$index]['count'])) {
1236  $this->saved_attributes[$index]['count'] = count($this->saved_attributes[$index]);
1237  }
1238  if ($this->saved_attributes[$index]['count'] == 1) {
1239  $tmp = $this->saved_attributes[$index][0];
1240  unset($this->saved_attributes[$index]);
1241  $this->saved_attributes[$index] = $tmp;
1242  continue;
1243  }
1244  }
1245  unset($this->saved_attributes[$index]['count']);
1246  }
1247  }
1248 
1253  function cleanup ()
1254  {
1255  foreach ($this->attrs as $index => $value) {
1256  /* Convert arrays with one element to non arrays, if the saved
1257  attributes are no array, too */
1258  if (is_array($this->attrs[$index]) &&
1259  (count($this->attrs[$index]) == 1) &&
1260  isset($this->saved_attributes[$index]) &&
1261  !is_array($this->saved_attributes[$index])) {
1262  $this->attrs[$index] = $this->attrs[$index][0];
1263  }
1264 
1265  /* Remove emtpy arrays if they do not differ */
1266  if (is_array($this->attrs[$index]) &&
1267  (count($this->attrs[$index]) == 0) &&
1268  !isset($this->saved_attributes[$index])) {
1269  unset($this->attrs[$index]);
1270  continue;
1271  }
1272 
1273  /* Remove single attributes that do not differ */
1274  if (!is_array($this->attrs[$index]) &&
1275  isset($this->saved_attributes[$index]) &&
1276  !is_array($this->saved_attributes[$index]) &&
1277  ($this->attrs[$index] == $this->saved_attributes[$index])) {
1278  unset($this->attrs[$index]);
1279  continue;
1280  }
1281 
1282  /* Remove arrays that do not differ */
1283  if (is_array($this->attrs[$index]) &&
1284  isset($this->saved_attributes[$index]) &&
1285  is_array($this->saved_attributes[$index]) &&
1286  !array_differs($this->attrs[$index], $this->saved_attributes[$index])) {
1287  unset($this->attrs[$index]);
1288  continue;
1289  }
1290  }
1291  }
1292 
1293  function prepareNextCleanup ()
1294  {
1295  /* Update saved attributes and ensure that next cleanups will be successful too */
1296  foreach ($this->attrs as $name => $value) {
1297  $this->saved_attributes[$name] = $value;
1298  }
1299  }
1300 
1303  function save (): array
1304  {
1305  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->dn, "save");
1306  $errors = $this->prepare_save();
1307  if (!empty($errors)) {
1308  return $errors;
1309  }
1310  if ($this->is_template) {
1311  $errors = templateHandling::checkFields($this->attrs);
1312  if (!empty($errors)) {
1313  return $errors;
1314  }
1315  $this->attrs = $this->templateSaveAttrs();
1316  $this->saved_attributes = [];
1317  }
1318  $this->cleanup();
1319  if (!$this->shouldSave()) {
1320  return []; /* Nothing to do here */
1321  }
1322  /* Pre hooks */
1323  $errors = $this->pre_save();
1324  if (!empty($errors)) {
1325  return $errors;
1326  }
1327  /* LDAP save itself */
1328  $errors = $this->ldap_save();
1329  if (!empty($errors)) {
1330  return $errors;
1331  }
1332  $this->prepareNextCleanup();
1333  /* Post hooks and logging */
1334  $this->post_save();
1335  return [];
1336  }
1337 
1338  protected function shouldSave (): bool
1339  {
1340  if ($this->mainTab && !$this->initially_was_account) {
1341  return TRUE;
1342  }
1343  return !empty($this->attrs);
1344  }
1345 
1346  /* Used by prepare_save and template::apply */
1347  public function mergeObjectClasses (array $oc): array
1348  {
1349  return array_merge_unique($oc, $this->objectclasses);
1350  }
1351 
1352  /* \!brief Prepare $this->attrs */
1353  protected function prepare_save (): array
1354  {
1355  global $config;
1356 
1357  $this->entryCSN = '';
1358 
1359  /* Start with empty array */
1360  $this->attrs = [];
1361  $oc = [];
1362 
1363  if (!$this->mainTab || $this->initially_was_account) {
1364  /* Get current objectClasses in order to add the required ones */
1365  $ldap = $config->get_ldap_link();
1366  $ldap->cat($this->dn, ['fdTemplateField', 'objectClass']);
1367 
1368  $tmp = $ldap->fetch();
1369 
1370  if ($this->is_template) {
1371  if (isset($tmp['fdTemplateField'])) {
1372  foreach ($tmp['fdTemplateField'] as $tpl_field) {
1373  if (preg_match('/^objectClass:(.+)$/', $tpl_field, $m)) {
1374  $oc[] = $m[1];
1375  }
1376  }
1377  }
1378  } else {
1379  if (isset($tmp['objectClass'])) {
1380  $oc = $tmp['objectClass'];
1381  unset($oc['count']);
1382  }
1383  }
1384  }
1385 
1386  $this->attrs['objectClass'] = $this->mergeObjectClasses($oc);
1387 
1388  /* Fill attributes LDAP values into the attrs array */
1389  foreach ($this->attributesInfo as $sectionInfo) {
1390  foreach ($sectionInfo['attrs'] as $attr) {
1391  $attr->fillLdapValue($this->attrs);
1392  }
1393  }
1394  /* Some of them have post-filling hook */
1395  foreach ($this->attributesInfo as $sectionInfo) {
1396  foreach ($sectionInfo['attrs'] as $attr) {
1397  $attr->fillLdapValueHook($this->attrs);
1398  }
1399  }
1400 
1401  return [];
1402  }
1403 
1404  protected function pre_save (): array
1405  {
1406  if ($this->initially_was_account) {
1407  return $this->handle_pre_events('modify', ['modifiedLdapAttrs' => array_keys($this->attrs)]);
1408  } else {
1409  return $this->handle_pre_events('add', ['modifiedLdapAttrs' => array_keys($this->attrs)]);
1410  }
1411  }
1412 
1413  /* Returns an array with the errors or an empty array */
1414  protected function ldap_save (): array
1415  {
1416  global $config;
1417 
1418  /* Check if this is a new entry ... add/modify */
1419  $ldap = $config->get_ldap_link();
1420  if ($this->mainTab && !$this->initially_was_account) {
1421  if ($ldap->dn_exists($this->dn)) {
1422  return [
1423  new SimplePluginError(
1424  $this,
1425  htmlescape(sprintf(_('There is already an entry with the same dn: %s'), $this->dn))
1426  )
1427  ];
1428  }
1429  $ldap->cd($config->current['BASE']);
1430  try {
1431  $ldap->create_missing_trees(preg_replace('/^[^,]+,/', '', $this->dn));
1432  } catch (FusionDirectoryError $error) {
1433  return [$error];
1434  }
1435  $action = 'add';
1436  } else {
1437  if (!$ldap->dn_exists($this->dn)) {
1438  return [
1439  new SimplePluginError(
1440  $this,
1441  htmlescape(sprintf(_('The entry %s is not existing'), $this->dn))
1442  )
1443  ];
1444  }
1445  $action = 'modify';
1446  }
1447 
1448  $ldap->cd($this->dn);
1449  $ldap->$action($this->attrs);
1450  $this->ldap_error = $ldap->get_error();
1451 
1452  /* Check for errors */
1453  if (!$ldap->success()) {
1454  return [
1456  $this,
1457  $this->dn,
1458  ($action == 'modify' ? LDAP_MOD : LDAP_ADD),
1459  $ldap->get_error(),
1460  $ldap->get_errno()
1461  )
1462  ];
1463  }
1464  return [];
1465  }
1466 
1471  protected function post_save ()
1472  {
1473  $auditAttributesValuesToBeHidden = $this->getAuditAttributesListFromConf();
1474 
1475  if (!empty($auditAttributesValuesToBeHidden)) {
1476  foreach ($auditAttributesValuesToBeHidden as $key) {
1477  if (key_exists($key, $this->attrs)) {
1478  $this->attrs[$key] = 'Value not stored by policy';
1479  }
1480  }
1481  }
1482 
1483  /* Propagate and log the event */
1484  if ($this->initially_was_account) {
1485  $errors = $this->handle_post_events('modify', ['modifiedLdapAttrs' => array_keys($this->attrs)]);
1486 
1487  $modifiedAttrs = $this->getModifiedAttributesValues();
1488  // We log values of attributes as well if modification occur in order for notification to be aware of the change. (Json allows array to string conversion).
1489  logging::log('modify', 'plugin/' . get_class($this), $this->dn, [json_encode($modifiedAttrs)], $this->ldap_error);
1490 
1491  } else {
1492  $errors = $this->handle_post_events('add', ['modifiedLdapAttrs' => array_keys($this->attrs)]);
1493  logging::log('create', 'plugin/' . get_class($this), $this->dn, array_keys($this->attrs), $this->ldap_error);
1494  }
1495 
1496  if (!empty($errors)) {
1497  msg_dialog::displayChecks($errors);
1498  }
1499  }
1500 
1506  protected function getAuditAttributesListFromConf (): array
1507  {
1508  global $config;
1509  $result = [];
1510 
1511  // If audit plugin is installed only.
1512  if (class_available('auditConfig')) {
1513  if (!empty($config->current['AUDITCONFHIDDENATTRVALUES'])) {
1514  if (is_string($config->current['AUDITCONFHIDDENATTRVALUES'])) {
1515  $result[] = $config->current['AUDITCONFHIDDENATTRVALUES'];
1516  } else {
1517  $result = $config->current['AUDITCONFHIDDENATTRVALUES'];
1518  }
1519  }
1520  }
1521 
1522  return $result;
1523  }
1524 
1525  private function getModifiedAttributesValues (): array
1526  {
1527  // Initialize result array
1528  $result = [];
1529 
1530  // Find common keys between old attributes and modified attributes.
1531  $commonKeys = array_intersect_key($this->attrs, $this->beforeLdapChangeAttributes);
1532 
1533  // Iterate over each common key
1534  foreach ($commonKeys as $key => $value) {
1535  // Check if the new value differs from the old value
1536  if ($this->attrs[$key] !== $this->beforeLdapChangeAttributes[$key]) {
1537  $newValues = $this->attrs[$key];
1538  $oldValues = $this->beforeLdapChangeAttributes[$key];
1539 
1540  // Ensure both new and old values are arrays for comparison
1541  if (is_array($newValues) && is_array($oldValues)) {
1542  // Find the new values that are not present in the old values
1543  $diffValues = array_diff($newValues, $oldValues);
1544 
1545  // Store only the new values that are different
1546  if (!empty($diffValues)) {
1547  $result[$key] = $diffValues;
1548  }
1549  } else {
1550  // If values are scalar (non-array), store the new value directly if it differs
1551  $result[$key] = $newValues;
1552  }
1553  }
1554  }
1555 
1556  return $result;
1557  }
1558 
1568  protected function handle_hooks (string $when, string $mode, array $addAttrs = []): array
1569  {
1570  switch ($mode) {
1571  case 'add':
1572  return $this->callHook($when . 'CREATE', $addAttrs);
1573 
1574  case 'modify':
1575  return $this->callHook($when . 'MODIFY', $addAttrs);
1576 
1577  case 'remove':
1578  return $this->callHook($when . 'REMOVE', $addAttrs);
1579 
1580  default:
1581  trigger_error(sprintf('Invalid %s event type given: "%s"! Valid types are: add, modify, remove.', strtolower($when), $mode));
1582  return [];
1583  }
1584  }
1585 
1589  function handle_post_events (string $mode, array $addAttrs = [])
1590  {
1591  /* Update foreign keys */
1592  if ($mode == 'remove') {
1593  $this->handleForeignKeys($this->dn, NULL);
1594  } elseif ($mode == 'modify') {
1595  $this->handleForeignKeys();
1596  }
1597  return $this->handle_hooks('POST', $mode, $addAttrs);
1598  }
1599 
1604  function handle_pre_events (string $mode, array $addAttrs = []): array
1605  {
1606  global $config;
1607 
1608  $this->ldap_error = '';
1609  if ($this->mainTab && ($mode == 'remove')) {
1610  /* Store information if there was subobjects before deletion */
1611  $ldap = $config->get_ldap_link();
1612  $ldap->cd($this->dn);
1613  $ldap->search('(objectClass=*)', ['dn'], 'one');
1614  $this->hadSubobjects = ($ldap->count() > 0);
1615  }
1616  return $this->handle_hooks('PRE', $mode, $addAttrs);
1617  }
1618 
1619  function fillHookAttrs (array &$addAttrs)
1620  {
1621  // Walk trough attributes list and add the plugins attributes.
1622  foreach ($this->attributes as $attr) {
1623  if (!isset($addAttrs[$attr])) {
1624  $addAttrs[$attr] = $this->$attr;
1625  }
1626  }
1627  }
1628 
1634  function callHook ($cmd, array $addAttrs = [], &$returnOutput = [], &$returnCode = NULL): array
1635  {
1636  if ($this->is_template) {
1637  return [];
1638  }
1639  global $config;
1640 
1641  $commands = $config->searchHooks(get_class($this), $cmd);
1642  $messages = [];
1643 
1644  foreach ($commands as $command) {
1645  $this->fillHookAttrs($addAttrs);
1646 
1647  $ui = get_userinfo();
1648 
1649  $addAttrs['callerDN'] = $ui->dn;
1650  $addAttrs['callerCN'] = $ui->cn;
1651  $addAttrs['callerUID'] = $ui->uid;
1652  $addAttrs['callerSN'] = $ui->sn;
1653  $addAttrs['callerGIVENNAME'] = $ui->givenName;
1654  $addAttrs['callerMAIL'] = $ui->mail;
1655 
1656  $addAttrs['dn'] = $this->dn;
1657  $addAttrs['location'] = $config->current['NAME'];
1658 
1659  if (isset($this->parent->by_object)) {
1660  foreach ($this->parent->by_object as $class => $object) {
1661  if ($class != get_class($this)) {
1662  $object->fillHookAttrs($addAttrs);
1663  }
1664  }
1665  }
1666 
1667  if (!isset($addAttrs['base']) && isset($this->base)) {
1668  $addAttrs['base'] = $this->base;
1669  }
1670 
1671  $command = templateHandling::parseString($command, $addAttrs, 'escapeshellarg');
1672  logging::debug(DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, 'Execute');
1673  exec($command, $arr, $returnCode);
1674 
1675  $command = static::passwordProtect($command);
1676 
1677  $returnOutput = $arr;
1678 
1679  if ($returnCode != 0) {
1680  $str = implode("\n", $arr);
1681  $str = static::passwordProtect($str);
1682  logging::debug(DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, 'Execution failed code: ' . $returnCode);
1683  logging::debug(DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, 'Output: ' . $str);
1684  $messages[] = new SimplePluginHookError(
1685  $this,
1686  $cmd,
1687  $str,
1688  $returnCode
1689  );
1690  } elseif (is_array($arr)) {
1691  $str = implode("\n", $arr);
1692  $str = static::passwordProtect($str);
1693  logging::debug(DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, 'Output: ' . $str);
1694  if (!empty($str) && $config->get_cfg_value('displayHookOutput', 'FALSE') == 'TRUE') {
1695  msg_dialog::display('[' . get_class($this) . ' ' . strtolower($cmd) . 'trigger] ' . $command, htmlescape($str), INFO_DIALOG);
1696  }
1697  }
1698  unset($arr, $command, $returnCode);
1699  }
1700  return $messages;
1701  }
1702 
1705  protected static function passwordProtect (string $hookCommand = NULL): string
1706  {
1707  if (isset($_POST["userPassword_password"]) && !empty($_POST["userPassword_password"])) {
1708  if (strpos($hookCommand, $_POST["userPassword_password"]) !== FALSE) {
1709  $hookCommand = str_replace($_POST["userPassword_password"], '*******', $hookCommand);
1710  }
1711  }
1712  return $hookCommand;
1713  }
1714 
1717  function check (): array
1718  {
1719  logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->dn, 'check');
1720  $messages = [];
1721 
1722  foreach ($this->attributesInfo as $sectionInfo) {
1723  foreach ($sectionInfo['attrs'] as $attr) {
1724  $error = $attr->check();
1725  if (!empty($error)) {
1726  if (is_array($error)) {
1727  $messages = array_merge($messages, $error);
1728  } else {
1729  $messages[] = $error;
1730  }
1731  }
1732  }
1733  }
1734 
1735  $error = $this->callHook('CHECK', ['nbCheckErrors' => count($messages)], $returnOutput);
1736  if (!empty($error)) {
1737  $messages = array_merge($messages, $error);
1738  } elseif (!empty($returnOutput)) {
1739  $messages[] = join("\n", $returnOutput);
1740  }
1741 
1742  /* Check entryCSN */
1743  if (!empty($this->entryCSN)) {
1744  $current_csn = getEntryCSN($this->dn);
1745  if (($current_csn != $this->entryCSN) && !empty($current_csn)) {
1746  $this->entryCSN = $current_csn;
1747  $messages[] = _('The object has changed since being opened in FusionDirectory. All changes that may be done by others will get lost if you save this entry!');
1748  }
1749  }
1750 
1751  return $messages;
1752  }
1753 
1754  function handleForeignKeys (string $olddn = NULL, string $newdn = NULL, string $mode = 'move')
1755  {
1756  if (($olddn !== NULL) && ($olddn == $newdn)) {
1757  return;
1758  }
1759  if ($this->is_template) {
1760  return;
1761  }
1762  $this->browseForeignKeys(
1763  'handle_' . $mode,
1764  $olddn,
1765  $newdn
1766  );
1767  }
1768 
1769  function browseForeignKeys (string $mode, $param1 = NULL, $param2 = NULL)
1770  {
1771  global $config, $plist;
1772 
1773  $subobjects = FALSE;
1774  if (preg_match('/^handle_/', $mode)) {
1775  $olddn = $param1;
1776  $newdn = $param2;
1777  $classes = [get_class($this)];
1778  if (($olddn != $newdn) && $this->mainTab) {
1779  if ($newdn === NULL) {
1780  $subobjects = $this->hadSubobjects;
1781  } else {
1782  $ldap = $config->get_ldap_link();
1783  $ldap->cd($newdn);
1784  $ldap->search('(objectClass=*)', ['dn'], 'one');
1785  $subobjects = ($ldap->count() > 0);
1786  }
1787  }
1788  } elseif ($mode == 'references') {
1789  $classes = array_keys($this->parent->by_object);
1790  }
1791  // We group by objectType concerned
1792  $foreignRefs = [];
1793  if ($subobjects) {
1794  $field = 'dn';
1795  /* Special treatment for foreign keys on DN when moving an object
1796  * All references on DN are treated on subobjects */
1797  foreach ($plist->dnForeignRefs as $ref) {
1798  $class = $ref[0];
1799  $ofield = $ref[1];
1800  $filter = $ref[2];
1801  $filtersub = $ref[3];
1802  if ($filtersub == '*') {
1803  if ($config->get_cfg_value('wildcardForeignKeys', 'TRUE') == 'TRUE') {
1804  $filtersub = $ofield . '=*';
1805  } else {
1806  continue;
1807  }
1808  }
1809  if ($class == 'aclAssignment') {
1810  /* Special case: aclAssignment foreignKey is ignored on department types as it’s handled by the aclAssignment objectType */
1811  $objectTypes = ['ACLASSIGNMENT'];
1812  } elseif (is_subclass_of($class, 'simpleService')) {
1813  $objectTypes = ['SERVER'];
1814  } else {
1815  $objectTypes = [];
1816  $cinfos = pluglist::pluginInfos($class);
1817  foreach ($cinfos['plObjectType'] as $key => $objectType) {
1818  if (!is_numeric($key)) {
1819  $objectType = $key;
1820  }
1821  if (preg_match('/^ogroup-/i', $objectType)) {
1822  $objectType = 'OGROUP';
1823  }
1824  $objectTypes[] = strtoupper($objectType);
1825  }
1826  $objectTypes = array_unique($objectTypes);
1827  }
1828  foreach ($objectTypes as $objectType) {
1829  $oldvalue = $olddn;
1830  $newvalue = $newdn;
1831 
1832  $foreignRefs[$objectType]['refs'][$class][$ofield][$field]
1833  = [
1834  'tab' => $classes[0],
1835  'field' => $field,
1836  'oldvalue' => $oldvalue,
1837  'newvalue' => $newvalue,
1838  ];
1839  $filter = templateHandling::parseString($filtersub, ['oldvalue' => $oldvalue, 'newvalue' => $newvalue], 'ldap_escape_f');
1840  if (!preg_match('/^\(.*\)$/', $filter)) {
1841  $filter = '(' . $filter . ')';
1842  }
1843  $foreignRefs[$objectType]['filters'][$filter] = $filter;
1844  }
1845  }
1846  }
1847  foreach ($classes as $tabclass) {
1848  try {
1849  $infos = pluglist::pluginInfos($tabclass);
1850  foreach ($infos['plForeignRefs'] as $field => $refs) {
1851  if (preg_match('/^handle_/', $mode)) {
1852  if (
1853  (($newdn !== NULL) && ($field != 'dn') && ($mode == 'handle_move')) ||
1854  (($newdn === NULL) && ($olddn === NULL) && (($field == 'dn') || (!$this->attributeHaveChanged($field))))
1855  ) {
1856  // Move action, ignore other fields than dn
1857  // Edit action, ignore dn changes or attributes which did not change
1858  continue;
1859  }
1860  // else = delete action, all fields are concerned, nothing to do here
1861  }
1862  foreach ($refs as $ref) {
1863  $class = $ref[0];
1864  $ofield = $ref[1];
1865  $filter = $ref[2];
1866  $cinfos = pluglist::pluginInfos($class);
1867  if ($class == 'aclAssignment') {
1868  /* Special case: aclAssignment foreignKey is ignored on department types as it’s handled by the aclAssignment objectType */
1869  $objectTypes = ['ACLASSIGNMENT'];
1870  } elseif (is_subclass_of($class, 'simpleService')) {
1871  $objectTypes = ['SERVER'];
1872  } else {
1873  $objectTypes = [];
1874  foreach ($cinfos['plObjectType'] as $key => $objectType) {
1875  if (!is_numeric($key)) {
1876  $objectType = $key;
1877  }
1878  if (preg_match('/^ogroup-/i', $objectType)) {
1879  $objectType = 'OGROUP';
1880  }
1881  $objectTypes[] = $objectType;
1882  }
1883  $objectTypes = array_unique($objectTypes);
1884  }
1885  foreach ($objectTypes as $objectType) {
1886  if (preg_match('/^handle_/', $mode)) {
1887  if ($field == 'dn') {
1888  $oldvalue = $olddn;
1889  $newvalue = $newdn;
1890  } elseif (($olddn !== NULL) && ($newdn === NULL)) {
1891  $oldvalue = $this->attributeInitialValue($field);
1892  $newvalue = NULL;
1893  } else {
1894  $oldvalue = $this->attributeInitialValue($field);
1895  $newvalue = $this->attributeValue($field);
1896  }
1897  $foreignRefs[$objectType]['refs'][$class][$ofield][$field]
1898  = [
1899  'tab' => $tabclass,
1900  'field' => $field,
1901  'oldvalue' => $oldvalue,
1902  'newvalue' => $newvalue,
1903  ];
1904  $filter = templateHandling::parseString($filter, ['oldvalue' => $oldvalue, 'newvalue' => $newvalue], 'ldap_escape_f');
1905  } elseif ($mode == 'references') {
1906  $foreignRefs[$objectType]['refs'][$class]['name'] = $cinfos['plShortName'];
1907 
1908  $foreignRefs[$objectType]['refs'][$class]['fields'][$ofield][$field]
1909  = [
1910  'tab' => $tabclass,
1911  'field' => $field,
1912  'tabname' => $this->parent->by_name[$tabclass],
1913  'value' => $this->parent->by_object[$tabclass]->$field,
1914  ];
1915  $filter = templateHandling::parseString($filter, ['oldvalue' => $this->parent->by_object[$tabclass]->$field], 'ldap_escape_f');
1916  }
1917  if (!preg_match('/^\(.*\)$/', $filter)) {
1918  $filter = '(' . $filter . ')';
1919  }
1920  $foreignRefs[$objectType]['filters'][$filter] = $filter;
1921  }
1922  }
1923  }
1924  } catch (UnknownClassException $e) {
1925  /* May happen in special cases like setup */
1926  continue;
1927  }
1928  }
1929 
1930  /* Back up POST content */
1931  $SAVED_POST = $_POST;
1932  $refs = [];
1933  // For each concerned objectType
1934  foreach ($foreignRefs as $objectType => $tabRefs) {
1935  // Compute filter
1936  $filters = array_values($tabRefs['filters']);
1937  $filter = '(|' . join($filters) . ')';
1938  // Search objects
1939  try {
1940  $objects = objects::ls($objectType, ['dn' => 'raw'], NULL, $filter);
1941  } catch (NonExistingObjectTypeException $e) {
1942  continue;
1943  } catch (EmptyFilterException $e) {
1944  continue;
1945  }
1946  // For each object of this type
1947  foreach (array_keys($objects) as $dn) {
1948  // Build the object
1949  $tabobject = objects::open($dn, $objectType);
1950  if (preg_match('/^handle_/', $mode)) {
1951  // For each tab concerned
1952  foreach ($tabRefs['refs'] as $tab => $fieldRefs) {
1953  // If the tab is activated on this object
1954  $pluginobject = $tabobject->getTabOrServiceObject($tab);
1955  if ($pluginobject !== FALSE) {
1956  // For each field
1957  foreach ($fieldRefs as $ofield => $fields) {
1958  foreach ($fields as $field) {
1959  // call plugin::foreignKeyUpdate(ldapname, oldvalue, newvalue, source) on the object
1960  $pluginobject->foreignKeyUpdate(
1961  $ofield,
1962  $field['oldvalue'],
1963  $field['newvalue'],
1964  [
1965  'CLASS' => $field['tab'],
1966  'FIELD' => $field['field'],
1967  'MODE' => preg_replace('/^handle_/', '', $mode),
1968  'DN' => $this->dn,
1969  ]
1970  );
1971  }
1972  }
1973  $pluginobject->update();
1974  }
1975  }
1976  $errors = $tabobject->save();
1977  msg_dialog::displayChecks($errors);
1978  } elseif ($mode == 'references') {
1979  // For each tab concerned
1980  foreach ($tabRefs['refs'] as $tab => $tab_infos) {
1981  // If the tab is activated on this object
1982  $pluginobject = $tabobject->getTabOrServiceObject($tab);
1983  if ($pluginobject !== FALSE) {
1984  // For each field
1985  foreach ($tab_infos['fields'] as $ofield => $fields) {
1986  foreach ($fields as $field) {
1987  if ($pluginobject->foreignKeyCheck(
1988  $ofield,
1989  $field['value'],
1990  [
1991  'CLASS' => $field['tab'],
1992  'FIELD' => $field['field'],
1993  'DN' => $this->dn,
1994  ]
1995  )) {
1996  if (!isset($refs[$dn])) {
1997  $refs[$dn] = [
1998  'link' => '',
1999  'tabs' => [],
2000  ];
2001  try {
2002  $refs[$dn]['link'] = objects::link($dn, $objectType);
2003  } catch (FusionDirectoryException $e) {
2004  trigger_error("Could not create link to $dn: " . $e->getMessage());
2005  $refs[$dn]['link'] = $dn;
2006  }
2007  }
2008  if (!isset($refs[$dn]['tabs'][$tab])) {
2009  $refs[$dn]['tabs'][$tab] = [
2010  'link' => '',
2011  'fields' => [],
2012  ];
2013  try {
2014  if (is_subclass_of($tab, 'simpleService')) {
2015  $refs[$dn]['tabs'][$tab]['link'] = objects::link($dn, $objectType, "service_$tab", sprintf(_('Service "%s"'), $tab_infos['name']));
2016  } else {
2017  $refs[$dn]['tabs'][$tab]['link'] = objects::link($dn, $objectType, "tab_$tab", sprintf(_('Tab "%s"'), $tab_infos['name']));
2018  }
2019  } catch (FusionDirectoryException $e) {
2020  trigger_error("Could not create link to $dn $tab: " . $e->getMessage());
2021  $refs[$dn]['tabs'][$tab]['link'] = $tab;
2022  }
2023  }
2024  $refs[$dn]['tabs'][$tab]['fields'][$ofield] = $field;
2025  }
2026  }
2027  }
2028  }
2029  }
2030  }
2031  }
2032  }
2033  /* Restore POST */
2034  $_POST = $SAVED_POST;
2035  if ($mode == 'references') {
2036  return $refs;
2037  }
2038  }
2039 
2047  function create_unique_dn (string $attribute, string $base): string
2048  {
2049  global $config;
2050  $ldap = $config->get_ldap_link();
2051  $base = preg_replace('/^,*/', '', $base);
2052 
2053  /* Try to use plain entry first */
2054  $dn = $attribute . '=' . ldap_escape_dn($this->$attribute) . ',' . $base;
2055  if (($dn == $this->orig_dn) || !$ldap->dn_exists($dn)) {
2056  return $dn;
2057  }
2058 
2059  /* Build DN with multiple attributes */
2060  $usableAttributes = [];
2061  foreach ($this->attributes as $attr) {
2062  if (($attr != $attribute) && is_scalar($this->$attr) && ($this->$attr != '')) {
2063  $usableAttributes[] = (string)$attr;
2064  }
2065  }
2066  for ($i = 1; $i < count($usableAttributes); $i++) {
2067  foreach (new Combinations($usableAttributes, $i) as $attrs) {
2068  $dn = $attribute . '=' . ldap_escape_dn($this->$attribute);
2069  foreach ($attrs as $attr) {
2070  $dn .= '+' . $attr . '=' . ldap_escape_dn($this->$attr);
2071  }
2072  $dn .= ',' . $base;
2073  if (($dn == $this->orig_dn) || !$ldap->dn_exists($dn)) {
2074  return $dn;
2075  }
2076  }
2077  }
2078 
2079  /* None found */
2080  throw new FusionDirectoryException(_('Failed to create a unique DN'));
2081  }
2082 
2092  function adapt_from_template (array $attrs, array $skip = [])
2093  {
2094  $this->attrs = array_merge($this->attrs, $attrs);
2095 
2096  /* Walk through attributes */
2097  foreach ($this->attributesAccess as $ldapName => &$attr) {
2098  /* Skip the ones in skip list */
2099  if (in_array($ldapName, $skip)) {
2100  continue;
2101  }
2102  /* Load values */
2103  $attr->loadValue($attrs);
2104  }
2105  unset($attr);
2106 
2107  /* Is Account? */
2108  $this->is_account = $this->is_this_account($this->attrs);
2109  }
2110 
2114  function resetCopyInfos ()
2115  {
2116  $this->dn = 'new';
2117  $this->orig_dn = $this->dn;
2118 
2119  $this->saved_attributes = [];
2120  $this->initially_was_account = FALSE;
2121  }
2122 
2123  protected function attributeHaveChanged (string $field): bool
2124  {
2125  return $this->attributesAccess[$field]->hasChanged();
2126  }
2127 
2128  protected function attributeValue (string $field)
2129  {
2130  return $this->attributesAccess[$field]->getValue();
2131  }
2132 
2133  protected function attributeInitialValue (string $field)
2134  {
2135  return $this->attributesAccess[$field]->getInitialValue();
2136  }
2137 
2138  function foreignKeyUpdate (string $field, $oldvalue, $newvalue, array $source)
2139  {
2140  if (!isset($source['MODE'])) {
2141  $source['MODE'] = 'move';
2142  }
2143 
2144  // In case of SetAttribute, value is an array needing to be changed to string.
2145  if (is_array($oldvalue) && isset($oldvalue[0])) {
2146 
2147  $oldvalue = $oldvalue[0];
2148  }
2149  if (is_array($newvalue) && isset($newvalue[0])) {
2150 
2151  $newvalue = $newvalue[0];
2152  }
2153 
2154  $this->attributesAccess[$field]->foreignKeyUpdate($oldvalue, $newvalue, $source);
2155  }
2156 
2157  /*
2158  * Source is an array like this:
2159  * array(
2160  * 'CLASS' => class,
2161  * 'FIELD' => field,
2162  * 'DN' => dn,
2163  * 'MODE' => mode
2164  * )
2165  * mode being either 'copy' or 'move', defaults to 'move'
2166  */
2167  function foreignKeyCheck (string $field, $value, array $source)
2168  {
2169  // In case of SetAttribute, value is an array needing to be changed to string.
2170  if (is_array($value) && isset($value[0])) {
2171 
2172  $value = $value[0];
2173  }
2174  return $this->attributesAccess[$field]->foreignKeyCheck($value, $source);
2175  }
2176 
2177  function deserializeValues (array $values, bool $checkAcl = TRUE)
2178  {
2179  foreach ($values as $name => $value) {
2180  if (isset($this->attributesAccess[$name])) {
2181  if (!$checkAcl || $this->attrIsWriteable($name)) {
2182  $error = $this->attributesAccess[$name]->deserializeValue($value);
2183  if (!empty($error)) {
2184  return $error;
2185  }
2186  } else {
2187  return new SimplePluginPermissionError($this, msgPool::permModify($this->dn, $name));
2188  }
2189  } else {
2190  return new SimplePluginError(
2191  $this,
2192  htmlescape(sprintf(_('Unknown field "%s"'), $name))
2193  );
2194  }
2195  }
2196  return TRUE;
2197  }
2198 
2203  function showInTemplate (string $attr, array $templateAttrs): bool
2204  {
2205  if (isset($templateAttrs[$attr])) {
2206  return FALSE;
2207  }
2208  return TRUE;
2209  }
2210 
2211  function is_modal_dialog (): bool
2212  {
2213  return (isset($this->dialog) && $this->dialog);
2214  }
2215 
2216  static function fillAccountAttrsNeeded (&$needed)
2217  {
2218  $infos = pluglist::pluginInfos(get_called_class());
2219  if (isset($infos['plFilterObject'])) {
2220  $attrs = $infos['plFilterObject']->listUsedAttributes();
2221  foreach ($attrs as $attr) {
2222  if (!isset($needed[$attr])) {
2223  $needed[$attr] = '*';
2224  }
2225  }
2226  }
2227  }
2228 
2229  static function isAccount ($attrs)
2230  {
2231  $infos = pluglist::pluginInfos(get_called_class());
2232  if (isset($infos['plFilterObject'])) {
2233  return $infos['plFilterObject']($attrs);
2234  }
2235  return NULL;
2236  }
2237 
2238  static function getLdapFilter ()
2239  {
2240  $infos = pluglist::pluginInfos(get_called_class());
2241  if (isset($infos['plFilter'])) {
2242  return $infos['plFilter'];
2243  }
2244  return NULL;
2245  }
2246 
2247  static function getLdapFilterObject ()
2248  {
2249  $infos = pluglist::pluginInfos(get_called_class());
2250  if (isset($infos['plFilterObject'])) {
2251  return $infos['plFilterObject'];
2252  }
2253  return NULL;
2254  }
2255 
2261  static function plInfo (): array
2262  {
2263  return [];
2264  }
2265 
2272  static function generatePlProvidedAcls (array $attributesInfo, bool $operationalAttributes = NULL): array
2273  {
2274  $plProvidedAcls = [];
2275  foreach ($attributesInfo as $sectionInfo) {
2276  foreach ($sectionInfo['attrs'] as $attr) {
2277  if (($attr->getLdapName() === 'base') && ($operationalAttributes === NULL)) {
2278  /* If we handle base, we also handle LDAP operational attributes */
2279  $operationalAttributes = TRUE;
2280  }
2281  $aclInfo = $attr->getAclInfo();
2282  if ($aclInfo !== FALSE) {
2283  $plProvidedAcls[$aclInfo['name']] = $aclInfo['desc'];
2284  }
2285  }
2286  }
2287  if ($operationalAttributes) {
2288  $plProvidedAcls['createTimestamp'] = _('The time the entry was added');
2289  $plProvidedAcls['modifyTimestamp'] = _('The time the entry was last modified');
2290  }
2291 
2292  return $plProvidedAcls;
2293  }
2294 
2308  static function mainInc ($classname = NULL, $entry_dn = NULL, $tabs = FALSE, $edit_mode = TRUE, $objectType = FALSE)
2309  {
2310  global $remove_lock, $cleanup, $display, $config, $plug, $ui, $smarty;
2311 
2312  if ($classname === NULL) {
2313  $classname = get_called_class();
2314  }
2315 
2316  if ($entry_dn === NULL) {
2317  $entry_dn = $ui->dn;
2318  }
2319 
2320  $plInfo = pluglist::pluginInfos($classname);
2321  $plIcon = (isset($plInfo['plIcon']) ? $plInfo['plIcon'] : 'plugin.png');
2322  $plHeadline = $plInfo['plTitle'];
2323  if ($objectType === FALSE) {
2324  $key = key($plInfo['plObjectType']);
2325  if (is_numeric($key)) {
2326  $key = $plInfo['plObjectType'][$key];
2327  }
2328  $objectType = $key;
2329  }
2330 
2331  $lock_msg = "";
2332  if ($edit_mode
2333  && ($remove_lock || (isset($_POST['edit_cancel']) && session::is_set('edit')))
2334  && session::is_set($classname)) {
2335  /* Remove locks created by this plugin */
2336  Lock::deleteByObject($entry_dn);
2337  }
2338 
2339  /* Remove this plugin from session */
2340  if ($cleanup) {
2341  session::un_set($classname);
2342  session::un_set('edit');
2343  } else {
2344  /* Reset requested? */
2345  if ($edit_mode && isset($_POST['edit_cancel'])) {
2346  session::un_set($classname);
2347  session::un_set('edit');
2348  }
2349 
2350  /* Create tab object on demand */
2351  if (!session::is_set($classname) || (isset($_GET['reset']) && $_GET['reset'] == 1)) {
2352  try {
2353  $tabObject = objects::open($entry_dn, $objectType);
2354  } catch (NonExistingLdapNodeException $e) {
2355  $tabObject = objects::open('new', $objectType);
2356  }
2357  if ($edit_mode) {
2358  $tabObject->setNeedEditMode(TRUE);
2359  }
2360  if (!$tabs) {
2361  $tabObject->current = $classname;
2362  }
2363  session::set($classname, $tabObject);
2364  }
2365  $tabObject = session::get($classname);
2366 
2367  if (!$edit_mode || session::is_set('edit')) {
2368  /* Save changes back to object */
2369  $tabObject->readPost();
2370  $tabObject->update();
2371  } else {
2372  /* Allow changing tab before going into edit mode */
2373  $tabObject->readPostTabChange();
2374  }
2375 
2376  if ($edit_mode) {
2377  /* Enter edit mode? */
2378  if ((isset($_POST['edit'])) && (!session::is_set('edit'))) {
2379  /* Check locking */
2380  if ($locks = Lock::get($entry_dn)) {
2381  session::set('LOCK_VARS_TO_USE', ['/^edit$/', '/^plug$/']);
2382  $lock_msg = Lock::genLockedMessage($locks);
2383  } else {
2384  /* Lock the current entry */
2385  Lock::add($entry_dn);
2386  session::set('edit', TRUE);
2387  }
2388  }
2389 
2390  /* save changes to LDAP and disable edit mode */
2391  if (isset($_POST['edit_finish'])) {
2392  /* Perform checks */
2393  $errors = $tabObject->save();
2394 
2395  /* No errors, save object */
2396  if (count($errors) == 0) {
2397  Lock::deleteByObject($entry_dn);
2398  session::un_set('edit');
2399 
2400  /* Remove from session */
2401  session::un_set($classname);
2402  } else {
2403  /* Errors found, show errors */
2404  msg_dialog::displayChecks($errors);
2405  }
2406  }
2407  }
2408 
2409  /* Execute formular */
2410  if ($edit_mode && $lock_msg) {
2411  $display = $lock_msg;
2412  } else {
2413  if ($tabs) {
2414  $display .= $tabObject->render();
2415  } else {
2416  $display .= $tabObject->by_object[$classname]->render();
2417  }
2418  }
2419 
2420  /* Store changes in session */
2421  if (!$edit_mode || session::is_set('edit')) {
2422  session::set($classname, $tabObject);
2423  }
2424 
2425  /* Show page footer depending on the mode */
2426  $info = $entry_dn . '&nbsp;';
2427  if ($edit_mode && (!$tabObject->dialogOpened()) && empty($lock_msg)) {
2428  /* Are we in edit mode? */
2429  if (session::is_set('edit')) {
2430  $display .= '<p class="plugbottom">' . "\n";
2431  $display .= '<input type="submit" name="edit_finish" style="width:80px" value="' . msgPool::okButton() . '"/>' . "\n";
2432  $display .= '&nbsp;';
2433  $display .= '<input type="submit" formnovalidate="formnovalidate" name="edit_cancel" value="' . msgPool::cancelButton() . '"/>' . "\n";
2434  $display .= "</p>\n";
2435  } elseif (strpos($tabObject->by_object[$tabObject->current]->aclGetPermissions(''), 'w') !== FALSE) {
2436  /* Only display edit button if there is at least one attribute writable */
2437  $display .= '<p class="plugbottom">' . "\n";
2438  $info .= '<div style="float:left;" class="optional"><img class="center" alt="information" ' .
2439  'src="geticon.php?context=status&amp;icon=dialog-information&amp;size=16"> ' .
2440  msgPool::clickEditToChange() . '</div>';
2441  $display .= '<input type="submit" name="edit" value="' . msgPool::editButton() . '"/>' . "\n";
2442  $display .= "</p>\n";
2443  }
2444  }
2445 
2446  /* Page header */
2447  if (!preg_match('/^geticon/', $plIcon)) {
2448  $plIcon = get_template_path($plIcon);
2449  }
2450  $smarty->assign('headline', $plHeadline);
2451  $smarty->assign('headline_image', $plIcon);
2452  $display = '<div class="pluginfo">' . $info . "</div>\n" . $display;
2453  }
2454  }
2455 }
$parent
Reference to parent object.
$saved_attributes
The state of the attributes when we opened the object.
htmlescape(string $str)
Escape string for HTML output.
Definition: php_setup.inc:32
handle_post_events(string $mode, array $addAttrs=[])
Forward command execution requests to the post hook execution method.
This class is made for easy plugin creation for editing LDAP attributes.
setNeedEditMode(bool $bool)
Sets whether the opened objet has an edit button.
in_array_ics($value, array $items)
Check if a value exists in an array (case-insensitive)
Definition: functions.inc:814
getEntryCSN(string $dn)
Get the Change Sequence Number of a certain DN.
Definition: functions.inc:1334
$inheritance
FALSE to disable inheritance. Array like array (&#39;objectClass&#39; => &#39;attribute&#39;) to specify oc of the gr...
callHook($cmd, array $addAttrs=[], &$returnOutput=[], &$returnCode=NULL)
Calls external hooks which are defined for this plugin (fusiondirectory.conf) Replaces placeholder by...
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.
acl_is_readable($attribute)
Can we read the acl.
acl_is_createable(string $base=NULL)
Can we create the object.
$objectclasses
The objectClasses set by this tab.
const DEBUG_LDAP
get_allowed_bases()
Returns a list of all available departments for this object.
update()
Update state and return FALSE if the dialog was closed.
handle_hooks(string $when, string $mode, array $addAttrs=[])
Forward command execution requests to the pre/post hook execution method.
static featuresEnabled($name, $depends='')
List the features settings enabled.
This class contains all function to manage tabs classes.
isActivatable()
Test whether a tab can be deactivated.
compute_dn()
This function returns the dn this object should have.
__set($name, $value)
This function allows to use the syntax $plugin->attributeName to set attributes values.
$dn
dn of the opened object
save()
This function saves the object in the LDAP.
$mainTab
Is this plugin the main tab, the one that handle the object itself.
static genLockedMessage(array $locks, bool $allowReadonly=FALSE, string $action=NULL)
Generate a lock message.
Definition: class_Lock.inc:331
& get_userinfo()
Return the current userinfo object.
Definition: functions.inc:312
const DEBUG_SHELL
create_unique_dn(string $attribute, string $base)
Create unique DN.
move(string $src_dn, string $dst_dn)
Move ldap entries from one place to another.
getAclBase(bool $callParent=TRUE)
Get LDAP base to use for ACL checks.
static editButton($escape=TRUE)
Text for an edit button.
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
static plInfo()
Return plugin informations for acl handling.
static display($title, string $message, int $type=INFO_DIALOG, array $trace=[])
Display a message dialog.
Error returned by any method of SimplePlugin.
static addFeaturesButton($name)
Display Add features button.
static set($name, $value)
Set a value in a session.
acl_is_writeable($attribute, bool $skipWrite=FALSE)
Can we write the attribute.
save_object()
This function handle $_POST informations.
Error returned by a hook called from SimplePlugin.
cleanup()
Remove attributes, empty arrays, arrays single attributes that do not differ.
$ldap_error
Last LDAP error (used by logging calls from post_* methods)
static passwordProtect(string $hookCommand=NULL)
This function protect the clear string password by replacing char.
static ls($types, $attrs=NULL, string $ou=NULL, string $filter='', bool $checkAcl=FALSE, string $scope='subtree', bool $templateSearch=FALSE, bool $sizeLimit=FALSE)
Get list of object of objectTypes from $types in $ou.
static add($object, string $user=NULL)
Add a lock for object(s)
Definition: class_Lock.inc:47
__construct(string $dn=NULL, $object=NULL, $parent=NULL, bool $mainTab=FALSE, array $attributesInfo=NULL)
constructor
& get_smarty()
Get global smarty object.
Definition: functions.inc:324
$attributesAccess
This attribute store references toward attributes.
static generatePlProvidedAcls(array $attributesInfo, bool $operationalAttributes=NULL)
This function generate the needed ACLs for a given attribtues array.
openDialog(FusionDirectoryDialog $dialog)
This function allows you to open a dialog.
__get($name)
This function allows to use the syntax $plugin->attributeName to get attributes values.
$preInitAttributes
Attributes that needs to be initialized before the others.
static featuresDisabled($name, array $depends=[], array $conflicts=[])
List the features settings disabled.
isActive()
Test whether a tab is active.
__isset($name)
This function allows to use the syntax isset($plugin->attributeName)
Error returned by an LDAP operation called from SimplePlugin.
set_acl_category(string $category)
Set acl category.
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.
show_header(string $buttonHtmlText, string $htmlText, bool $plugin_enabled, bool $button_disabled=FALSE, string $name='modify_state')
Show header message for tab dialogs.
Parent class for all exceptions thrown in FusionDirectory.
array_differs(array $src, array $dst)
Definition: functions.inc:1066
getObjectClassFilter()
This function returns an LDAP filter for this plugin object classes.
static deleteByObject($object)
Remove a lock for object(s)
Definition: class_Lock.inc:135
setTemplate(bool $bool)
Sets whether the opened objet is a template.
acl_is_moveable(string $base=NULL)
Can we move the object.
$read_only
Used when the entry is opened as "readonly" due to locks.
fillHookAttrs(array &$addAttrs)
Fill attributes which may be used in hooks.
$is_account
Mark plugin as account.
static fieldsFromLDAP(array $template_attrs)
Translate template attrs into $attrs as if taken from LDAP.
Fatal error class. Does not extend FusionDirectoryError.
$needEditMode
Are we executed in a edit-mode environment? (this is FALSE if we&#39;re called from management, TRUE if we&#39;re called from a main.inc)
$orig_dn
original dn of the opened object
static fieldsToLDAP(array $template_attrs, array $attrs)
Translate $attrs into template attrs.
static un_set($name)
Unset a session.
closeDialog()
This function closes the dialog.
static cancelButton($escape=TRUE)
Text for a cancel button.
readPost()
This function handle $_POST informations.
acl_is_removeable(string $base=NULL)
Can we delete the object.
static noValidExtension($name)
Display error about invalid extension from account.
$is_template
Mark plugin as template.
render()
This function display the plugin and return the html code.
mergeObjectClasses(array $oc)
Merge in objectClasses needed by this tab.
attrIsReadable($attr)
Check if logged in user have enough right to read this attribute value.
getRequiredAttributes()
Returns list of required LDAP attributes.
adapt_from_template(array $attrs, array $skip=[])
Adapt from template.
$attrs
Represent temporary LDAP data.
attrIsWriteable($attr)
Check if logged in user have enough right to write this attribute value.
$attributesInfo
This attribute store all information about attributes.
$displayHeader
Do we want a header allowing to able/disable this plugin.
static okButton($escape=TRUE)
Text for a ok button.
static permModify($name='', $field='')
Display that we have no permission to modify an object.
array_merge_unique(array $ar1, array $ar2)
Definition: functions.inc:275
deserializeValues(array $values, bool $checkAcl=TRUE)
Deserialize values.
handle_pre_events(string $mode, array $addAttrs=[])
Forward command execution requests to the pre hook execution method.
Parent class for all errors in FusionDirectory.
check()
This function checks the attributes values and yell if something is wrong.
showInTemplate(string $attr, array $templateAttrs)
Returns TRUE if this attribute should be asked in the creation by template dialog.
readOnly()
Indicates if this object is opened as read-only (because of locks)
array_remove_entries_ics(array $needles, array $haystack)
Definition: functions.inc:257
static link(string $dn, string $type, string $subaction='', $text=NULL, bool $icon=TRUE, bool $link=TRUE)
resetCopyInfos()
This function is called on the copied object to set its dn to where it will be saved.
static is_set($name)
Check if the name of the session is set.
static checkFields($attrs)
Check template fields.
aclHasPermissions()
Test if there are ACLs for this plugin.
This class allow to handle easily a String LDAP attribute.
is_modal_dialog()
Is there a modal dialog opened.
post_save()
This function is called after LDAP save to do some post operations and logging.
static mainInc($classname=NULL, $entry_dn=NULL, $tabs=FALSE, $edit_mode=TRUE, $objectType=FALSE)
This function is the needed main.inc for plugins that are not used inside a management class...
static parseString(string $string, array $attrs, $escapeMethod=NULL, string $unique=NULL, string $target=NULL)
Parse template masks in a single string.
class_available($name)
Checks if a class is available.
Definition: functions.inc:92
$entryCSN
Object entry CSN.
static removeFeaturesButton($name)
Display Remove features button.
aclGetPermissions($attribute='0', string $base=NULL, bool $skipWrite=FALSE)
Get the acl permissions for an attribute or the plugin itself.
static clickEditToChange()
Display : Click the "Edit" button below to change information in this dialog.