class_plugin.inc 52.2 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-2013  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
26
/*!
 * \file class_plugin.inc
 * Source code for the class plugin
 */

27
/*!
28
 * \brief This is the base class for all plugins.
29
 *
Benoit Mortier's avatar
Benoit Mortier committed
30
31
32
 * \author  Cajus Pollmeier <pollmeier@gonicus.de>
 * \version 2.00
 * \date    24.07.2003
33
 *
Benoit Mortier's avatar
Benoit Mortier committed
34
 * This is the base class for all plugins. It can be used standalone or
35
 * can be included by the tabs class. All management should be done
Benoit Mortier's avatar
Benoit Mortier committed
36
 * within this class. Extend your plugins from this class.
37
38
39
40
 */
class plugin
{
  /*!
41
   * \brief Reference to parent object
42
   *
43
44
45
46
47
48
   * 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
49
   */
50
  var $parent = NULL;
51
52
53
54
55
56

  /*!
    \brief Configuration container

    Access to global configuration
   */
57
  var $config = NULL;
58
59
60
61
62
63
64
65
66
67

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

68
    \sa plugin::__construct()
69
   */
70
71
  var $is_account             = FALSE;
  var $initially_was_account  = FALSE;
72
73
74
75
76
77
78
79
80
81

  /*!
    \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()
   */
82
  var $is_template    = FALSE;
83
  var $template_cn    = FALSE;
84
85
  var $ignore_account = FALSE;
  var $is_modified    = FALSE;
86
87
88
89
90
91

  /*!
    \brief Represent temporary LDAP data

    This is only used internally.
   */
92
  var $attrs = array();
93
94

  /* Keep set of conflicting plugins */
95
  var $conflicts = array();
96
97
98
99
100
101

  /*!
    \brief Used standard values

    dn
   */
102
103
104
  var $dn         = "";
  var $acl        = "*none*";
  var $dialog     = FALSE;
105
106

  /* attribute list for save action */
107
108
109
110
  var $attributes       = array();
  var $objectclasses    = array();
  var $is_new           = TRUE;
  var $saved_attributes = array();
111

112
113
114
  var $acl_base     = "";
  var $acl_category = "";
  var $read_only    = FALSE; // Used when the entry is opened as "readonly" due to locks.
115
116

  /* This can be set to render the tabulators in another stylesheet */
117
  var $pl_notify = FALSE;
118
119
120
121
122
123
124

  /* Object entry CSN */
  var $entryCSN         = "";
  var $CSN_check_active = FALSE;

  var $selected_edit_values = array();

125
  /*!
126
127
128
129
130
   * \brief plugin constructor
   *
   * If 'dn' is set, the node loads the given 'dn' from LDAP
   *
   * \param $config configuration
131
   *
132
   * \param $dn Distinguished name to initialize plugin from
133
   *
134
   * \param $object NULL
135
   *
136
   * \sa plugin()
137
   */
138
  function __construct (&$config, $dn = NULL, $object = NULL)
139
140
  {
    /* Configuration is fine, allways */
141
142
    $this->config = &$config;
    $this->dn     = $dn;
143
144

    // Ensure that we've a valid acl_category set.
145
    if (empty($this->acl_category)) {
146
      $tmp = pluglist::pluginInfos(get_class($this));
147
148
      if (isset($tmp['plCategory'])) {
        $c = key($tmp['plCategory']);
149
        if (is_numeric($c)) {
150
151
152
153
154
155
156
          $c = $tmp['plCategory'][0];
        }
        $this->acl_category = $c."/";
      }
    }

    /* Handle new accounts, don't read information from LDAP */
157
158
159
160
161
162
163
164
    if ($this->dn != "new") {
      /* Check if this entry was opened in read only mode */
      if (isset($_POST['open_readonly'])) {
        if (session::global_is_set("LOCK_CACHE")) {
          $cache = &session::get("LOCK_CACHE");
          if (isset($cache['READ_ONLY'][$this->dn])) {
            $this->read_only = TRUE;
          }
165
166
167
        }
      }

168
169
170
      /* Save current dn as acl_base */
      $this->acl_base = $this->dn;
    }
171
172

    /* Get LDAP descriptor */
Côme Bernigaud's avatar
Côme Bernigaud committed
173
    if (($this->dn != "new" && $this->dn !== NULL) || ($object !== NULL)) {
174
      /* Load data to 'attrs' and save 'dn' */
175
176
      if ($object !== NULL) {
        $this->attrs = $object->attrs;
177
      } else {
178
179
180
        $ldap = $this->config->get_ldap_link();
        $ldap->cat($this->dn);
        $this->attrs = $ldap->fetch();
181
182
      }

183
      /* Set the template flag according to the existence of objectClass fdTemplate */
184
      if (isset($this->attrs['objectClass'])) {
185
186
        if (in_array_ics ("fdTemplate", $this->attrs['objectClass'])) {
          @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, "found", "Template check");
187
          $this->templateLoadAttrs($this->attrs);
188
189
190
191
        }
      }

      /* Is Account? */
192
      if ($this->is_this_account($this->attrs)) {
193
194
        $this->is_account = TRUE;
        @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, "found", "Object check");
195
      }
196
    }
