class_simplePlugin.inc 69.9 KB
Newer Older
1
2
3
<?php
/*
  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
4
5

  Copyright (C) 2012-2019  FusionDirectory
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/

22
23
24
25
26
/*!
 * \file class_simplePlugin.inc
 * Source code for the class simplePlugin
 */

27
28
29
/*! \brief This class is made for easy plugin creation for editing LDAP attributes
 *
 */
30
class simplePlugin implements SimpleTab
31
{
32
  /*! \brief This attribute store all information about attributes */
33
  public $attributesInfo;
34

35
36
37
38
  /*! \brief This attribute store references toward attributes
   *
   * associative array that stores attributeLdapName => reference on object
   */
39
  public $attributesAccess = [];
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

  /*!
    \brief Mark plugin as account

    Defines whether this plugin is defined as an account or not.
    This has consequences for the plugin to be saved from tab
    mode. If it is set to 'FALSE' the tab will call the delete
    function, else the save function. Should be set to 'TRUE' if
    the construtor detects a valid LDAP object.

    \sa simplePlugin::is_this_account()
   */
  public $is_account            = FALSE;
  public $initially_was_account = FALSE;
  public $ignore_account        = FALSE;
55

56
57
58
  public $acl_base      = '';
  public $acl_category  = '';

59
60
61
62
63
64
65
66
67
68
69
70
71
  /*! \brief dn of the opened object */
  public $dn      = '';

  /*! \brief original dn of the opened object */
  public $orig_dn = '';

  /*!
   * \brief Reference to parent object
   *
   * This variable is used when the plugin is included in tabs
   * and keeps reference to the tab class. Communication to other
   * tabs is possible by 'name'. So the 'fax' plugin can ask the
   * 'userinfo' plugin for the fax number.
72
   *
73
   * \sa simpleTabs
74
   */
75
  public $parent = NULL;
76

77
78
79
80
81
82
  /*!
    \brief Mark plugin as template

    Defines whether we are editing a template or a normal object.
    Has consequences on the way execute() shows the formular and how
    save() puts the data to LDAP.
83
   */
84
85
  public $is_template    = FALSE;

86
87
88
  /*!
    \brief Represent temporary LDAP data

89
    This should only be used internally.
90
   */
91
  public $attrs = [];
92
93

  /*! \brief The objectClasses set by this tab */
94
  protected $objectclasses = [];
95
96

  /*! \brief The state of the attributes when we opened the object */
97
  protected $saved_attributes = [];
98

99
100
101
102
  /*! \brief Do we want a header allowing to able/disable this plugin */
  protected $displayHeader = FALSE;

  /*! \brief Is this plugin the main tab, the one that handle the object itself */
103
104
105
106
  protected $mainTab = FALSE;

  protected $header = "";

107
108
  protected $templatePath;

109
110
  protected $dialog = FALSE;

111
112
113
114
  /*! \brief Are we executed in a edit-mode environment? (this is FALSE if we're called from management, TRUE if we're called from a main.inc)
   */
  protected $needEditMode = FALSE;

115
  /*! \brief Attributes that needs to be initialized before the others */
116
  protected $preInitAttributes = [];
117

118
119
120
121
122
  /*! \brief FALSE to disable inheritance. Array like array ('objectClass' => 'attribute') to specify oc of the groups it might be inherited from
   */
  protected $inheritance      = FALSE;
  protected $member_of_group  = FALSE;
  protected $editing_group    = NULL;
123
  protected $group_attrs      = [];
124

125
126
127
  /*! \brief Used when the entry is opened as "readonly" due to locks */
  protected $read_only = FALSE;

128
129
130
  /*! \brief Last LDAP error (used by logging calls from post_* methods) */
  protected $ldap_error;

131
132
133
134
135
136
137
138
139
  /*!
   * \brief Object entry CSN
   *
   * If an entry was edited while we have edited the entry too,
   * an error message will be shown.
   * To configure this check correctly read the FAQ.
   */
  protected $entryCSN = '';

Côme Chilliet's avatar
Côme Chilliet committed
140
141
  private $hadSubobjects = FALSE;

142
143
144
145
  /*! \brief constructor
   *
   *  \param string $dn The dn of this instance
   *  \param Object $object An object to copy values from
146
147
   *  \param Object $parent A parent instance, usually a simpleTabs instance.
   *  \param boolean $mainTab Whether or not this is the main tab
148
149
150
   *  \param array $attributesInfo An attributesInfo array, if NULL, getAttributesInfo will be used.
   *
   */
151
  function __construct (string $dn = NULL, $object = NULL, $parent = NULL, bool $mainTab = FALSE, array $attributesInfo = NULL)
152
  {
153
    global $config;
154

155
    $this->dn       = $dn;
156
157
158
    $this->parent   = $parent;
    $this->mainTab  = $mainTab;

159
160
161
162
    try {
      $plInfo = pluglist::pluginInfos(get_class($this));
    } catch (UnknownClassException $e) {
      /* May happen in special cases like setup */
163
      $plInfo = [];
164
    }
165

166
    if (empty($this->objectclasses) && isset($plInfo['plObjectClass'])) {
167
168
169
      $this->objectclasses = $plInfo['plObjectClass'];
    }

170
171
172
173
174
175
176
177
    if ($attributesInfo === NULL) {
      $attributesInfo = $this->getAttributesInfo();
    }
    if (!$this->displayHeader) {
      // If we don't display the header to activate/deactive the plugin, that means it's always activated
      $this->ignore_account = TRUE;
    }

178
    $this->attributesInfo = [];
179
    foreach ($attributesInfo as $section => $sectionInfo) {
180
      $attrs = [];
181
182
183
184
185
186
187
188
189
190
191
192
      foreach ($sectionInfo['attrs'] as $attr) {
        $name = $attr->getLdapName();
        if (isset($attrs[$name])) {
          // We check that there is no duplicated attribute name
          trigger_error("Duplicated attribute LDAP name '$name' in a simplePlugin subclass");
        }
        // We make so that attribute have their LDAP name as key
        // That allow the plugin to use $this->attributesInfo[$sectionName]['attrs'][$myLdapName] to retreive the attribute info.
        $attrs[$name] = $attr;
      }
      $sectionInfo['attrs']           = $attrs;
      $this->attributesInfo[$section] = $sectionInfo;
193
194
195
196
197
198
199
200
      foreach ($this->attributesInfo[$section]['attrs'] as $name => $attr) {
        if (isset($this->attributesAccess[$name])) {
          // We check that there is no duplicated attribute name
          trigger_error("Duplicated attribute LDAP name '$name' in a simplePlugin subclass");
        }
        $this->attributesAccess[$name] =& $this->attributesInfo[$section]['attrs'][$name];
        unset($this->$name);
      }
201
    }
202

203
204
    /* Ensure that we've a valid acl_category set */
    if (empty($this->acl_category)) {
205
206
      if (isset($plInfo['plCategory'])) {
        $c = key($plInfo['plCategory']);
207
        if (is_numeric($c)) {
208
          $c = $plInfo['plCategory'][0];
209
210
211
212
213
214
215
216
        }
        $this->acl_category = $c.'/';
      }
    }

    /* Handle read only */
    if ($this->dn != 'new') {
      /* Check if this entry was opened in read only mode */
217
218
219
220
      if (isset($_POST['open_readonly']) && session::global_is_set('LOCK_CACHE')) {
        $cache = session::get('LOCK_CACHE');
        if (isset($cache['READ_ONLY'][$this->dn])) {
          $this->read_only = TRUE;
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
        }
      }

      /* Save current dn as acl_base */
      $this->acl_base = $this->dn;
    }

    /* Load LDAP data */
    if (($this->dn != 'new' && $this->dn !== NULL) || ($object !== NULL)) {
      /* Load data to 'attrs' */
      if ($object !== NULL) {
        /* From object */
        $this->attrs = $object->attrs;
        if (isset($object->is_template)) {
          $this->setTemplate($object->is_template);
        }
      } else {
        /* From LDAP */
        $ldap = $config->get_ldap_link();
        $ldap->cat($this->dn);
        $this->attrs = $ldap->fetch();
        if (empty($this->attrs)) {
          throw new NonExistingLdapNodeException('Could not open dn '.$this->dn);
        }
245
246
247
248
249
        if ($this->mainTab) {
          /* Make sure that initially_was_account is TRUE if we loaded an LDAP node,
           *  even if it’s missing an objectClass */
          $this->is_account = TRUE;
        }
250
251
252
      }

      /* Set the template flag according to the existence of objectClass fdTemplate */
253
      if (isset($this->attrs['objectClass']) && in_array_ics('fdTemplate', $this->attrs['objectClass'])) {
254
        @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, 'found', 'Template check');
255
256
        $this->setTemplate(TRUE);
        $this->templateLoadAttrs($this->attrs);
257
258
259
260
261
      }

      /* Is Account? */
      if ($this->is_this_account($this->attrs)) {
        $this->is_account = TRUE;
262
        @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, get_class($this), 'Tab active');
263
264
265
      }
    }

