class_plugin.inc 50.8 KB
Newer Older
1
2
3
4
<?php
/*
  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
  Copyright (C) 2003-2010  Cajus Pollmeier
5
  Copyright (C) 2011-2016  FusionDirectory
6
7
8
9
10
11
12
13
14
15
16
17
18

  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
19
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
20
21
*/

22
23
24
25
/*!
 * \file class_plugin.inc
 * Source code for the class plugin
 */
26
27
28
class plugin
{
  /*!
29
   * \brief Reference to parent object
30
   *
31
32
33
34
35
36
   * 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.
   *
   * \sa tab
37
   */
38
  var $parent = NULL;
39
40
41
42
43
44

  /*!
    \brief Configuration container

    Access to global configuration
   */
45
  var $config = NULL;
46
47
48
49
50
51
52
53
54
55

  /*!
    \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.

56
    \sa plugin::__construct()
57
   */
58
59
  var $is_account             = FALSE;
  var $initially_was_account  = FALSE;
60
61
62
63
64
65
66
67
68
69

  /*!
    \brief Mark plugin as template

    Defines whether we are creating a template or a normal object.
    Has conseqences on the way execute() shows the formular and how
    save() puts the data to LDAP.

    \sa plugin::save() plugin::execute()
   */
70
71
72
  var $is_template    = FALSE;
  var $ignore_account = FALSE;
  var $is_modified    = FALSE;
73
74
75
76
77
78

  /*!
    \brief Represent temporary LDAP data

    This is only used internally.
   */
79
  var $attrs = array();
80
81

  /* Keep set of conflicting plugins */
82
  var $conflicts = array();
83
84
85
86
87
88

  /*!
    \brief Used standard values

    dn
   */
89
90
  var $dn         = "";
  var $dialog     = FALSE;
91
92

  /* attribute list for save action */
93
94
95
  var $attributes       = array();
  var $objectclasses    = array();
  var $saved_attributes = array();
96

97
98
99
  var $acl_base     = "";
  var $acl_category = "";
  var $read_only    = FALSE; // Used when the entry is opened as "readonly" due to locks.
100
101

  /* This can be set to render the tabulators in another stylesheet */
102
  var $pl_notify = FALSE;
103

104
105
106
107
108
109
110
111
  /*!
   * \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.
   */
  var $entryCSN         = '';
112
113
  var $CSN_check_active = FALSE;

114
  /*!
115
116
117
118
119
   * \brief plugin constructor
   *
   * If 'dn' is set, the node loads the given 'dn' from LDAP
   *
   * \param $config configuration
120
   *
121
   * \param $dn Distinguished name to initialize plugin from
122
   *
123
   * \param $object NULL
124
   *
125
   * \sa plugin()
126
   */
127
  function __construct (&$config, $dn = NULL, $object = NULL)
128
129
  {
    /* Configuration is fine, allways */
130
131
    $this->config = &$config;
    $this->dn     = $dn;
132
133

    // Ensure that we've a valid acl_category set.
134
    if (empty($this->acl_category)) {
135
      $tmp = pluglist::pluginInfos(get_class($this));
136
137
      if (isset($tmp['plCategory'])) {
        $c = key($tmp['plCategory']);
138
        if (is_numeric($c)) {
139
140
          $c = $tmp['plCategory'][0];
        }
141
        $this->acl_category = $c.'/';
142
143
144
145
      }
    }

    /* Handle new accounts, don't read information from LDAP */
146
    if ($this->dn != 'new') {
147
148
      /* Check if this entry was opened in read only mode */
      if (isset($_POST['open_readonly'])) {
149
150
        if (session::global_is_set('LOCK_CACHE')) {
          $cache = &session::get('LOCK_CACHE');
151
152
153
          if (isset($cache['READ_ONLY'][$this->dn])) {
            $this->read_only = TRUE;
          }
154
155
156
        }
      }

157
158
159
      /* Save current dn as acl_base */
      $this->acl_base = $this->dn;
    }
160
161

    /* Get LDAP descriptor */
162
    if (($this->dn != 'new' && $this->dn !== NULL) || ($object !== NULL)) {
163
      /* Load data to 'attrs' and save 'dn' */
164
165
      if ($object !== NULL) {
        $this->attrs = $object->attrs;
166
167
168
        if (isset($object->is_template)) {
          $this->setTemplate($object->is_template);
        }
169
      } else {
170
171
172
        $ldap = $this->config->get_ldap_link();
        $ldap->cat($this->dn);
        $this->attrs = $ldap->fetch();
173
174
      }

175
      /* Set the template flag according to the existence of objectClass fdTemplate */
176
      if (isset($this->attrs['objectClass'])) {
177
178
179
        if (in_array_ics ('fdTemplate', $this->attrs['objectClass'])) {
          @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, 'found', 'Template check');
          $this->templateLoadAttrs($this->attrs);
180
181
182
183
        }
      }

      /* Is Account? */
184
      if ($this->is_this_account($this->attrs)) {
185
        $this->is_account = TRUE;
186
        @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, 'found', 'Object check');
187
      }
188
    }
