FusionDirectory
class_Attribute.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 
23 
32 class Attribute
33 {
34  /* \brief Name of this attribute in the LDAP */
35  private $ldapName;
36  /* \brief Label of this attribute in the form */
37  private $label;
38  /* \brief Description of this attribute */
39  private $description;
40  /* \brief Is this attribute mandatory */
41  private $required;
42  /* \brief Should this attribute be saved into the LDAP */
43  private $inLdap = TRUE;
44 
45  /* \brief Should this attribute be unique
46  * FALSE -> no unicity check
47  * one -> unicity check in the same base -> broken right now because of object ous
48  * sub -> unicity check in the same subtree
49  * \__> this should not be used as it’s breaking reciprocity
50  * whole -> unicity check in the whole LDAP
51  */
52  private $unique = FALSE;
53 
54  /* \brief Filter to use when checking unicity
55  * Most of the time this is NULL and filter is computed from plugin objectTypes and objectClasses */
56  private $uniqueFilter = NULL;
57 
58  /* \brief Prefix for the html id */
59  protected $htmlid_prefix = '';
60  /* \brief Should this attribute be shown */
61  protected $visible = TRUE;
62  /* \brief Name of the ACL to use, empty if we need our own */
63  protected $acl;
64  /* \brief Is this attribute disabled */
65  protected $disabled = FALSE;
66  /* \brief Should this attribute submit formular when changing value
67  * If this is not a boolean it is a string containing a method name to call on the plugin when changing value */
68  protected $submitForm = FALSE;
69  /* \brief Value of this attribute */
70  protected $value;
71  /* \brief Value we read from POST */
72  protected $postValue;
73  /* \brief Default value of this attribute */
74  protected $defaultValue;
75  /* \brief Initial value of this attribute */
76  protected $initialValue;
77  /* \brief Reference to the plugin */
78  protected $plugin;
79  /* \brief Array of attributes to manage (prefix => value => attribute)
80  * Prefix should be 'erase' or 'disable' */
81  protected $managedAttributes = [];
82  /* \brief Array of multiple values groups for managed attributes */
83  protected $managedAttributesMultipleValues = [];
84 
85  /* \bried Array of booleans telling for each managing attributes if he's disabling us */
86  protected $managingAttributesOrders = [];
87 
88  /* \bried If this is TRUE it means this attribute is not directly submitted with the form
89  * but is part of a multivalue attribute.
90  * It means it should not be set as required in the HTML form for instance.
91  */
92  protected $isSubAttribute = FALSE;
93 
103  function __construct (string $label, string $description, string $ldapName, bool $required = FALSE, $defaultValue = '', string $acl = '')
104  {
105  $this->label = $label;
106  $this->description = $description;
107  $this->ldapName = $ldapName;
108  $this->required = $required;
109  $this->defaultValue = $defaultValue;
110  $this->value = $defaultValue;
111  $this->postValue = $this->value;
112  $this->acl = $acl;
113  $this->plugin = NULL;
114  }
115 
120  function setParent (&$plugin)
121  {
122  $this->plugin = $plugin;
123  $this->manageAttributes($this->getValue());
124  }
125 
130  function getParent ()
131  {
132  return $this->plugin;
133  }
134 
135  function setIsSubAttribute (bool $bool)
136  {
137  $this->isSubAttribute = $bool;
138  }
139 
140  function setInLdap (bool $inLdap)
141  {
142  $this->inLdap = $inLdap;
143  }
144 
145  function setVisible (bool $visible)
146  {
147  $this->visible = $visible;
148  }
149 
150  function isVisible (): bool
151  {
152  return $this->visible;
153  }
154 
155  function isTemplate (): bool
156  {
157  return (is_object($this->plugin) && $this->plugin->is_template);
158  }
159 
160  function setUnique ($unique, string $filter = NULL)
161  {
162  if ($unique === TRUE) {
163  trigger_error('Deprecated fallback');
164  $this->unique = 'one';
165  } else {
166  $this->unique = $unique;
167  }
168  $this->uniqueFilter = $filter;
169  }
170 
171  function getUnique (): string
172  {
173  return $this->unique;
174  }
175 
176  function isInLdap (): bool
177  {
178  return $this->inLdap;
179  }
180 
185  public function getExample ()
186  {
187  return NULL;
188  }
189 
190  function checkValue ($value)
191  {
192  /* Should throw InvalidValueException if needed */
193  }
194 
195  function setValue ($value)
196  {
197  $this->checkValue($value);
198  $old_value = $this->value;
199  $this->value = $value;
200  if (($this->submitForm !== FALSE) && ($this->submitForm !== TRUE) && ($old_value != $value) && is_object($this->plugin)) {
201  $func = $this->submitForm;
202  $this->plugin->$func();
203  }
204  $this->manageAttributes($this->value);
205  }
206 
208  function setPostValue ($value)
209  {
210  if ($this->isVisible()) {
211  $this->postValue = $this->fixPostValue($value);
212  $this->manageAttributes($this->postValue);
213  }
214  }
215 
217  function fixPostValue ($value)
218  {
219  return $value;
220  }
221 
224  function resetToDefault ()
225  {
226  $this->setValue($this->defaultValue);
227  }
228 
229  function getValue ()
230  {
231  return $this->value;
232  }
233 
234  /* Return the value as an array of values to be displayed in a table columns */
235  function getArrayValue (): array
236  {
237  return [$this->displayValue($this->getValue())];
238  }
239 
240  function getLdapName (): string
241  {
242  return $this->ldapName;
243  }
244 
245  function getHtmlId (): string
246  {
247  return $this->htmlid_prefix.\userinfo::sanitizeAttributeName($this->getLdapName());
248  }
249 
250  /* html id to put in the "for" attribute of our "label" tag */
251  function getForHtmlId (): string
252  {
253  return $this->getHtmlId();
254  }
255 
256  function getLabel (): string
257  {
258  return $this->label;
259  }
260 
261  function getDescription (): string
262  {
263  return $this->description;
264  }
265 
266  function getAcl (): string
267  {
268  if (empty($this->acl)) {
269  return $this->getHtmlId();
270  } else {
271  return $this->acl;
272  }
273  }
274 
275  function setAcl (string $acl)
276  {
277  $this->acl = $acl;
278  }
279 
280  function isRequired (): bool
281  {
282  return $this->required;
283  }
284 
285  function setRequired (bool $bool)
286  {
287  $this->required = $bool;
288  }
289 
290  protected function setLabel (string $label)
291  {
292  $this->label = $label;
293  }
294 
295  protected function setDescription (string $description)
296  {
297  $this->description = $description;
298  }
299 
300  function setDisabled (bool $disabled)
301  {
302  $this->disabled = $disabled;
303  }
304 
305  function isDisabled (): bool
306  {
307  return $this->disabled;
308  }
309 
310  function setManagingDisabled ($sender, $value)
311  {
312  $this->managingAttributesOrders[$sender] = $value;
313  $this->setDisabled(array_reduce($this->managingAttributesOrders,
314  function ($a, $b)
315  {
316  return $a || $b;
317  }
318  ));
319  }
320 
321  function setSubmitForm ($submitForm)
322  {
323  $this->submitForm = $submitForm;
324  }
325 
328  function loadValue (array $attrs)
329  {
330  if ($this->inLdap) {
331  $this->loadAttrValue($attrs);
332  }
333  $this->initialValue = $this->getValue();
334  }
335 
342  protected function loadAttrValue (array $attrs)
343  {
344  if (isset($attrs[$this->getLdapName()])) {
345  $this->setValue($this->inputValue($attrs[$this->getLdapName()][0]));
346  }
347  }
348 
349  function getInitialValue ()
350  {
351  return $this->initialValue;
352  }
353 
354  function setInitialValue ($value)
355  {
356  $this->initialValue = $value;
357  }
358 
359  function hasChanged (): bool
360  {
361  return ($this->getValue() !== $this->initialValue);
362  }
363 
364  function displayValue ($value): string
365  {
366  return $value;
367  }
368 
373  function inputValue ($ldapValue)
374  {
375  return $ldapValue;
376  }
377 
378  function setDefaultValue ($value)
379  {
380  $this->defaultValue = $value;
381  }
382 
386  function setManagedAttributes (array $mAttributes)
387  {
388  if (isset($mAttributes['multiplevalues'])) {
389  $this->managedAttributesMultipleValues = $mAttributes['multiplevalues'];
390  unset($mAttributes['multiplevalues']);
391  } else {
392  $this->managedAttributesMultipleValues = [];
393  }
394  $this->managedAttributes = $mAttributes;
395  $this->manageAttributes($this->getValue());
396  }
397 
398  protected function isValueManagingValue ($myvalue, $mavalue): bool
399  {
400  if (isset($this->managedAttributesMultipleValues[$mavalue])) {
401  return in_array($myvalue, $this->managedAttributesMultipleValues[$mavalue]);
402  } else {
403  return ($myvalue == $mavalue);
404  }
405  }
406 
407  function manageAttributes ($myvalue): bool
408  {
409  if ($this->plugin === NULL) {
410  return FALSE;
411  }
412  foreach ($this->managedAttributes as $array) {
413  foreach ($array as $value => $attributes) {
414  foreach ($attributes as $attribute) {
415  $disable = $this->isValueManagingValue($myvalue, $value);
416  $this->plugin->attributesAccess[$attribute]->setManagingDisabled($this->getLdapName(), $disable);
417  }
418  }
419  }
420  return TRUE;
421  }
422 
425  function loadPostValue ()
426  {
427  if ($this->isVisible()) {
428  $this->postValue = $this->value;
429  if (isset($_POST[$this->getHtmlId()])) {
430  $this->setPostValue($_POST[$this->getHtmlId()]);
431  }
432  }
433  }
434 
437  function applyPostValue ()
438  {
439  if (!$this->disabled && $this->isVisible()) {
440  $this->setValue($this->postValue);
441  }
442  }
443 
446  function computeLdapValue ()
447  {
448  return $this->getValue();
449  }
450 
453  function fillLdapValue (array &$attrs)
454  {
455  if ($this->inLdap) {
456  $ldapValue = $this->computeLdapValue();
457  if ($ldapValue !== '') {
458  $attrs[$this->getLdapName()] = $ldapValue;
459  } else {
460  $attrs[$this->getLdapName()] = [];
461  }
462  }
463  }
464 
467  function fillLdapValueHook (array &$attrs)
468  {
469  foreach ($this->managedAttributes as $prefix => $array) {
470  if ($prefix != 'erase') {
471  continue;
472  }
473  foreach ($array as $value => $attributes) {
474  $myvalue = $this->getValue();
475  $erase = $this->isValueManagingValue($myvalue, $value);
476  if (!$erase) {
477  continue;
478  }
479  foreach ($attributes as $attribute) {
480  $attrs[$attribute] = [];
481  }
482  }
483  }
484  }
485 
488  function check ()
489  {
490  global $config;
491  $currentValue = $this->getValue();
492  if ($this->isRequired() && !$this->disabled && (($currentValue === "") || ($currentValue === []))) {
493  return new \SimplePluginCheckError(
494  $this,
495  \msgPool::required($this->getLabel())
496  );
497  } elseif (($this->unique !== FALSE) && !$this->disabled) {
498  $ldapValue = $this->computeLdapValue();
499  if (($ldapValue === "") || ($ldapValue === [])) {
500  return;
501  }
502  $ldap = $config->get_ldap_link();
503  $base = $config->current['BASE'];
504  if ($this->unique !== 'whole') {
505  if (isset($this->plugin->base) && !empty($this->plugin->base)) {
506  $base = $this->plugin->base;
507  } elseif (isset($this->plugin->dn) && !empty($this->plugin->dn) && ($this->plugin->dn != 'new')) {
508  $base = dn2base($this->plugin->dn);
509  }
510  }
511  if (is_array($ldapValue)) {
512  $filter = '(|('.$this->getLdapName().'='.join(')('.$this->getLdapName().'=', array_map('ldap_escape_f', $ldapValue)).'))';
513  } else {
514  $filter = '('.$this->getLdapName().'='.ldap_escape_f($ldapValue).')';
515  }
516  $infos = \pluglist::pluginInfos(get_class($this->plugin));
517  if ($this->uniqueFilter === NULL) {
518  $objectTypeFilters = array_map(
519  function ($key, $ot)
520  {
521  if (!is_numeric($key)) {
522  $ot = $key;
523  }
524  try {
525  $oinfos = \objects::infos($ot);
526  return $oinfos['filter'];
527  } catch (\NonExistingObjectTypeException $e) {
528  return '';
529  }
530  },
531  array_keys($infos['plObjectType']),
532  array_values($infos['plObjectType'])
533  );
534  $filters = [];
535  if (!empty($objectTypeFilters)) {
536  $filters[] = '(|'.implode($objectTypeFilters).')';
537  }
538  $pluginFilter = call_user_func([get_class($this->plugin), 'getLdapFilter']);
539  if (!empty($pluginFilter)) {
540  $filters[] = $pluginFilter;
541  }
542  } else {
543  $filters = [$this->uniqueFilter];
544  }
545  $filter = '(&'.$filter.implode($filters).')';
546  $branches = array_filter(
547  array_map(
548  function ($key, $ot)
549  {
550  if (!is_numeric($key)) {
551  $ot = $key;
552  }
553  try {
554  $oinfos = \objects::infos($ot);
555  return $oinfos['ou'];
556  } catch (\NonExistingObjectTypeException $e) {
557  return FALSE;
558  }
559  },
560  array_keys($infos['plObjectType']),
561  array_values($infos['plObjectType'])
562  ),
563  function ($ou)
564  {
565  return ($ou !== FALSE);
566  }
567  );
568  $ldap->cd($base);
569  $ldap->search($filter, [$this->getLdapName()]);
570  while ($attrs = $ldap->fetch()) {
571  if ($attrs['dn'] != $this->plugin->dn) {
572  $dn_base = preg_replace('/^[^,]+,/', '', $attrs['dn']);
573  $found = FALSE;
574  if ($this->unique === 'one') {
575  /* Check that this entry is in a concerned branch */
576  foreach ($branches as $branch) {
577  if ($branch.$base == $dn_base) {
578  $dn_base = preg_replace('/^'.preg_quote($branch, '/').'/', '', $dn_base);
579  $found = TRUE;
580  break;
581  }
582  }
583  } elseif ($this->uniqueFilter === NULL) { /* whole (or sub) */
584  /* Check that this entry is in a concerned branch */
585  foreach ($branches as $branch) {
586  if (preg_match('/^'.preg_quote($branch, '/').'/', $dn_base)) {
587  $dn_base = preg_replace('/^'.preg_quote($branch, '/').'/', '', $dn_base);
588  $found = TRUE;
589  break;
590  }
591  }
592  if (!in_array($dn_base, $config->getDepartmentList())) {
593  continue;
594  }
595  } else {
596  $found = TRUE;
597  }
598  if (!$found) {
599  continue;
600  }
601 
602  return new \SimplePluginCheckError(
603  $this,
604  \msgPool::duplicated($this->getLabel(), $attrs['dn'])
605  );
606  }
607  }
608  if (class_available('archivedObject')) {
609  $filter = \archivedObject::buildUniqueSearchFilter($this->getLdapName(), $ldapValue);
610  $ldap->search($filter, [$this->getLdapName()]);
611  if ($attrs = $ldap->fetch()) {
612  return new \SimplePluginCheckError(
613  $this,
614  \msgPool::duplicated($this->getLabel(), $attrs['dn'])
615  );
616  }
617  }
618  }
619  }
620 
631  function renderAttribute (array &$attributes, bool $readOnly, bool $readable, bool $writable)
632  {
633  if ($this->visible) {
634  if ($readOnly) {
635  $currentValue = $this->getValue();
636  if (is_array($currentValue)) {
637  $input = '{literal}'.implode('<br/>', array_map('htmlescape', $currentValue)).'{/literal}';
638  } else {
639  $input = '{literal}'.htmlescape($currentValue).'{/literal}';
640  }
641  } elseif ($this->isTemplate()) {
642  $input = $this->renderTemplateInput();
643  } else {
644  $input = $this->renderFormInput();
645  }
646  $attributes[$this->getLdapName()] = [
647  'htmlid' => $this->getForHtmlId(),
648  'label' => '{literal}'.htmlescape($this->getLabel()).'{/literal}',
649  'description' => ($this->isRequired() ? sprintf(_("%s (required)"), $this->getDescription()) : $this->getDescription()),
650  'input' => $input,
651  'subattribute' => $this->isSubAttribute,
652  'required' => $this->isRequired(),
653  'readable' => $readable,
654  'writable' => $writable,
655  ];
656  }
657  }
658 
664  function serializeAttribute (array &$attributes, bool $form = TRUE)
665  {
666  if (!$form || $this->visible) {
667  $class = get_class($this);
668  $type = [];
669  while ($class != FALSE) {
670  $type[] = $class;
671  $class = get_parent_class($class);
672  }
673  $infos = [
674  'htmlid' => $this->getHtmlId(),
675  'label' => $this->getLabel(),
676  'required' => $this->isRequired(),
677  'disabled' => $this->disabled,
678  'description' => $this->getDescription(),
679  'value' => $this->serializeValue(),
680  'default' => $this->serializeValue($this->defaultValue),
681  'type' => $type,
682  ];
683  if (!$form) {
684  $infos['inldap'] = $this->isInLdap();
685  $infos['visible'] = $this->visible;
686  $infos['htmlids'] = $this->htmlIds();
687  }
688  $attributes[$this->getLdapName()] = $infos;
689  }
690  }
691 
696  function deserializeValue ($value)
697  {
698  if ($this->disabled) {
699  return new \SimplePluginError(
700  $this,
701  htmlescape(sprintf(_('Attribute %s is disabled, its value could not be set'), $this->getLdapName()))
702  );
703  }
704  $this->setValue($value);
705  }
706 
711  function serializeValue ($value = NULL)
712  {
713  if ($value === NULL) {
714  $value = $this->getValue();
715  }
716  return $value;
717  }
718 
723  function renderAcl (string $display): string
724  {
725  return '{render aclName="'.$this->getAcl().'" acl=$'.$this->getAcl()."ACL}\n$display\n{/render}";
726  }
727 
730  function getAclInfo ()
731  {
732  if (empty($this->acl)) {
733  return [
734  'name' => $this->getHtmlId(),
735  'desc' => $this->getDescription()
736  ];
737  } else {
738  /* If acl is not empty, we use an acl that is not ours, we have no acl to create */
739  return FALSE;
740  }
741  }
742 
743  protected function changeStateJS (): string
744  {
745  return implode('', array_map(
746  function ($id)
747  {
748  return 'changeState('.json_encode($id).');';
749  },
750  $this->htmlIds()
751  ));
752  }
753 
754  public function htmlIds (): array
755  {
756  return [$this->getHtmlId()];
757  }
758 
759  protected function managedAttributesJS (): string
760  {
761  $js = '';
762  $id = $this->getHtmlId();
763  foreach ($this->managedAttributes as $array) {
764  foreach ($array as $value => $attributes) {
765  if (isset($this->managedAttributesMultipleValues[$value])) {
766  $js .= 'disableAttributes = inArray(document.getElementById('.json_encode($id).').value,'.json_encode($this->managedAttributesMultipleValues[$value]).');';
767  } else {
768  $js .= 'disableAttributes = (document.getElementById('.json_encode($id).').value == '.json_encode($value).');'."\n";
769  }
770  foreach ($attributes as $attribute) {
771  foreach ($this->plugin->attributesAccess[$attribute]->htmlIds() as $htmlId) {
772  $js .= 'if (document.getElementById('.json_encode($htmlId).')) { document.getElementById('.json_encode($htmlId).').disabled = disableAttributes; }'."\n";
773  }
774  }
775  }
776  }
777  return $js;
778  }
779 
780  function renderFormInput (): string
781  {
782  throw new \FusionDirectoryException('Not implemented in base class (abstract method)');
783  }
784 
785  function renderTemplateInput (): string
786  {
787  return $this->renderFormInput();
788  }
789 
790  function foreignKeyUpdate ($oldvalue, $newvalue, array $source)
791  {
792  if ($source['MODE'] == 'move') {
793  if ($newvalue === NULL) {
794  $this->resetToDefault();
795  } elseif ($source['FIELD'] == 'dn') {
796  $initialValue = $this->getInitialValue();
797  $initialValue = preg_replace('/'.preg_quote($oldvalue, '/').'$/', $newvalue, $initialValue, -1, $count);
798  if ($count > 0) {
799  $this->setValue($initialValue);
800  }
801  } elseif ($this->getInitialValue() == $oldvalue) {
802  $this->setValue($newvalue);
803  }
804  }
805  }
806 
807  function foreignKeyCheck ($value, array $source): bool
808  {
809  return ($this->getValue() == $value);
810  }
811 
812  protected function renderInputField (string $type, string $name, array $attributes = [], bool $smartyEscape = TRUE): string
813  {
814  $input = '<input type="'.htmlescape($type).'" '.
815  'name="'.htmlescape($name).'" id="'.htmlescape($name).'"'.
816  ($this->disabled ? ' disabled="disabled"' : '');
817  foreach ($attributes as $label => $value) {
818  $input .= ' '.$label.'="'.htmlescape($value).'"';
819  }
820  $input .= '/>';
821  return ($smartyEscape ? '{literal}'.$input.'{/literal}' : $input);
822  }
823 }
htmlescape(string $str)
Escape string for HTML output.
Definition: php_setup.inc:32
fillLdapValue(array &$attrs)
Fill LDAP value in the attrs array.
setParent(&$plugin)
Set the parent plugin for this attribute.
deserializeValue($value)
Apply value from RPC requests.
fillLdapValueHook(array &$attrs)
Post-modify the attrs array if needed (used for erasing managed attributes)
serializeAttribute(array &$attributes, bool $form=TRUE)
Serialize this attribute for RPC requests.
static required($name)
Display error about required field empty.
loadValue(array $attrs)
If in LDAP, loads this attribute value from the attrs array.
fixPostValue($value)
In case a treatment is needed on POST content.
loadPostValue()
Update this attributes postValue depending of the $_POST values.
loadAttrValue(array $attrs)
Loads this attribute value from the attrs array if present.
applyPostValue()
Apply this attribute postValue in value if this attribute is enabled.
dn2base($dn, $ou=NULL)
Return the base of a given DN.
Definition: functions.inc:614
static duplicated($name, $dn=NULL)
Display error about existing entry in the system.
renderAttribute(array &$attributes, bool $readOnly, bool $readable, bool $writable)
Render this attribute form input(s)
check()
Check the correctness of this attribute.
This class contains all the function needed to manage acl.
Definition: class_acl.inc:30
serializeValue($value=NULL)
Serialize value for RPC requests.
getAclInfo()
Get ACL information about the ACL we need to create.
renderAcl(string $display)
Add ACL information around display.
setManagedAttributes(array $mAttributes)
Set a list of attributes that are managed by this attributes. See FusionDirectory wiki for detailed d...
inputValue($ldapValue)
Return the ldap value in the correct intern format value.
getParent()
Get parent plugin instance, if any.
resetToDefault()
Reset this attribute to its default value.
This class allow to handle easily any kind of LDAP attribute.
class_available($name)
Checks if a class is available.
Definition: functions.inc:92
__construct(string $label, string $description, string $ldapName, bool $required=FALSE, $defaultValue='', string $acl='')
The constructor of Attribute.