266
267
    if (is_array($this->inheritance)) {
      /* Check group membership */
268
269
      $ldap = $config->get_ldap_link();
      $ldap->cd($config->current['BASE']);
270
271
      foreach ($this->inheritance as $oc => $at) {
        if ($this->mainTab) {
272
          $filter = '(&(objectClass='.$oc.')('.$at.'='.ldap_escape_f($this->dn).'))';
273
        } else {
274
          $filter = '(&(objectClass='.$oc.')'.static::getLdapFilter().'('.$at.'='.ldap_escape_f($this->dn).'))';
275
276
277
278
279
280
281
282
283
284
        }
        $ldap->search($filter, $this->attributes);
        if ($ldap->count() == 1) {
          $this->member_of_group = TRUE;
          $attrs = $ldap->fetch();
          $this->group_attrs = $attrs;
          break;
        }
      }
    }
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302

    /* Save initial account state */
    $this->initially_was_account = $this->is_account;

    $this->loadAttributes();

    $this->prepareSavedAttributes();

    $this->orig_dn = $dn;

    if ($this->mainTab) {
      $this->is_account = TRUE;
      $this->entryCSN = getEntryCSN($this->dn);
    }

    if (!isset($this->templatePath)) {
      $this->templatePath = get_template_path('simpleplugin.tpl');
    }