189

190
    $this->loadAttributes();
191

192
193
    $this->prepareSavedAttributes();

194
195
196
    /* Save initial account state */
    $this->initially_was_account = $this->is_account;
  }
197

198
199
200
201
202
203
204
205
206
207
208
  protected function loadAttributes()
  {
    /* Copy needed attributes */
    foreach ($this->attributes as $val) {
      $found = array_key_ics($val, $this->attrs);
      if ($found != "") {
        $this->$val = $found[0];
      }
    }
  }

209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
  function is_this_account($attrs)
  {
    $found = TRUE;
    foreach ($this->objectclasses as $obj) {
      if (preg_match('/top/i', $obj)) {
        continue;
      }
      if (!isset($attrs['objectClass']) || !in_array_ics ($obj, $attrs['objectClass'])) {
        $found = FALSE;
        break;
      }
    }
    return $found;
  }

224
225
226
227
  function prepareSavedAttributes()
  {
    /* Prepare saved attributes */
    $this->saved_attributes = $this->attrs;
228
    foreach (array_keys($this->saved_attributes) as $index) {
229
230
231
      if (is_numeric($index)) {
        unset($this->saved_attributes[$index]);
        continue;
232
233
      }

234
235
236
      if (!in_array_ics($index, $this->attributes) && strcasecmp('objectClass', $index)) {
        unset($this->saved_attributes[$index]);
        continue;
237
      }
238
239
240
241
242
243
244
245
246
247
248
249

      if (isset($this->saved_attributes[$index][0])) {
        if (!isset($this->saved_attributes[$index]["count"])) {
          $this->saved_attributes[$index]["count"] = count($this->saved_attributes[$index]);
        }
        if ($this->saved_attributes[$index]["count"] == 1) {
          $tmp = $this->saved_attributes[$index][0];
          unset($this->saved_attributes[$index]);
          $this->saved_attributes[$index] = $tmp;
          continue;
        }
      }
250
      unset($this->saved_attributes[$index]["count"]);
251
252
253
    }
  }

254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
  protected function templateLoadAttrs($template_attrs)
  {
    $this->is_template = TRUE;
    if ($this->mainTab) {
      $this->_template_cn = $template_attrs['cn'][0];
    }
    $this->attrs = self::tpl_template_to_attrs($template_attrs);
  }

  protected function templateSaveAttrs()
  {
    $ldap = $this->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.');
      }
      $template_attrs = array(
        'objectClass'     => array('fdTemplate'),
        'fdTemplateField' => array()
      );
    } else {
      unset($template_attrs['dn']);
      unset($template_attrs['fdTemplateField']['count']);
      unset($template_attrs['objectClass']['count']);
280
      unset($template_attrs['cn']['count']);
281
282
283
284
285
286
287
288
289
290
      for ($i = 0; $i < $template_attrs['count']; ++$i) { // Remove numeric keys
        unset($template_attrs[$i]);
      }
      unset($template_attrs['count']);
    }
    if ($this->mainTab) {
      $template_attrs['cn'] = $this->_template_cn;
    }
    /* First remove all concerned values */
    foreach ($template_attrs['fdTemplateField'] as $key => $value) {
291
      preg_match('/^([^:]+):(.*)$/s', $value, $m);
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
      if (isset($this->attrs[$m[1]])) {
        unset($template_attrs['fdTemplateField'][$key]);
      }
    }
    /* Then insert non-empty values */
    foreach ($this->attrs as $key => $value) {
      if (is_array($value)) {
        foreach ($value as $v) {
          if ($value == "") {
            continue;
          }
          $template_attrs['fdTemplateField'][] = $key.':'.$v;
        }
      } else {
        if ($value == "") {
          continue;
        }
        $template_attrs['fdTemplateField'][] = $key.':'.$value;
      }
    }
    sort($template_attrs['fdTemplateField']);
    return $template_attrs;
  }