197

198
199
200
201
    $this->loadAttributes();

    $this->prepareSavedAttributes();

202
203
204
    /* Save initial account state */
    $this->initially_was_account = $this->is_account;
  }
205

206
207
208
209
210
211
212
213
214
215
216
  protected function loadAttributes()
  {
    /* Copy needed attributes */
    foreach ($this->attributes as $val) {
      $found = array_key_ics($val, $this->attrs);
      if ($found != "") {
        $this->$val = $found[0];
      }
    }
  }

217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
  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;
  }

232
233
234
235
  function prepareSavedAttributes()
  {
    /* Prepare saved attributes */
    $this->saved_attributes = $this->attrs;
236
    foreach (array_keys($this->saved_attributes) as $index) {
237
238
239
      if (is_numeric($index)) {
        unset($this->saved_attributes[$index]);
        continue;
240
241
      }

242
243
244
      if (!in_array_ics($index, $this->attributes) && strcasecmp('objectClass', $index)) {
        unset($this->saved_attributes[$index]);
        continue;
245
      }
246
247
248
249
250
251
252
253
254
255
256
257

      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;
        }
      }
258
      unset($this->saved_attributes[$index]["count"]);
259
260
261
    }
  }

262
  protected function templateLoadAttrs($template_attrs)
263
  {
264
265
266
267
    $this->is_template = TRUE;
    if ($this->mainTab) {
      $this->template_cn = $template_attrs['cn'][0];
    }
268
    $this->attrs = self::tpl_template_to_attrs($template_attrs);
269
270
  }

271
  protected function templateSaveAttrs()
272
  {
273
274
275
276
277
278
    $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.');
279
      }
280
281
282
283
      $template_attrs = array(
        'objectClass'     => array('fdTemplate'),
        'fdTemplateField' => array()
      );
284
285
286
287
288
289
290
291
    } else {
      unset($template_attrs['dn']);
      unset($template_attrs['fdTemplateField']['count']);
      unset($template_attrs['objectClass']['count']);
      for ($i = 0; $i < $template_attrs['count']; ++$i) { // Remove numeric keys
        unset($template_attrs[$i]);
      }
      unset($template_attrs['count']);
292
293
294
295
296
297
298
299
300
    }
    if ($this->mainTab) {
      $template_attrs['cn'] = $this->template_cn;
    }
    /* First remove all concerned values */
    foreach ($template_attrs['fdTemplateField'] as $key => $value) {
      preg_match('/^([^:]+):(.*)$/', $value, $m);
      if (isset($this->attrs[$m[1]])) {
        unset($template_attrs['fdTemplateField'][$key]);
301
302
      }
    }
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
    /* 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']);
320
321
322
    return $template_attrs;
  }

323
324
325
326
327
  /*!
   * \brief This function is called on the copied object to set its dn to where it will be saved
   */
  function resetCopyInfos()
  {
328
329
    $this->dn       = 'new';
    $this->orig_dn  = $this->dn;
330

331
332
333
334
    $this->saved_attributes       = array();
    $this->initially_was_account  = FALSE;

    $this->postCopyHook();
335
336
  }