303
304
  }

305
  protected function loadAttributes ()
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
  {
    // We load attributes values
    // First the one flagged as preInit
    foreach ($this->preInitAttributes as $attr) {
      $this->attributesAccess[$attr]->setParent($this);
      $this->attributesAccess[$attr]->loadValue($this->attrs);
    }
    // Then the others
    foreach ($this->attributesInfo as &$sectionInfo) {
      foreach ($sectionInfo['attrs'] as $name => &$attr) {
        if (in_array($name, $this->preInitAttributes)) {
          /* skip the preInit ones */
          continue;
        }
        $attr->setParent($this);
        $attr->loadValue($this->attrs);
      }
      unset($attr);
    }
    unset($sectionInfo);
  }

328
  function is_this_account ($attrs)
329
  {
330
331
332
333
    $result = static::isAccount($attrs);
    if ($result === NULL) {
      if (!empty($this->objectclasses)) {
        trigger_error('Deprecated fallback was used for '.get_called_class().'::is_this_account');
334
      }
335
336
337
338
339
      $found = TRUE;
      foreach ($this->objectclasses as $obj) {
        if (preg_match('/^top$/i', $obj)) {
          continue;
        }
340
        if (!isset($attrs['objectClass']) || !in_array_ics($obj, $attrs['objectClass'])) {
341
342
343
          $found = FALSE;
          break;
        }
344
      }
345
      return $found;
346
    }
347
    return $result;
348
349
  }

350
  function setTemplate (bool $bool)
351
  {
352
    $this->is_template = $bool;
353
354
355
    if ($this->is_template && $this->mainTab) {
      /* Unshift special section for template infos */
      $this->attributesInfo = array_merge(
356
357
358
        [
          '_template' => [
            'class' => ['fullwidth'],
359
            'name'  => _('Template settings'),
360
            'attrs' => [
361
362
              '_template_cn' => new StringAttribute(
                _('Template name'), _('This is the name of the template'),
363
364
                '_template_cn', TRUE,
                '', 'template_cn'
365
              )
366
367
368
369
            ]
          ],
          '_template_dummy' => [
            'class' => ['invisible'],
370
            'name'  => '_template_dummy',
371
372
373
            'attrs' => []
          ]
        ],
374
375
376
377
378
        $this->attributesInfo
      );
      $this->attributesAccess['_template_cn'] =& $this->attributesInfo['_template']['attrs']['_template_cn'];
      $this->attributesAccess['_template_cn']->setInLdap(FALSE);
      $this->attributesAccess['_template_cn']->setValue($this->_template_cn);
379
      $this->attributesAccess['_template_cn']->setParent($this);
380
381
382
383
      unset($this->_template_cn);
    }
  }

384
  protected function templateLoadAttrs (array $template_attrs)
385
386
387
388
389
390
391
  {
    if ($this->mainTab) {
      $this->_template_cn = $template_attrs['cn'][0];
    }
    $this->attrs = templateHandling::fieldsFromLDAP($template_attrs);
  }

392
  protected function templateSaveAttrs ()