316
317
318
319
320
  /*!
   * \brief This function is called on the copied object to set its dn to where it will be saved
   */
  function resetCopyInfos()
  {
321
322
323
324
325
    $this->dn       = 'new';
    $this->orig_dn  = $this->dn;

    $this->saved_attributes       = array();
    $this->initially_was_account  = FALSE;
326

327
    $this->postCopyHook();
328
329
  }

330

331
  /*!
332
   * \brief Generates the html output for this node
333
334
335
336
   */
  function execute()
  {
    /* Reset Lock message POST/GET check array, to prevent perg_match errors*/
337
338
339
340
    session::set('LOCK_VARS_TO_USE', array());
    session::set('LOCK_VARS_USED_GET', array());
    session::set('LOCK_VARS_USED_POST', array());
    session::set('LOCK_VARS_USED_REQUEST', array());
341
342
  }

343
  /*!
344
   * \brief Removes object from parent
345
346
347
   */
  function remove_from_parent()
  {
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
    $this->attrs = array();

    if (!$this->mainTab) {
      /* include global link_info */
      $ldap = $this->config->get_ldap_link();

      /* Get current objectClasses in order to add the required ones */
      $ldap->cat($this->dn);
      $tmp  = $ldap->fetch ();
      $oc   = array();
      if ($this->is_template) {
        if (isset($tmp['fdTemplateField'])) {
          foreach ($tmp['fdTemplateField'] as $tpl_field) {
            if (preg_match('/^objectClass:(.+)$/', $tpl_field, $m)) {
              $oc[] = $m[1];
            }
364
          }
365
        }
366
367
368
369
370
      } else {
        if (isset($tmp['objectClass'])) {
          $oc = $tmp['objectClass'];
          unset($oc['count']);
        }
371
      }
372

373
374
      /* Remove objectClasses from entry */
      $this->attrs['objectClass'] = array_remove_entries_ics($this->objectclasses, $oc);
375

376
377
378
379
      /* Unset attributes from entry */
      foreach ($this->attributes as $val) {
        $this->attrs["$val"] = array();
      }
380
381
    }

382
383
    if ($this->initially_was_account) {
      $this->handle_pre_events('remove');
384
385
386
387
    }
  }


388
  /*!
389
   * \brief Save HTML posted data to object
390
391
392
393
   */
  function save_object()
  {
    /* Update entry CSN if it is empty. */
394
    if (empty($this->entryCSN) && $this->CSN_check_active) {
395
396
397
398
      $this->entryCSN = getEntryCSN($this->dn);
    }

    /* Save values to object */
399
400
    foreach ($this->attributes as $val) {
      if ($this->acl_is_writeable($val) && isset ($_POST["$val"])) {
401
        /* Check for modifications */
402
        $data = $_POST["$val"];
403
404

        if ($this->$val != $data) {
405
          $this->is_modified = TRUE;
406
        }
407

408
409
        $this->$val = $data;

410
411
412
413
414
        /* Okay, how can I explain this fix ...
         * In firefox, disabled option fields aren't selectable ... but in IE you can select these fileds.
         * So IE posts these 'unselectable' option, with value = chr(194)
         * chr(194) seems to be the &nbsp; in between the ...option>&nbsp;</option.. because there is no value=".." specified in these option fields
         * This &nbsp; was added for W3c compliance, but now causes these ... ldap errors ...
415
416
         * So we set these Fields to ""; a normal empty string, and we can check these values in plugin::check() again ...
         */
417
        if (isset($data[0]) && ($data[0] == chr(194))) {
418
          $data = "";
419
        }
420
        $this->$val = $data;
421
422
423
424
425
      }
    }
  }


426
  /*!
427
   * \brief Save data to LDAP, depending on is_account we save or delete
Benoit Mortier's avatar
Benoit Mortier committed
428
   */
429
430
431
  function save()
  {
    /* include global link_info */
432
    $ldap = $this->config->get_ldap_link();
433

434
    $this->entryCSN = '';
435
436

    /* Start with empty array */
437
    $this->attrs = array();
438

439
440
441
442
443
444
    /* Get current objectClasses in order to add the required ones */
    $ldap->cat($this->dn);

    $tmp = $ldap->fetch();
    $oc = array();

445
    if ($this->is_template) {
446
447
448
449
450
      if (isset($tmp['fdTemplateField'])) {
        foreach ($tmp['fdTemplateField'] as $tpl_field) {
          if (preg_match('/^objectClass:(.+)$/', $tpl_field, $m)) {
            $oc[] = $m[1];
          }
451
        }
452
453
454
      }
    } else {
      if (isset($tmp['objectClass'])) {
455
        $oc = $tmp['objectClass'];
456
457
458
        unset($oc['count']);
      }
    }
459
    $is_new = empty($oc);
460
461
462

    /* Load (minimum) attributes, add missing ones */
    $this->attrs['objectClass'] = array_merge_unique($oc, $this->objectclasses);
463
464

    /* Copy standard attributes */
465
466
467
    foreach ($this->attributes as $val) {
      if ($this->$val != "") {
        $this->attrs["$val"] = $this->$val;
468
      } elseif (!$is_new) {
469
        $this->attrs["$val"] = array();
470
471
472
      }
    }

473
    if ($this->initially_was_account) {
474
      $this->handle_pre_events('modify');
475
476
    } else {
      $this->handle_pre_events('add');
477
478
479
    }
  }