337

338
  /*!
339
   * \brief Generates the html output for this node
340
341
342
343
   */
  function execute()
  {
    /* This one is empty currently. Fabian - please fill in the docu code */
344
    session::global_set('current_class_for_help', get_class($this));
345
346

    /* Reset Lock message POST/GET check array, to prevent perg_match errors*/
347
348
349
350
    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());
351
352
  }

353
  /*!
354
   * \brief Removes object from parent
355
356
357
358
   */
  function remove_from_parent()
  {
    /* include global link_info */
359
    $ldap = $this->config->get_ldap_link();
360
361
362

    /* Get current objectClasses in order to add the required ones */
    $ldap->cat($this->dn);
363
364
365
366
    $tmp  = $ldap->fetch ();
    $oc   = array();
    if (isset($tmp['objectClass'])) {
      $oc = $tmp['objectClass'];
367
368
369
370
371
      unset($oc['count']);
    }

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

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

    /* Do not write in plugin base class, this must be done by
       children, since there are normally additional attribs,
       lists, etc. */
383
384
    if ($this->initially_was_account) {
      $this->handle_pre_events('remove');
385
386
387
388
    }
  }


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

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

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

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

411
412
413
414
415
        /* 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 ...
416
417
         * So we set these Fields to ""; a normal empty string, and we can check these values in plugin::check() again ...
         */
418
        if (isset($data[0]) && ($data[0] == chr(194))) {
419
          $data = "";
420
        }
421
        $this->$val = $data;
422
423
424
425
426
      }
    }
  }


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

    /* Save all plugins */
    $this->entryCSN = "";

    /* Start with empty array */
439
    $this->attrs = array();
440
441
442

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

444
    $tmp = $ldap->fetch ();
445

446
447
448
    $oc = array();
    if (isset($tmp['objectClass'])) {
      $oc = $tmp["objectClass"];
449
      unset($oc['count']);
450
      $this->is_new = FALSE;
451
    } else {
452
      $this->is_new = TRUE;
453
454
455
    }

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

    /* Copy standard attributes */
459
460
461
    foreach ($this->attributes as $val) {
      if ($this->$val != "") {
        $this->attrs["$val"] = $this->$val;
462
      } elseif (!$this->is_new) {
463
        $this->attrs["$val"] = array();
464
465
466
      }
    }

467
468
469
470
    if ($this->is_new) {
      $this->handle_pre_events('add');
    } else {
      $this->handle_pre_events('modify');
471
472
473
    }
  }

474
475
476
477
  /*!
   * \brief Remove attributes, empty arrays, arrays
   * single attributes that do not differ
   */
478
479
  function cleanup()
  {
480
    foreach ($this->attrs as $index => $value) {
481

482
483
      /* Convert arrays with one element to non arrays, if the saved
         attributes are no array, too */
484
      if (is_array($this->attrs[$index]) &&
485
486
          count ($this->attrs[$index]) == 1 &&
          isset($this->saved_attributes[$index]) &&
487
488
          !is_array($this->saved_attributes[$index])) {
        $this->attrs[$index] = $this->attrs[$index][0];
489
490
491
492
493
      }

      /* Remove emtpy arrays if they do not differ */
      if (is_array($this->attrs[$index]) &&
          count($this->attrs[$index]) == 0 &&
494
          !isset($this->saved_attributes[$index])) {
495
496
497
498
499
500
501
502
        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]) &&
503
          $this->attrs[$index] == $this->saved_attributes[$index]) {
504
505
506
507
508
        unset ($this->attrs[$index]);
        continue;
      }

      /* Remove arrays that do not differ */