393
394
395
396
397
398
399
400
401
  {
    global $config;
    $ldap = $config->get_ldap_link();
    $ldap->cat($this->dn);
    $template_attrs = $ldap->fetch();
    if (!$template_attrs) {
      if (!$this->mainTab) {
        trigger_error('It seems main tab has not been saved.');
      }
402
403
404
405
      $template_attrs = [
        'objectClass'     => ['fdTemplate'],
        'fdTemplateField' => []
      ];
406
407
408
409
410
411
412
413
    }
    $template_attrs = templateHandling::fieldsToLDAP($template_attrs, $this->attrs);
    if ($this->mainTab) {
      $template_attrs['cn'] = $this->_template_cn;
    }
    return $template_attrs;
  }

414
415
416
417
  /*! \brief This function returns an LDAP filter for this plugin object classes
   */
  function getObjectClassFilter ()
  {
418
419
    trigger_error('Deprecated');
    return static::getLdapFilter();
420
421
  }

422
  /*! \brief This function allows to use the syntax $plugin->attributeName to get attributes values
423
   *
424
425
426
   * It calls the getValue method on the concerned attribute
   * It also adds the $plugin->attribtues syntax to get attributes list
   */
427
  public function __get ($name)
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
  {
    if ($name == 'attributes') {
      $plugin = $this;
      return array_filter(array_keys($this->attributesAccess),
        function ($a) use ($plugin)
        {
          return $plugin->attributesAccess[$a]->isInLdap();
        }
      );
    } elseif (isset($this->attributesAccess[$name])) {
      return $this->attributesAccess[$name]->getValue();
    } else {
      /* Calling default behaviour */
      return $this->$name;
    }
  }

  /*! \brief This function allows to use the syntax $plugin->attributeName to set attributes values

    It calls the setValue method on the concerned attribute
   */
449
  public function __set ($name, $value)
450
  {
451
452
453
    if ($name == 'attributes') {
      trigger_error('Tried to set obsolete attribute "attributes" (it is now dynamic)');
    } elseif (isset($this->attributesAccess[$name])) {
454
455
456
457
458
459
460
461
462
463
464
      $this->attributesAccess[$name]->setValue($value);
    } else {
      /* Calling default behaviour */
      $this->$name = $value;
    }
  }

  /*! \brief This function allows to use the syntax isset($plugin->attributeName)

    It returns FALSE if the attribute has an empty value.
   */
465
  public function __isset ($name)
466
467
468
469
470
  {
    if ($name == 'attributes') {
      return TRUE;
    }
    return isset($this->attributesAccess[$name]);
471
472
  }

473
474
  /*! \brief This function returns the dn this object should have
   */
475
  public function compute_dn (): string
476
  {
477
    global $config;
478
    if (!$this->mainTab) {
Côme Bernigaud's avatar
Côme Bernigaud committed
479
480
      msg_dialog::display(_('Fatal error'), _('Only main tab can compute dn'), FATAL_ERROR_DIALOG);
      exit;
481
482
    }
    if (!isset($this->parent) || !($this->parent instanceof simpleTabs)) {
Côme Bernigaud's avatar
Côme Bernigaud committed
483
484
485
486
487
488
489
490
491
      msg_dialog::display(
        _('Fatal error'),
        sprintf(
          _('Could not compute dn: no parent tab class for "%s"'),
          get_class($this)
        ),
        FATAL_ERROR_DIALOG
      );
      exit;
492
493
494
    }
    $infos = $this->parent->objectInfos();
    if ($infos === FALSE) {
Côme Bernigaud's avatar
Côme Bernigaud committed
495
496
497
      msg_dialog::display(
        _('Fatal error'),
        sprintf(
498
          _('Could not compute dn: could not find objectType infos from tab class "%s"'),
Côme Bernigaud's avatar
Côme Bernigaud committed
499
500
501
502
503
          get_class($this->parent)
        ),
        FATAL_ERROR_DIALOG
      );
      exit;
504
505
506
    }
    $attr = $infos['mainAttr'];
    $ou   = $infos['ou'];
507
508
509
510
511
    if (isset($this->base)) {
      $base = $this->base;
    } else {
      $base = $config->current['BASE'];
    }
512
    if ($this->is_template) {
513
      return 'cn='.ldap_escape_dn($this->_template_cn).',ou=templates,'.$ou.$base;
514
    }
515
    return $attr.'='.ldap_escape_dn($this->attributesAccess[$attr]->computeLdapValue()).','.$ou.$base;
516
517
  }

518
  protected function addAttribute (string $section, Attribute $attr)
519
520
521
522
523
524
525
526
  {
    $name = $attr->getLdapName();
    $this->attributesInfo[$section]['attrs'][$name] = $attr;
    $this->attributesAccess[$name] =& $this->attributesInfo[$section]['attrs'][$name];
    $this->attributesAccess[$name]->setParent($this);
    unset($this->$name);
  }