480
481
482
483
  /*!
   * \brief Remove attributes, empty arrays, arrays
   * single attributes that do not differ
   */
484
485
  function cleanup()
  {
486
    foreach ($this->attrs as $index => $value) {
487

488
489
      /* Convert arrays with one element to non arrays, if the saved
         attributes are no array, too */
490
      if (is_array($this->attrs[$index]) &&
491
492
          count ($this->attrs[$index]) == 1 &&
          isset($this->saved_attributes[$index]) &&
493
494
          !is_array($this->saved_attributes[$index])) {
        $this->attrs[$index] = $this->attrs[$index][0];
495
496
497
498
499
      }

      /* Remove emtpy arrays if they do not differ */
      if (is_array($this->attrs[$index]) &&
          count($this->attrs[$index]) == 0 &&
500
          !isset($this->saved_attributes[$index])) {
501
502
503
504
505
506
507
508
        unset ($this->attrs[$index]);
        continue;
      }

      /* Remove single attributes that do not differ */
      if (!is_array($this->attrs[$index]) &&
          isset($this->saved_attributes[$index]) &&
          !is_array($this->saved_attributes[$index]) &&
509
          $this->attrs[$index] == $this->saved_attributes[$index]) {
510
511
512
513
514
        unset ($this->attrs[$index]);
        continue;
      }

      /* Remove arrays that do not differ */
515
      if (is_array($this->attrs[$index]) &&
516
          isset($this->saved_attributes[$index]) &&
517
518
          is_array($this->saved_attributes[$index])) {
        if (!array_differs($this->attrs[$index], $this->saved_attributes[$index])) {
519
520
521
522
523
524
525
          unset ($this->attrs[$index]);
          continue;
        }
      }
    }

    /* Update saved attributes and ensure that next cleanups will be successful too */
526
    foreach ($this->attrs as $name => $value) {
527
528
529
530
      $this->saved_attributes[$name] = $value;
    }
  }

531
  /*!
532
   * \brief Check formular input
Benoit Mortier's avatar
Benoit Mortier committed
533
   */
534
535
  function check()
  {
536
    $message = array();
537
538

    /* Skip if we've no config object */
539
    if (!isset($this->config) || !is_object($this->config)) {
540
541
542
      return $message;
    }

543
    $this->callHook('CHECK', array(), $returnOutput);
544
545
    if (!empty($returnOutput)) {
      $message[] = join("\n", $returnOutput);
546
547
548
    }

    /* Check entryCSN */
549
    if ($this->CSN_check_active) {
550
      $current_csn = getEntryCSN($this->dn);
551
      if (($current_csn != $this->entryCSN) && !empty($this->entryCSN) && !empty($current_csn)) {
552
        $this->entryCSN = $current_csn;
553
        $message[] = _('The object has changed since opened in FusionDirectory. All changes that may be done by others will get lost if you save this entry!');
554
555
      }
    }
556
    return $message;
557
558
  }

559
560
561
  /*
   * \brief Adapt from template, using 'dn'
   *
562
   * \param string $dn The DN
563
564
   *
   * \param array $skip A new array
565
   */
566
  function adapt_from_template($attrs, $skip = array())
567
  {
568
    $this->attrs = $attrs;
569

570
    /* Walk through attributes */
571
    foreach ($this->attributes as $val) {
572
      /* Skip the ones in skip list */
573
      if (in_array($val, $skip)) {
574
575
576
        continue;
      }

577
      if (isset($this->attrs["$val"][0])) {
578
        $this->$val = $this->attrs["$val"][0];
579
580
581
582
      }
    }

    /* Is Account? */
583
    $this->is_account = $this->is_this_account($this->attrs);
584
585
  }

586
587
588
589
  public function setNeedEditMode ($bool)
  {
  }