509
      if (is_array($this->attrs[$index]) &&
510
          isset($this->saved_attributes[$index]) &&
511
512
          is_array($this->saved_attributes[$index])) {
        if (!array_differs($this->attrs[$index], $this->saved_attributes[$index])) {
513
514
515
516
517
518
519
          unset ($this->attrs[$index]);
          continue;
        }
      }
    }

    /* Update saved attributes and ensure that next cleanups will be successful too */
520
    foreach ($this->attrs as $name => $value) {
521
522
523
524
      $this->saved_attributes[$name] = $value;
    }
  }

525
  /*!
526
   * \brief Check formular input
Benoit Mortier's avatar
Benoit Mortier committed
527
   */
528
529
  function check()
  {
530
    $message = array();
531
532

    /* Skip if we've no config object */
533
    if (!isset($this->config) || !is_object($this->config)) {
534
535
536
      return $message;
    }

537
538
539
    self::callHook($this, 'CHECK', array(), $returnOutput);
    if (!empty($returnOutput)) {
      $message[] = join("\n", $returnOutput);
540
541
542
    }

    /* Check entryCSN */
543
    if ($this->CSN_check_active) {
544
      $current_csn = getEntryCSN($this->dn);
545
      if (($current_csn != $this->entryCSN) && !empty($this->entryCSN) && !empty($current_csn)) {
546
        $this->entryCSN = $current_csn;
Benoit Mortier's avatar
Benoit Mortier committed
547
        $message[] = _("The object has changed since opened in FusionDirectory. All changes that may be done by others get lost if you save this entry!");
548
549
      }
    }
550
    return $message;
551
552
  }

553
554
555
  /*
   * \brief Adapt from template, using 'dn'
   *
556
   * \param string $dn The DN
557
558
   *
   * \param array $skip A new array
559
   */
560
  function adapt_from_template($attrs, $skip = array())
561
  {
562
    $this->attrs = $attrs;
563
564

    /* Walk through attributes */
565
    foreach ($this->attributes as $val) {
566
      /* Skip the ones in skip list */
567
      if (in_array($val, $skip)) {
568
569
570
        continue;
      }

571
      if (isset($this->attrs["$val"][0])) {
572
        $this->$val = $this->attrs["$val"][0];
573
574
575
576
      }
    }

    /* Is Account? */
577
    $this->is_account = $this->is_this_account($this->attrs);
578
579
  }

580
581
582
583
  public function setNeedEditMode ($bool)
  {
  }

584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
  static function tpl_fetch_template($dn)
  {
    global $config;

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

  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) {
      preg_match('/^([^:]+):(.*)$/', $field, $m);
      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;
  }

616
617
618
  /* Apply a modifier
   * Returns an array of possible values */
  static function tpl_apply_modifier($m, $args, $str)
619
  {
620
621
    mb_internal_encoding('UTF-8');
    mb_regex_encoding('UTF-8');
622
623
624
625
    if (is_array($str) && (strtolower($m) == $m)) {
      /* $str is an array and $m is lowercase, so it's a string modifier */
      $str = $str[0];
    }
626
    switch ($m) {
627
628
629
630
631
632
633
634
635
636
      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));
        }
637
638
      case 'c': // comment
        return array('');
639
640
641
642
643
      case 'b': // base64
        if (isset($args[0]) && ($args[0] == 'd')) {
          return array(base64_decode($str));
        }
        return array(base64_encode($str));
644
645
646
647
648
      case 'u': // uppercase
        return array(mb_strtoupper($str, 'UTF-8'));
      case 'l': // lowercase
        return array(mb_strtolower($str, 'UTF-8'));
      case 'a': // remove accent
649
650
651
652
        $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
653
        return array(preg_replace('#&[^;]+;#', '', $str)); // delete unhandled characters
654
655
      case 'p': // spaces
        return array(preg_replace('/\s/u', '', $str));
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
      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]));
        }
672
673
      default:
        trigger_error("Unkown modifier '$m'");
674
        return array($str);
675
676
677
    }
  }

678
  static function tpl_parse_mask($mask, $attrs)