527
  protected function removeAttribute (string $section, string $id)
528
529
530
531
532
  {
    unset($this->attributesInfo[$section]['attrs'][$id]);
    unset($this->attributesAccess[$id]);
  }

533
534
535
536
537
538
539
540
541
  /*!
   * \brief Returns a list of all available departments for this object.
   *
   * If this object is new, all departments we are allowed to create a new object in are returned.
   * If this is an existing object, return all deps we are allowed to move this object to.
   * Used by BaseSelectorAttribute
   *
   * \return array [dn] => "..name"  // All deps. we are allowed to act on.
  */
542
  function get_allowed_bases (): array
543
544
  {
    global $config;
545
    $deps = [];
546
547
548

    /* Is this a new object ? Or just an edited existing object */
    foreach ($config->idepartments as $dn => $name) {
549
550
551
552
      if (
          (!$this->initially_was_account && $this->acl_is_createable($dn)) ||
          ($this->initially_was_account && $this->acl_is_moveable($dn))
        ) {
553
554
555
556
557
558
559
560
561
562
563
564
565
        $deps[$dn] = $name;
      }
    }

    /* Add current base */
    if (isset($this->base) && isset($config->idepartments[$this->base])) {
      $deps[$this->base] = $config->idepartments[$this->base];
    } elseif (strtolower($this->dn) != strtolower($config->current['BASE'])) {
      trigger_error('Cannot return list of departments, no default base found in class '.get_class($this).'. (base is "'.$this->base.'")');
    }
    return $deps;
  }

566
567
568
569
570
  /*!
   * \brief Set acl base
   *
   * \param string $base
   */
571
  function set_acl_base (string $base)
572
573
574
575
576
577
578
579
580
  {
    $this->acl_base = $base;
  }

  /*!
   * \brief Set acl category
   *
   * \param string $category
   */
581
  function set_acl_category (string $category)
582
583
584
585
  {
    $this->acl_category = "$category/";
  }

586
587
588
589
590
591
  /*!
    * \brief Move ldap entries from one place to another
    *
    * \param  string  $src_dn the source DN.
    *
    * \param  string  $dst_dn the destination DN.
592
593
    *
    * \return TRUE on success, error string on failure
594
    */
595
  function move (string $src_dn, string $dst_dn)
596
597
598
599
600
601
602
603
604
605
606
607
608
609
  {
    global $config, $ui;

    /* Do not move if only case has changed */
    if (strtolower($src_dn) == strtolower($dst_dn)) {
      return TRUE;
    }

    /* Try to move with ldap routines */
    $ldap = $config->get_ldap_link();
    $ldap->cd($config->current['BASE']);
    $ldap->create_missing_trees(preg_replace('/^[^,]+,/', '', $dst_dn));
    if (!$ldap->rename_dn($src_dn, $dst_dn)) {
      logging::log('debug', 'Ldap Protocol v3 implementation error, ldap_rename failed.',
610
        "FROM: $src_dn  -- TO: $dst_dn", [], $ldap->get_error());
611
612
      @DEBUG(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, "Rename failed FROM: $src_dn  -- TO:  $dst_dn",
        'Ldap Protocol v3 implementation error. Error:'.$ldap->get_error());
613
      return $ldap->get_error();
614
615
616
617
618
    }

    /* Update userinfo if necessary */
    if (preg_match('/'.preg_quote($src_dn, '/').'$/i', $ui->dn)) {
      $ui_dn = preg_replace('/'.preg_quote($src_dn, '/').'$/i', $dst_dn, $ui->dn);
619
      logging::log('view', 'acl/'.get_class($this), $this->dn, [], 'Updated userinfo dn from "'.$ui->dn.'" to "'.$ui_dn.'"');
620
621
622
623
624
      $ui->dn = $ui_dn;
    }

    /* Check if departments were moved. If so, force the reload of config->departments */
    $ldap->cd($dst_dn);
625
    $ldap->search('(objectClass=gosaDepartment)', ['dn']);
626
627
628
629
630
631
632
633
634
635
    if ($ldap->count()) {
      $config->get_departments();
      $config->make_idepartments();
      $ui->reset_acl_cache();
    }

    $this->handleForeignKeys($src_dn, $dst_dn);
    return TRUE;
  }

636
  function getRequiredAttributes (): array
637
  {
638
    $tmp = [];
639
640
641
642
643
644
645
646
    foreach ($this->attributesAccess as $attr) {
      if ($attr->isRequired()) {
        $tmp[] = $attr->getLdapName();
      }
    }
    return $tmp;
  }