590
591
592
593
594
  function setTemplate ($bool)
  {
    $this->is_template = $bool;
  }

595
596
597
598
599
600
601
  static function tpl_fetch_template($dn)
  {
    global $config;

    $ldap = $config->get_ldap_link();
    $ldap->cat($dn);
    $attrs    = $ldap->fetch();
602
    $attrs    = self::tpl_template_to_attrs($attrs);
603
604
605
606
607
    $depends  = self::tpl_attrs_depends($attrs);
    $attrs    = self::tpl_sort_attrs($attrs, $depends);
    return array($attrs, $depends);
  }

608
609
610
611
612
613
614
  static function tpl_template_to_attrs($template_attrs)
  {
    /* Translate template attrs into $attrs as if taken from LDAP */
    unset($template_attrs['fdTemplateField']['count']);
    sort($template_attrs['fdTemplateField']);
    $attrs = array();
    foreach ($template_attrs['fdTemplateField'] as $field) {
615
      preg_match('/^([^:]+):(.*)$/s', $field, $m);
616
617
618
619
620
621
622
623
624
625
626
      if (isset($attrs[$m[1]])) {
        $attrs[$m[1]][] = $m[2];
        $attrs[$m[1]]['count']++;
      } else {
        $attrs[$m[1]]           = array($m[2]);
        $attrs[$m[1]]['count']  = 1;
      }
    }
    return $attrs;
  }

627
628
629
  /* Apply a modifier
   * Returns an array of possible values */
  static function tpl_apply_modifier($m, $args, $str)
630
  {
631
632
    mb_internal_encoding('UTF-8');
    mb_regex_encoding('UTF-8');
633
634
635
636
    if (is_array($str) && (strtolower($m) == $m)) {
      /* $str is an array and $m is lowercase, so it's a string modifier */
      $str = $str[0];
    }
637
    switch ($m) {
638
639
640
641
642
643
644
645
646
647
      case 'F': // First
        return array($str[0]);
      case 'L': // Last
        return array(end($str));
      case 'J': // Join
        if (isset($args[0])) {
          return array(join($args[0], $str));
        } else {
          return array(join($str));
        }
648
649
      case 'C': // Count
        return array(count($str));
650
651
      case 'c': // comment
        return array('');
652
653
654
655
656
      case 'b': // base64
        if (isset($args[0]) && ($args[0] == 'd')) {
          return array(base64_decode($str));
        }
        return array(base64_encode($str));
657
658
659
660
661
      case 'u': // uppercase
        return array(mb_strtoupper($str, 'UTF-8'));
      case 'l': // lowercase
        return array(mb_strtolower($str, 'UTF-8'));
      case 'a': // remove accent
662
663
664
665
        $str = htmlentities($str, ENT_NOQUOTES, 'UTF-8');

        $str = preg_replace('#&([A-za-z])(?:acute|cedil|circ|grave|orn|ring|slash|th|tilde|uml);#', '\1', $str);
        $str = preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $str); // handle ligatures
666
        return array(preg_replace('#&[^;]+;#', '', $str)); // delete unhandled characters
667
668
669
670
      case 't': // translit
        $localesaved = setlocale(LC_CTYPE, 0);
        $ret = array();
        foreach ($args as $arg) {
671
          setlocale(LC_CTYPE, array($arg,"$arg.UTF8"));
672
673
674
          $ret[] = iconv('UTF8', 'ASCII//TRANSLIT', $str);
        }
        setlocale(LC_CTYPE, $localesaved);
675
        return array_unique($ret);
676
677
      case 'p': // spaces
        return array(preg_replace('/\s/u', '', $str));
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
      case 's': // substring
        if (count($args) < 1) {
          trigger_error("Missing 's' substr modifier parameter");
        }
        if (count($args) < 2) {
          array_unshift($args, 0);
        }
        if (preg_match('/^(\d+)-(\d+)$/', $args[1], $m)) {
          $res = array();
          for ($i = $m[1];$i < $m[2]; ++$i) {
            $res[] = substr($str, $args[0], $i);
          }
          return array_unique($res);
        } else {
          return array(substr($str, $args[0], $args[1]));
        }
694
695
696
697
698
699
700
701
702
703
704
705
      case 'r': // random string
        $length = 8;
        $chars  = 'b';
        if (count($args) >= 2) {
          $length = mt_rand($args[0], $args[1]);
          if (count($args) >= 3) {
            $chars = $args[2];
          }
        } elseif (count($args) >= 1) {
          $length = $args[0];
        }
        $res = '';
706
        for ($i = 0; $i < $length; ++$i) {
Côme Chilliet's avatar
Côme Chilliet committed
707
          switch ($chars) {
708
            case 'l':
Côme Chilliet's avatar
Côme Chilliet committed
709
              $res .= (string)rand(0, 9);
710
711
            break;
            case 'd':
Côme Chilliet's avatar
Côme Chilliet committed
712
              $nb = mt_rand(65, 116);
713
714
715
716
717
718
719
720
              if ($nb > 90) {
                /* lowercase */
                $nb += 6;
              }
              $res .= chr($nb);
            break;
            case 'b':
            default:
Côme Chilliet's avatar
Côme Chilliet committed
721
              $nb = mt_rand(65, 126);
722
723
724
725
726
727
728
729
730
731
732
733
734
735
              if ($nb > 116) {
                /* digit */
                $nb = (string)($nb - 117);
              } else {
                if ($nb > 90) {
                  /* lowercase */
                  $nb += 6;
                }
                $nb = chr($nb);
              }
              $res .= $nb;
            break;
          }
        }
736
        return array($res);
737
738
      default:
        trigger_error("Unkown modifier '$m'");
739
        return array($str);
740
741
742
    }
  }