679
680
  {
    if ($mask == "|") {
681
      return array("%");
682
    }
683
    $modifiers = '';
684
    if (preg_match('/^([^|]+)\|/', $mask, $m)) {
685
      $modifiers = $m[1];
686
687
      $mask = substr($mask, strlen($m[0]));
    }
688
689
    if (isset($attrs[$mask])) {
      $result = array($attrs[$mask]);
690
691
692
      if (is_array($result[0])) {
        unset($result[0]['count']);
      }
693
694
    } else {
      trigger_error("'$mask' was not found in attributes");
695
      $result = array('');
696
    }
697
    $len    = strlen($modifiers);
698
    for ($i = 0; $i < $len; ++$i) {
699
700
      $args     = array();
      $modifier = $modifiers[$i];
701
      if (preg_match('/^\[([^\]]+)\].*$/', substr($modifiers, $i + 1), $m)) {
702
703
        /* get modifier args */
        $args = explode(',', $m[1]);
704
        $i += strlen($m[1]) + 2;
705
706
707
708
709
710
      }
      $result_tmp = array();
      foreach ($result as $r) {
        $result_tmp = array_merge($result_tmp, self::tpl_apply_modifier($modifier, $args, $r));
      }
      $result = $result_tmp;
711
    }
712
713
714
715
716
717
    foreach ($result as &$r) { // Array that were not converted by a modifier into a string are now converted to strings
      if (is_array($r)) {
        $r = $r[0];
      }
    }
    unset($r);
718
719
720
    return $result;
  }

721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
  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)
        {
          return plugin::tpl_depends_of ($cache, $depends, $a, $forbidden);
        },
        $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
   */
  static function tpl_needed_attrs($attrs, $flatdepends)
  {
    $needed = array();
    $dependencies = array_unique(call_user_func_array('array_merge', $flatdepends));
    foreach ($dependencies as $attr) {
      if (empty($flatdepends[$attr])) {
        $needed[] = $attr;
      }
    }
    return $needed;
  }


  /*! Brief Parse attrs template masks
   *
   * return an array with the final values of attributes
   */
  static function tpl_parse_attrs($attrs)
816
  {
817
    foreach ($attrs as &$attr) {
818
819
820
821
822
823
      if (is_array($attr)) {
        foreach ($attr as $key => &$string) {
          if (!is_numeric($key)) {
            continue;
          }
          $string = self::tpl_parse_string($string, $attrs);
824
        }
825
        unset($string);
826
827
828
829
      }
    }
    unset($attr);
    return $attrs;
830
831
  }

832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
  /*! 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;
  }

848
849
  /*!
   * \brief Show header message for tab dialogs
850
   *
851
   * \param string $button_text The button text
852
   *
853
   * \param string $text The text
854
   *
855
   * \param boolean $disabled FALSE
Benoit Mortier's avatar
Benoit Mortier committed
856
   */
857
  function show_enable_header($button_text, $text, $disabled = FALSE, $name = "modify_state")
858
  {
859
    return $this->show_header($button_text, $text, FALSE, $disabled, $name);
860
861
862
  }


863
  /*!
864
   * \brief Show header message for tab dialogs
865
   *
866
   * \param string $button_text The button text
867
   *
868
   * \param string $text The text
869
   *
870
   * \param boolean $disabled FALSE
Benoit Mortier's avatar
Benoit Mortier committed
871
   */
872
  function show_disable_header($button_text, $text, $disabled = FALSE, $name = "modify_state")
873
  {
874
    return $this->show_header($button_text, $text, TRUE, $disabled, $name);
875
876
877
  }


878
  /*!
879
   * \brief Show header message for tab dialogs
880
   *
881
   * \param string $button_text The button text
882
   *
883
   * \param string $text The text
884
   *
885
   * \param boolean $plugin_enabled
886
   *
887
   * \param boolean $button_disabled FALSE
Benoit Mortier's avatar
Benoit Mortier committed
888
   */
889
  function show_header($button_text, $text, $plugin_enabled, $button_disabled = FALSE, $name = "modify_state")