647
648
649
650
651
652
653
  function editing_group ()
  {
    if ($this->editing_group == NULL) {
      if (isset($this->parent)) {
        $this->editing_group = (get_class($this->parent->getBaseObject()) == 'ogroup');
      } else {
        return NULL;
654
655
      }
    }
656
    return $this->editing_group;
657
658
  }

659
  /*! \brief Indicates if this object is opened as read-only (because of locks) */
660
  function readOnly ()
661
662
663
664
  {
    return $this->read_only;
  }

665
666
  /*! \brief This function display the plugin and return the html code
   */
667
  function execute (): string
668
  {
669
    @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->dn, "execute");
670
671

    /* Reset Lock message POST/GET check array, to prevent preg_match errors */
672
673
674
675
    session::set('LOCK_VARS_TO_USE', []);
    session::set('LOCK_VARS_USED_GET', []);
    session::set('LOCK_VARS_USED_POST', []);
    session::set('LOCK_VARS_USED_REQUEST', []);
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693

    $this->displayPlugin  = TRUE;
    $this->header         = "";

    if (is_object($this->dialog)) {
      $dialogResult = $this->dialog->execute();
      if ($dialogResult === FALSE) {
        $this->closeDialog();
      } else {
        $this->header         = $dialogResult;
        $this->displayPlugin  = FALSE;
        return $this->header;
      }
    }

    if ($this->displayHeader) {
      /* Show tab dialog headers */
      if ($this->parent !== NULL) {
694
695
696
697
698
699
700
701
702
        list($disabled, $buttonText, $text) = $this->getDisplayHeaderInfos();
        $this->header = $this->show_header(
          $buttonText,
          $text,
          $this->is_account,
          $disabled,
          get_class($this).'_modify_state'
        );
        if (!$this->is_account) {
703
          $this->displayPlugin = FALSE;
704
          return $this->header.$this->inheritanceDisplay();
705
706
        }
      } elseif (!$this->is_account) {
707
        $plInfo = pluglist::pluginInfos(get_class($this));
708
        $this->header = '<img alt="'._('Error').'" src="geticon.php?context=status&amp;icon=dialog-error&amp;size=16" align="middle"/>&nbsp;<b>'.
709
                        msgPool::noValidExtension($plInfo['plShortName'])."</b>";
710
        $this->displayPlugin = FALSE;
711
        return $this->header.$this->inheritanceDisplay();
712
713
714
715
716
      }
    }

    $smarty = get_smarty();

717
718
719
720
721
722
723
724
725
726
727
728
    $this->renderAttributes(FALSE);
    $smarty->assign("hiddenPostedInput", get_class($this)."_posted");
    if (isset($this->focusedField)) {
      $smarty->assign("focusedField", $this->focusedField);
      unset($this->focusedField);
    } else {
      $smarty->assign("focusedField", key($this->attributesAccess));
    }

    return $this->header.$smarty->fetch($this->templatePath);
  }

729
  public function getDisplayHeaderInfos (): array
730
731
732
733
  {
    $plInfo   = pluglist::pluginInfos(get_class($this));
    $disabled = $this->acl_skip_write();
    if ($this->is_account) {
734
      $depends = [];
735
736
737
738
739
740
741
742
743
744
745
746
747
      if (isset($plInfo['plDepending'])) {
        foreach ($plInfo['plDepending'] as $plugin) {
          if (isset($this->parent->by_object[$plugin]) &&
              $this->parent->by_object[$plugin]->is_account) {
            $disabled       = TRUE;
            $dependPlInfos  = pluglist::pluginInfos($plugin);
            $depends[]      = $dependPlInfos['plShortName'];
          }
        }
      }
      $buttonText = msgPool::removeFeaturesButton($plInfo['plShortName']);
      $text       = msgPool::featuresEnabled($plInfo['plShortName'], $depends);
    } else {
748
749
      $depends    = [];
      $conflicts  = [];
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
      if (isset($plInfo['plDepends'])) {
        foreach ($plInfo['plDepends'] as $plugin) {
          if (isset($this->parent->by_object[$plugin]) &&
              !$this->parent->by_object[$plugin]->is_account) {
            $disabled   = TRUE;
            $dependPlInfos  = pluglist::pluginInfos($plugin);
            $depends[]      = $dependPlInfos['plShortName'];
          }
        }
      }
      if (isset($plInfo['plConflicts'])) {
        foreach ($plInfo['plConflicts'] as $plugin) {
          if (isset($this->parent->by_object[$plugin]) &&
              $this->parent->by_object[$plugin]->is_account) {
            $disabled   = TRUE;
            $conflictPlInfos  = pluglist::pluginInfos($plugin);
            $conflicts[]      = $conflictPlInfos['plShortName'];
          }
        }
      }
      $buttonText = msgPool::addFeaturesButton($plInfo['plShortName']);
      $text       = msgPool::featuresDisabled($plInfo['plShortName'], $depends, $conflicts);
    }
773
    return [$disabled,$buttonText,$text];
774
775
  }