743
  static function tpl_parse_mask($mask, $attrs)
744
  {
745
746
    if ($mask == '|') {
      return array('%');
747
    }
748
    $modifiers = '';
749
    if (preg_match('/^([^|]+)\|/', $mask, $m)) {
750
      $modifiers = $m[1];
751
752
      $mask = substr($mask, strlen($m[0]));
    }
753
    $result = array('');
754
755
756
757
    if (isset($attrs[$mask])) {
      $result = array($attrs[$mask]);
      if (is_array($result[0])) {
        unset($result[0]['count']);
758
      }
759
    } elseif (($mask != '') && !preg_match('/c/', $modifiers)) {
760
      trigger_error("'$mask' was not found in attributes");
761
    }
762
    $len    = strlen($modifiers);
763
    for ($i = 0; $i < $len; ++$i) {
764
765
      $args     = array();
      $modifier = $modifiers[$i];
766
      if (preg_match('/^\[([^\]]+)\].*$/', substr($modifiers, $i + 1), $m)) {
767
768
        /* get modifier args */
        $args = explode(',', $m[1]);
769
        $i += strlen($m[1]) + 2;
770
771
772
773
774
775
      }
      $result_tmp = array();
      foreach ($result as $r) {
        $result_tmp = array_merge($result_tmp, self::tpl_apply_modifier($modifier, $args, $r));
      }
      $result = $result_tmp;
776
    }
777
778
    foreach ($result as &$r) { // Array that were not converted by a modifier into a string are now converted to strings
      if (is_array($r)) {
779
        $r = reset($r);
780
      }
781
    }
782
783
    unset($r);
    return $result;
784
785
  }

786
787
788
789
790
791
792
793
794
795
796
797
798
799
  static function tpl_depends_of (&$cache, $depends, $key, $forbidden = NULL)
  {
    if (isset($cache[$key])) {
      return $cache[$key];
    }
    if ($forbidden === NULL) {
      $forbidden = $key;
    } elseif ($forbidden == $key) {
      die('Error : recursive dependency');
    }
    $array        =
      array_map(
        function ($a) use (&$cache, $depends, $forbidden)
        {
800
801
802
803
804
805
          $deps = plugin::tpl_depends_of ($cache, $depends, $a, $forbidden);
          if (($askmeKey = array_search('askme', $deps)) !== FALSE) {
            /* Do not flat special askme dependency */
            unset($deps[$askmeKey]);
          }
          return $deps;
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
        },
        $depends[$key]
      );
    $array[]      = $depends[$key];
    $cache[$key]  = array_unique(call_user_func_array('array_merge_recursive', $array));
    return $cache[$key];
  }

  static function tpl_attrs_depends($attrs)
  {
    /* Compute dependencies of each attr */
    $depends = array();
    foreach ($attrs as $key => $values) {
      $depends[$key] = array();
      if (!is_array($values))  {
        $values = array($values);
      }
      unset ($values['count']);
      foreach ($values as $value) {
        $offset = 0;
        while (preg_match('/%([^%\|]+\|)?([^%]+)%/', $value, $m, PREG_OFFSET_CAPTURE, $offset)) {
          $offset = $m[0][1] + strlen($m[0][0]);
          $depends[$key][] = $m[2][0];
          if (!isset($attrs[$m[2][0]])) { // Dependency which has no value might be missing
            $attrs[$m[2][0]]    = array();
            $depends[$m[2][0]]  = array();
          }
        }
      }
    }
    /* Flattens dependencies */
    $flatdepends = array();
    foreach ($depends as $key => $value) {
      self::tpl_depends_of($flatdepends, $depends, $key);
    }
    return $flatdepends;
  }

  static function tpl_sort_attrs($attrs, $flatdepends)
  {
    /* Sort attrs depending of dependencies */
    uksort($attrs, function ($k1, $k2) use ($flatdepends) {
      if (in_array($k1, $flatdepends[$k2])) {
        return -1;
      } elseif (in_array($k2, $flatdepends[$k1])) {
        return 1;
      } else { // When no direct dependency, we sort by number of dependencies
        $c1 = count($flatdepends[$k1]);
        $c2 = count($flatdepends[$k2]);
        if ($c1 == $c2) {
            return 0;
        }
        return (($c1 < $c2) ? -1 : 1);
      }
    });
    return $attrs;
  }

  /*! Brief Return attrs needed before applying template
   *
   * return an array of attributes which are needed by the template
   */