890
  {
891
    if (($button_disabled) || ((!$this->acl_is_createable() && !$plugin_enabled) || (!$this->acl_is_removeable() && $plugin_enabled))) {
892
        $state = "disabled=\"disabled\"";
893
    } else {
894
        $state = "";
895
    }
896
897
    $display = "<div width=\"100%\"><p><b>$text</b><br/>\n";
    $display .= "<input type=\"submit\" value=\"$button_text\" name=\"$name\" ".$state.
898
      "></p></div><hr class=\"separator\"/>";
899

900
    return $display;
901
902
  }

903
904
905
906
907
908
909
  /*!
   * \brief Executes a command after an object has been copied
   */
  function postCopyHook()
  {
  }

910
  /*!
911
912
913
   * \brief Create unique DN
   *
   * \param string $attribute
914
   *
915
916
   * \param string $base
   */
917
918
  function create_unique_dn($attribute, $base)
  {
919
920
    $ldap = $this->config->get_ldap_link();
    $base = preg_replace('/^,*/', '', $base);
921
922

    /* Try to use plain entry first */
923
924
925
926
927
928
929
    $dn = "$attribute=".$this->$attribute.",$base";
    if ($dn == $this->orig_dn) {
      return $dn;
    }
    $ldap->cat($dn, array('dn'));
    if (!$ldap->fetch()) {
      return $dn;
930
931
932
    }

    /* Look for additional attributes */
933
934
    foreach ($this->attributes as $attr) {
      if ($attr == $attribute || $this->$attr == "" || is_array($this->$attr)) {
935
936
937
        continue;
      }

938
939
940
941
942
943
944
      $dn = "$attribute=".$this->$attribute."+$attr=".$this->$attr.",$base";
      if ($dn == $this->orig_dn) {
        return $dn;
      }
      $ldap->cat($dn, array('dn'));
      if (!$ldap->fetch()) {
        return $dn;
945
946
947
948
      }
    }

    /* None found */
949
    return 'none';
950
951
  }

952
953
954
  /*!
   * \brief ldap rebind
   *
955
   * \param string $ldap
956
   *
957
958
   * \param string $referral
   */
959
960
  function rebind($ldap, $referral)
  {
961
    $credentials = LDAP::get_credentials($referral, $this->config->current['REFERRAL']);
962
    if (ldap_bind($ldap, $credentials['ADMIN'], $this->config->get_credentials($credentials['PASSWORD']))) {
963
964
965
966
      $this->error      = "Success";
      $this->hascon     = TRUE;
      $this->reconnect  = TRUE;
      return 0;
967
968
969
970
971
972
    } else {
      $this->error = "Could not bind to " . $credentials['ADMIN'];
      return NULL;
    }
  }

973
  /*
974
   * \brief Recursively copy ldap object
975
   *
976
   * \param string $src_dn The DN source
977
   *
978
979
   * \param string $dst_dn The DN destination
   */
980
  function _copy($src_dn, $dst_dn)
981
  {
982
    $ldap = $this->config->get_ldap_link();
983
    $ldap->cat($src_dn);
984
    $attrs = $ldap->fetch();
985
986

    /* Grummble. This really sucks. PHP ldap doesn't support rdn stuff. */
987
    $ds = ldap_connect($this->config->current['SERVER']);
988
989
990
991
992
    ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
    if (function_exists("ldap_set_rebind_proc") && isset($this->config->current['REFERRAL'])) {
      ldap_set_rebind_proc($ds, array(&$this, "rebind"));
    }

993
    $pwd  = $this->config->get_credentials($this->config->current['ADMINPASSWORD']);
994
    ldap_bind($ds, $this->config->current['ADMINDN'], $pwd);
995
996

    /* Fill data from LDAP */
997
998
999
    $new = array();
    if ($sr = ldap_read($ds, LDAP::fix($src_dn), "objectClass=*")) {
      if ($ei = ldap_first_entry($ds, $sr)) {
1000
        foreach (array_keys($attrs) as $attr) {
For faster browsing, not all history is shown. View entire blame