776
777
778
779
780
781
782
  /*!
   * \brief Show header message for tab dialogs
   *
   * \param string $button_text The button text
   *
   * \param string $text The text
   *
783
   * \param boolean $plugin_enabled Is the plugin/tab activated
784
   *
785
786
787
   * \param boolean $button_disabled Is the button disabled
   *
   * \param string $name The html name of the input, defaults to modify_state
788
   */
789
  function show_header (string $button_text, string $text, bool $plugin_enabled, bool $button_disabled = FALSE, string $name = 'modify_state'): string
790
791
  {
    if ($button_disabled || ((!$this->acl_is_createable() && !$plugin_enabled) || (!$this->acl_is_removeable() && $plugin_enabled))) {
792
      $state = 'disabled="disabled"';
793
    } else {
794
      $state = '';
795
796
    }
    $display = '<div width="100%"><p><b>'.$text.'</b><br/>'."\n";
797
    $display .= '<input type="submit" formnovalidate="formnovalidate" value="'.$button_text.'" name="'.$name.'" '.$state.'></p></div><hr class="separator"/>';
798
799
800
801

    return $display;
  }

802
803
804
805
  /*! \brief Check if logged in user have enough right to write this attribute value
   *
   * \param mixed $attr Attribute object or name (in this case it will be fetched from attributesAccess)
   */
806
  function attrIsWriteable ($attr): bool
807
  {
808
809
810
    if (!is_object($attr)) {
      $attr = $this->attributesAccess[$attr];
    }
811
    if ($attr->getLdapName() == 'base') {
812
813
814
815
      return (
        !$this->acl_skip_write() &&
        (!$this->initially_was_account || $this->acl_is_moveable() || $this->acl_is_removeable())
      );
816
817
818
819
    }
    return $this->acl_is_writeable($attr->getAcl(), $this->acl_skip_write());
  }

820
  function renderAttributes (bool $readOnly = FALSE)
821
  {
822
    global $ui;
823
824
    $smarty = get_smarty();

825
826
827
828
829
830
    if ($this->is_template) {
      $smarty->assign('template_cnACL', $ui->get_permissions($this->acl_base, $this->acl_category.'template', 'template_cn', $this->acl_skip_write()));
    }

    /* Handle rights to modify the base */
    if (isset($this->attributesAccess['base'])) {
831
      if ($this->attrIsWriteable('base')) {
832
833
834
835
836
837
        $smarty->assign('baseACL', 'rw');
      } else {
        $smarty->assign('baseACL', 'r');
      }
    }

838
    $sections = [];
839
840
841
    foreach ($this->attributesInfo as $section => $sectionInfo) {
      $legend = $sectionInfo['name'];
      if (isset($sectionInfo['icon'])) {
Côme Bernigaud's avatar
Côme Bernigaud committed
842
843
        $legend = '<img '.
                  'src="'.htmlentities($sectionInfo['icon'], ENT_COMPAT, 'UTF-8').'" '.
844
                  'alt="" '.
Côme Bernigaud's avatar
Côme Bernigaud committed
845
                  '/>'.$legend;
846
847
848
      }
      $smarty->assign("section", $legend);
      $smarty->assign("sectionId", $section);
849
850
851
852
853
      if (isset($sectionInfo['class'])) {
        $smarty->assign("sectionClasses", ' '.join(' ', $sectionInfo['class']));
      } else {
        $smarty->assign("sectionClasses", '');
      }
854
      $attributes = [];
855
856
857
      foreach ($sectionInfo['attrs'] as $attr) {
        if ($attr->getAclInfo() !== FALSE) {
          // We assign ACLs so that attributes can use them in their template code
858
          $smarty->assign($attr->getAcl()."ACL", $this->aclGetPermissions($attr->getAcl(), NULL, $this->acl_skip_write()));
859
        }
860
        $attr->renderAttribute($attributes, $readOnly);
861
862
863
864
865
866
867
868
869
870
871
      }
      $smarty->assign("attributes", $attributes);
      // We fetch each section with the section template
      if (isset($sectionInfo['template'])) {
        $displaySection = $smarty->fetch($sectionInfo['template']);
      } else {
        $displaySection = $smarty->fetch(get_template_path('simpleplugin_section.tpl'));
      }
      $sections[$section] = $displaySection;
    }
    $smarty->assign("sections", $sections);
872
873
  }