868
  static function tpl_needed_attrs(&$attrs, $flatdepends)
869
870
  {
    $needed = array();
871
    foreach ($flatdepends as $attr => $depends) {
872
      if ((isset($depends[0])) && ($depends[0] == 'askme')) {
873
874
875
876
877
        $needed[] = $attr;
        unset($flatdepends[$attr]);
        unset($attrs[$attr]);
      }
    }
878
879
880
881
882
883
    $dependencies = array_unique(call_user_func_array('array_merge', $flatdepends));
    foreach ($dependencies as $attr) {
      if (empty($flatdepends[$attr])) {
        $needed[] = $attr;
      }
    }
884
    return array_unique($needed);
885
886
  }

887
888
889
890
891
892
893
  /*! Brief Parse attrs template masks
   *
   * return an array with the final values of attributes
   */
  static function tpl_parse_attrs($attrs)
  {
    foreach ($attrs as &$attr) {
894
895
896
897
898
899
      if (is_array($attr)) {
        foreach ($attr as $key => &$string) {
          if (!is_numeric($key)) {
            continue;
          }
          $string = self::tpl_parse_string($string, $attrs);
900
        }
901
        unset($string);
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
      }
    }
    unset($attr);
    return $attrs;
  }

  /*! Brief Parse template masks in a single string
   *
   * return the string with patterns replaced by their values
   */
  static function tpl_parse_string($string, $attrs)
  {
    $offset = 0;
    while (preg_match('/%([^%]+)%/', $string, $m, PREG_OFFSET_CAPTURE, $offset)) {
      $replace  = self::tpl_parse_mask($m[1][0], $attrs);
      $replace  = $replace[0];
      $string   = substr_replace($string, $replace, $m[0][1], strlen($m[0][0]));
      $offset   = $m[0][1] + strlen($replace);
    }
    return $string;
  }
923

924
925
926
927
928
929
930
931
932
933
  /*! Brief Parse template masks in a single string and list the fields it needs
   *
   * return An array with the names of the fields used in the string pattern
   */
  static function tpl_list_fields($string)
  {
    $fields = array();
    $offset = 0;
    while (preg_match('/%([^%]+)%/', $string, $m, PREG_OFFSET_CAPTURE, $offset)) {
      $mask   = $m[1][0];
934
      $offset = $m[0][1] + strlen($m[0][0]);
935
936
937
938
      if ($mask == '|') {
        continue;
      }
      if (preg_match('/^([^|]+)\|/', $mask, $m)) {
939
        $mask = substr($mask, strlen($m[0]));
940
      }
941
      $fields[] = $mask;
942
943
944
945
    }
    return $fields;
  }

946
947
  /*!
   * \brief Show header message for tab dialogs
948
   *
949
   * \param string $button_text The button text
950
   *
951
   * \param string $text The text
952
   *
953
   * \param boolean $disabled FALSE
Benoit Mortier's avatar
Benoit Mortier committed
954
   */
955
  function show_enable_header($button_text, $text, $disabled = FALSE, $name = 'modify_state')
956
  {
957
    return $this->show_header($button_text, $text, FALSE, $disabled, $name);
958
959
  }

960
  /*!
961
   * \brief Show header message for tab dialogs
962
   *
963
   * \param string $button_text The button text
964
   *
965
   * \param string $text The text
966
   *
967
   * \param boolean $disabled FALSE
Benoit Mortier's avatar
Benoit Mortier committed
968
   */
969
  function show_disable_header($button_text, $text, $disabled = FALSE, $name = 'modify_state')
970
  {
971
    return $this->show_header($button_text, $text, TRUE, $disabled, $name);
972
973
  }

974
  /*!
975
   * \brief Show header message for tab dialogs
976
   *
977
   * \param string $button_text The button text
978
   *
979
   * \param string $text The text
980
   *
981
   * \param boolean $plugin_enabled
982
   *
983
   * \param boolean $button_disabled FALSE
Benoit Mortier's avatar
Benoit Mortier committed
984
   */
985
  function show_header($button_text, $text, $plugin_enabled, $button_disabled = FALSE, $name = 'modify_state')
986
  {
987
988
    if ($button_disabled || ((!$this->acl_is_createable() && !$plugin_enabled) || (!$this->acl_is_removeable() && $plugin_enabled))) {
      $state = 'disabled="disabled"';
989
    } else {
990
      $state = '';
991
    }
992
993
    $display = '<div width="100%"><p><b>'.$text.'</b><br/>'."\n";
    $display .= '<input type="submit" value="'.$button_text.'" name="'.$name.'" '.$state.'></p></div><hr class="separator"/>';
994

995
    return $display;
996
997
  }

998
999
1000
1001
1002
1003
1004
  /*!
   * \brief Executes a command after an object has been copied
   */
  function postCopyHook()
  {
  }

1005
  /*!
1006
1007
1008
   * \brief Create unique DN
   *
   * \param string $attribute
1009
   *
1010
1011
   * \param string $base
   */
1012
1013
  function create_unique_dn($attribute, $base)
  {
1014
1015
    $ldap = $this->config->get_ldap_link();
    $base = preg_replace('/^,*/', '', $base);
1016
1017

    /* Try to use plain entry first */
1018
    $dn = $attribute.'='.ldap_escape_dn($this->$attribute).','.$base;
1019
1020
1021
    if ($dn == $this->orig_dn) {
      return $dn;
    }
1022
1023
1024
    $ldap->cat($dn, array('dn'));
    if (!$ldap->fetch()) {
      return $dn;
1025
1026
1027
    }

    /* Look for additional attributes */
1028
1029
    foreach ($this->attributes as $attr) {
      if ($attr == $attribute || $this->$attr == "" || is_array($this->$attr)) {
1030
1031
1032
        continue;
      }

1033
      $dn = $attribute.'='.ldap_escape_dn($this->$attribute).'+'.$attr.'='.ldap_escape_dn($this->$attr).','.$base;
1034
1035
1036
      if ($dn == $this->orig_dn) {
        return $dn;
      }
1037
1038
1039
      $ldap->cat($dn, array('dn'));
      if (!$ldap->fetch()) {
        return $dn;
1040
1041
1042
1043
      }
    }

    /* None found */
1044
    return 'none';
1045
1046
  }

1047
  /*!
1048
   * \brief  Rename/Move a given src_dn to the given dest_dn
1049
1050
1051
1052
   *
   * Move a given ldap object indentified by $src_dn to the
   * given destination $dst_dn
   *
1053
   * \param  string  $src_dn the source DN.
1054
   *
1055
   * \param  string  $dst_dn the destination DN.
1056
   *
1057
1058
   * \return boolean TRUE on success else FALSE.
   */
1059
  private function rename($src_dn, $dst_dn)
1060
1061
1062
1063
  {
    /* Try to move the source entry to the destination position */
    $ldap = $this->config->get_ldap_link();
    $ldap->cd($this->config->current['BASE']);
1064
    $ldap->create_missing_trees(preg_replace("/^[^,]+,/", '', $dst_dn));
1065
    if (!$ldap->rename_dn($src_dn, $dst_dn)) {
1066
      new log('debug', 'Ldap Protocol v3 implementation error, ldap_rename failed.',
1067
1068
              "FROM: $src_dn  -- TO: $dst_dn", array(), $ldap->get_error());
      @DEBUG(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, "Rename failed FROM: $src_dn  -- TO:  $dst_dn",
1069
          'Ldap Protocol v3 implementation error. Error:'.$ldap->get_error());
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
      return FALSE;
    }

    return TRUE;
  }


   /*!
    * \brief Move ldap entries from one place to another
    *
    * \param  string  $src_dn the source DN.
    *
    * \param  string  $dst_dn the destination DN.
    */
  function move($src_dn, $dst_dn)
  {
    /* Do not move if only upper- lowercase has changed */
    if (strtolower($src_dn) == strtolower($dst_dn)) {
      return TRUE;
    }

1091
    /* Try to move with ldap routines */