874
  function inheritanceDisplay (): string
875
876
877
878
879
880
881
  {
    if (!$this->member_of_group) {
      return "";
    }
    $class = get_class($this);
    $attrsWrapper = new stdClass();
    $attrsWrapper->attrs = $this->group_attrs;
882
    $group = new $class($this->group_attrs['dn'], $attrsWrapper, $this->parent, $this->mainTab);
883
884
    $smarty = get_smarty();

885
    $group->renderAttributes(TRUE);
886
887
    $smarty->assign("hiddenPostedInput", get_class($this)."_posted");

888
    return "<h1>Inherited information:</h1><div></div>\n".$smarty->fetch($this->templatePath);
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
  }

  /*! \brief This function allows you to open a dialog
   *
   *  \param mixed $dialog The dialog object
   */
  function openDialog ($dialog)
  {
    $this->dialog = $dialog;
  }

  /*! \brief This function closes the dialog
   */
  function closeDialog ()
  {
    $this->dialog = NULL;
  }

907
  public function setNeedEditMode (bool $bool)
908
909
910
911
  {
    $this->needEditMode = $bool;
  }

912
  protected function acl_skip_write (): bool
913
  {
914
    return ($this->needEditMode && !session::is_set('edit'));
915
916
  }

917
  /*! \brief Can we write the attribute */
918
  function acl_is_writeable ($attribute, bool $skipWrite = FALSE): bool
919
  {
920
    return preg_match('/w/', $this->aclGetPermissions($attribute, NULL, $skipWrite));
921
922
923
924
925
926
927
  }

  /*!
   * \brief Can we read the acl
   *
   * \param string $attribute
   */
928
  function acl_is_readable ($attribute): bool
929
930
931
932
933
934
935
936
937
  {
    return preg_match('/r/', $this->aclGetPermissions($attribute));
  }

  /*!
   * \brief Can we create the object
   *
   * \param string $base Empty string
   */
938
  function acl_is_createable (string $base = NULL): bool
939
940
941
942
943
944
945
946
947
  {
    return preg_match('/c/', $this->aclGetPermissions('0', $base));
  }

  /*!
   * \brief Can we delete the object
   *
   * \param string $base Empty string
   */
948
  function acl_is_removeable (string $base = NULL): bool
949
950
951
952
953
954
955
956
957
  {
    return preg_match('/d/', $this->aclGetPermissions('0', $base));
  }

  /*!
   * \brief Can we move the object
   *
   * \param string $base Empty string
   */
958
  function acl_is_moveable (string $base = NULL): bool
959
960
961
962
963
  {
    return preg_match('/m/', $this->aclGetPermissions('0', $base));
  }

  /*! \brief Get the acl permissions for an attribute or the plugin itself */
964
  function aclGetPermissions ($attribute = '0', string $base = NULL, bool $skipWrite = FALSE): string
965
  {
966
967
968
    if (isset($this->parent) && isset($this->parent->ignoreAcls) && $this->parent->ignoreAcls) {
      return 'cdmr'.($skipWrite ? '' : 'w');
    }
969
970
971
972
973
974
975
976
    $ui         = get_userinfo();
    $skipWrite  |= $this->readOnly();
    if ($base === NULL) {
      $base = $this->acl_base;
    }
    return $ui->get_permissions($base, $this->acl_category.get_class($this), $attribute, $skipWrite);
  }

977
978
  /*! \brief This function removes the object from LDAP
   */
979
  function remove (bool $fulldelete = FALSE): array
980
  {
981
    if (!$this->initially_was_account) {
982
      return [];
983
    }
984
985
986

    if (!$fulldelete && !$this->acl_is_removeable()) {
      trigger_error('remove was called on a tab without enough ACL rights');
987
      return [];
988
989
    }

990
    $this->prepare_remove();
991
992
    if ($this->is_template && (!defined('_OLD_TEMPLATES_') || !_OLD_TEMPLATES_)) {
      $this->attrs = $this->templateSaveAttrs();
993
      $this->saved_attributes = [];
994
    }
995
996
997
998
999
    /* Pre hooks */
    $errors = $this->pre_remove();
    if (!empty($errors)) {
      return $errors;
    }
1000
    $errors = $this->ldap_remove();
For faster browsing, not all history is shown. View entire blame