FusionDirectory
 All Data Structures Files Functions Variables
class_templateHandling.inc
Go to the documentation of this file.
1 <?php
2 /*
3  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
4  Copyright (C) 2011-2016 FusionDirectory
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with this program; if not, write to the Free Software
18  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20 
29 {
31  public static function fetch($dn)
32  {
33  global $config;
34 
35  $ldap = $config->get_ldap_link();
36  $ldap->cat($dn);
37  $attrs = $ldap->fetch();
38  $attrs = static::fieldsFromLDAP($attrs);
39  list($depends, $errors) = static::attributesDependencies($attrs);
40  msg_dialog::displayChecks($errors);
41  $attrs = static::sortAttributes($attrs, $depends);
42  return array($attrs, $depends);
43  }
44 
46  public static function fieldsFromLDAP (array $template_attrs)
47  {
48  unset($template_attrs['fdTemplateField']['count']);
49  sort($template_attrs['fdTemplateField']);
50  $attrs = array();
51  foreach ($template_attrs['fdTemplateField'] as $field) {
52  preg_match('/^([^:]+):(.*)$/s', $field, $m);
53  if (isset($attrs[$m[1]])) {
54  $attrs[$m[1]][] = $m[2];
55  $attrs[$m[1]]['count']++;
56  } else {
57  $attrs[$m[1]] = array($m[2]);
58  $attrs[$m[1]]['count'] = 1;
59  }
60  }
61  return $attrs;
62  }
63 
65  public static function fieldsToLDAP (array $template_attrs, array $attrs)
66  {
67  /* First a bit of cleanup */
68  unset($template_attrs['dn']);
69  unset($template_attrs['fdTemplateField']['count']);
70  unset($template_attrs['objectClass']['count']);
71  unset($template_attrs['cn']['count']);
72  if (isset($template_attrs['count'])) {
73  for ($i = 0; $i < $template_attrs['count']; ++$i) {
74  /* Remove numeric keys */
75  unset($template_attrs[$i]);
76  }
77  }
78  unset($template_attrs['count']);
79 
80  /* Remove all concerned values */
81  foreach ($template_attrs['fdTemplateField'] as $key => $value) {
82  preg_match('/^([^:]+):(.*)$/s', $value, $m);
83  if (isset($attrs[$m[1]])) {
84  unset($template_attrs['fdTemplateField'][$key]);
85  }
86  }
87  /* Then insert non-empty values */
88  foreach ($attrs as $key => $value) {
89  if (is_array($value)) {
90  foreach ($value as $v) {
91  if ($value == "") {
92  continue;
93  }
94  $template_attrs['fdTemplateField'][] = $key.':'.$v;
95  }
96  } else {
97  if ($value == "") {
98  continue;
99  }
100  $template_attrs['fdTemplateField'][] = $key.':'.$value;
101  }
102  }
103  sort($template_attrs['fdTemplateField']);
104  return $template_attrs;
105  }
106 
112  public static function checkFields ($attrs)
113  {
114  list(, $errors) = static::attributesDependencies($attrs);
115  return $errors;
116  }
117 
119  public static function parseMask($mask, array $attrs)
120  {
121  if ($mask == '|') {
122  return array('%');
123  }
124  $modifiers = '';
125  if (preg_match('/^([^|]+)\|/', $mask, $m)) {
126  $modifiers = $m[1];
127  $mask = substr($mask, strlen($m[0]));
128  }
129  $result = array('');
130  if (isset($attrs[$mask])) {
131  $result = array($attrs[$mask]);
132  if (is_array($result[0])) {
133  unset($result[0]['count']);
134  }
135  } elseif (($mask != '') && !preg_match('/c/', $modifiers)) {
136  trigger_error("'$mask' was not found in attributes");
137  }
138  $len = strlen($modifiers);
139  for ($i = 0; $i < $len; ++$i) {
140  $args = array();
141  $modifier = $modifiers[$i];
142  if (preg_match('/^\[([^\]]+)\].*$/', substr($modifiers, $i + 1), $m)) {
143  /* get modifier args */
144  $args = explode(',', $m[1]);
145  $i += strlen($m[1]) + 2;
146  }
147  $result_tmp = array();
148  foreach ($result as $r) {
149  $result_tmp = array_merge($result_tmp, static::applyModifier($modifier, $args, $r));
150  }
151  $result = $result_tmp;
152  }
153  foreach ($result as &$r) {
154  /* Array that were not converted by a modifier into a string are now converted to strings */
155  if (is_array($r)) {
156  $r = reset($r);
157  }
158  }
159  unset($r);
160  return $result;
161  }
162 
167  public static function neededAttrs(array &$attrs, array $flatdepends)
168  {
169  $needed = array();
170  foreach ($flatdepends as $attr => $depends) {
171  if ((isset($depends[0])) && ($depends[0] == 'askme')) {
172  $needed[] = $attr;
173  unset($flatdepends[$attr]);
174  unset($attrs[$attr]);
175  }
176  }
177  $dependencies = array_unique(call_user_func_array('array_merge', $flatdepends));
178  foreach ($dependencies as $attr) {
179  if (empty($flatdepends[$attr])) {
180  $needed[] = $attr;
181  }
182  }
183  return array_unique($needed);
184  }
185 
190  public static function parseArray(array $attrs)
191  {
192  foreach ($attrs as &$attr) {
193  if (is_array($attr)) {
194  foreach ($attr as $key => &$string) {
195  if (!is_numeric($key)) {
196  continue;
197  }
198  $string = static::parseString($string, $attrs);
199  }
200  unset($string);
201  }
202  }
203  unset($attr);
204  return $attrs;
205  }
206 
211  public static function parseString($string, array $attrs, $escapeMethod = NULL)
212  {
213  if (preg_match('/^%%/', $string)) {
214  /* Special case: %% at beginning of string means do not touch it. Used by binary attributes. */
215  return preg_replace('/^%%/', '', $string);
216  }
217  $offset = 0;
218  while (preg_match('/%([^%]+)%/', $string, $m, PREG_OFFSET_CAPTURE, $offset)) {
219  $replace = static::parseMask($m[1][0], $attrs);
220  $replace = $replace[0];
221  if ($escapeMethod !== NULL) {
222  $replace = $escapeMethod($replace);
223  }
224  $string = substr_replace($string, $replace, $m[0][1], strlen($m[0][0]));
225  $offset = $m[0][1] + strlen($replace);
226  }
227  return $string;
228  }
229 
234  public static function listFields($string)
235  {
236  $fields = array();
237  $offset = 0;
238  while (preg_match('/%([^%]+)%/', $string, $m, PREG_OFFSET_CAPTURE, $offset)) {
239  $mask = $m[1][0];
240  $offset = $m[0][1] + strlen($m[0][0]);
241  if ($mask == '|') {
242  continue;
243  }
244  if (preg_match('/^([^|]+)\|/', $mask, $m)) {
245  $mask = substr($mask, strlen($m[0]));
246  }
247  $fields[] = $mask;
248  }
249  return $fields;
250  }
251 
252  private static function modifierRemoveAccents($str)
253  {
254  $str = htmlentities($str, ENT_NOQUOTES, 'UTF-8');
255 
256  $str = preg_replace('#&([A-za-z])(?:acute|cedil|circ|grave|orn|ring|slash|th|tilde|uml);#', '\1', $str);
257  // handle ligatures
258  $str = preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $str);
259  // delete unhandled characters
260  return array(preg_replace('#&[^;]+;#', '', $str));
261  }
262 
263  private static function modifierTranslit(array $args, $str)
264  {
265  $localesaved = setlocale(LC_CTYPE, 0);
266  $ret = array();
267  foreach ($args as $arg) {
268  setlocale(LC_CTYPE, array($arg,"$arg.UTF8"));
269  $ret[] = iconv('UTF8', 'ASCII//TRANSLIT', $str);
270  }
271  setlocale(LC_CTYPE, $localesaved);
272  return array_unique($ret);
273  }
274 
275  private static function modifierPregReplace(array $args, $str)
276  {
277  $pattern = '/\s/';
278  $replace = '';
279  if (count($args) >= 1) {
280  $pattern = $args[0];
281  if (count($args) >= 2) {
282  $replace = $args[1];
283  }
284  }
285 
286  return array(preg_replace($pattern.'u', $replace, $str));
287  }
288 
289  private static function modifierSubString(array $args, $str)
290  {
291  if (count($args) < 1) {
292  trigger_error("Missing 's' substr modifier parameter");
293  }
294  if (count($args) < 2) {
295  array_unshift($args, 0);
296  }
297  if (preg_match('/^(\d+)-(\d+)$/', $args[1], $m)) {
298  $res = array();
299  for ($i = $m[1];$i < $m[2]; ++$i) {
300  $res[] = mb_substr($str, $args[0], $i);
301  }
302  return array_unique($res);
303  } else {
304  return array(mb_substr($str, $args[0], $args[1]));
305  }
306  }
307 
308  private static function modifierRandomString(array $args)
309  {
310  $length = 8;
311  $chars = 'b';
312  if (count($args) >= 2) {
313  $length = random_int($args[0], $args[1]);
314  if (count($args) >= 3) {
315  $chars = $args[2];
316  }
317  } elseif (count($args) >= 1) {
318  $length = $args[0];
319  }
320  $res = '';
321  for ($i = 0; $i < $length; ++$i) {
322  switch ($chars) {
323  case 'd':
324  /* digits */
325  $res .= (string)random_int(0, 9);
326  break;
327  case 'l':
328  /* letters */
329  $nb = random_int(65, 116);
330  if ($nb > 90) {
331  /* lowercase */
332  $nb += 6;
333  }
334  $res .= chr($nb);
335  break;
336  case 'b':
337  /* both */
338  default:
339  $nb = random_int(65, 126);
340  if ($nb > 116) {
341  /* digit */
342  $nb = (string)($nb - 117);
343  } else {
344  if ($nb > 90) {
345  /* lowercase */
346  $nb += 6;
347  }
348  $nb = chr($nb);
349  }
350  $res .= $nb;
351  break;
352  }
353  }
354 
355  return $res;
356  }
357 
358  private static function modifierDate(array $args)
359  {
360  if (count($args) < 1) {
361  $args[] = 'now';
362  }
363  if (count($args) < 2) {
364  $args[] = 'd.m.Y';
365  }
366  $dateObject = new DateTime($args[0], new DateTimeZone('UTC'));
367  if ($args[1] == 'epoch') {
368  /* Special handling for shadowExpire: days since epoch */
369  return array(floor($dateObject->format('U') / 86400));
370  }
371  return array($dateObject->format($args[1]));
372  }
373 
382  protected static function applyModifier($m, array $args, $str)
383  {
384  mb_internal_encoding('UTF-8');
385  mb_regex_encoding('UTF-8');
386  if (is_array($str) && (!is_numeric($m)) && (strtolower($m) == $m)) {
387  /* $str is an array and $m is lowercase, so it's a string modifier */
388  $str = reset($str);
389  }
390  $result = array($str);
391  switch ($m) {
392  case 'F':
393  // First
394  $result = array(reset($str));
395  break;
396  case 'L':
397  // Last
398  $result = array(end($str));
399  break;
400  case 'J':
401  // Join
402  if (isset($args[0])) {
403  $result = array(join($args[0], $str));
404  } else {
405  $result = array(join($str));
406  }
407  break;
408  case 'C':
409  // Count
410  $result = array(count($str));
411  break;
412  case 'M':
413  // Match
414  if (count($args) < 1) {
415  trigger_error('Missing "M" match modifier parameter');
416  $args[] = '/.*/';
417  }
418  $result = array(array_filter($str, function ($s) use ($args) { return preg_match($args[0], $s); }));
419  break;
420  case '4':
421  // IPv4
422  $result = array(array_filter($str, 'tests::is_ipv4'));
423  break;
424  case '6':
425  // IPv6
426  $result = array(array_filter($str, 'tests::is_ipv6'));
427  break;
428  case 'c':
429  // comment
430  $result = array('');
431  break;
432  case 'b':
433  // base64
434  if (isset($args[0]) && ($args[0] == 'd')) {
435  $result = array(base64_decode($str));
436  }
437  $result = array(base64_encode($str));
438  break;
439  case 'u':
440  // uppercase
441  $result = array(mb_strtoupper($str, 'UTF-8'));
442  break;
443  case 'l':
444  // lowercase
445  $result = array(mb_strtolower($str, 'UTF-8'));
446  break;
447  case 'a':
448  // remove accent
449  $result = static::modifierRemoveAccents($str);
450  break;
451  case 't':
452  // translit
453  $result = static::modifierTranslit($args, $str);
454  break;
455  case 'p':
456  // spaces
457  $result = static::modifierPregReplace($args, $str);
458  break;
459  case 's':
460  // substring
461  $result = static::modifierSubString($args, $str);
462  break;
463  case 'r':
464  // random string
465  $result = array(static::modifierRandomString($args));
466  break;
467  case 'd':
468  // date
469  $result = array(static::modifierDate($args));
470  break;
471  default:
472  trigger_error("Unkown modifier '$m'");
473  $result = array($str);
474  break;
475  }
476  return $result;
477  }
478 
480  protected static function flatDepends (&$cache, &$errors, $depends, $key, array $forbidden = array())
481  {
482  if (isset($cache[$key])) {
483  return $cache[$key];
484  }
485 
486  $forbidden[] = $key;
487 
488  $array =
489  array_map(
490  function ($a) use (&$cache, &$errors, $depends, $forbidden, $key)
491  {
492  if (in_array($a, $forbidden)) {
493  $errors[] = sprintf(
494  _('Recursive dependency in the template fields: "%1$s" cannot depend on "%2$s" as "%2$s" already depends on "%1$s"'),
495  $key,
496  $a
497  );
498  return array();
499  }
500  $deps = static::flatDepends ($cache, $errors, $depends, $a, $forbidden);
501  if (($askmeKey = array_search('askme', $deps)) !== FALSE) {
502  /* Do not flat special askme dependency */
503  unset($deps[$askmeKey]);
504  }
505  return $deps;
506  },
507  $depends[$key]
508  );
509  $array[] = $depends[$key];
510  $cache[$key] = array_unique(call_user_func_array('array_merge_recursive', $array));
511  return $cache[$key];
512  }
513 
515  protected static function attributesDependencies(array $attrs)
516  {
517  /* Compute dependencies of each attr */
518  $depends = array();
519  foreach ($attrs as $key => $values) {
520  $depends[$key] = array();
521  if (!is_array($values)) {
522  $values = array($values);
523  }
524  unset ($values['count']);
525  foreach ($values as $value) {
526  $offset = 0;
527  while (preg_match('/%([^%\|]+\|)?([^%]+)%/', $value, $m, PREG_OFFSET_CAPTURE, $offset)) {
528  $offset = $m[0][1] + strlen($m[0][0]);
529  $depends[$key][] = $m[2][0];
530  if (!isset($attrs[$m[2][0]])) {
531  /* Dependency which has no value might be missing */
532  $attrs[$m[2][0]] = array();
533  $depends[$m[2][0]] = array();
534  }
535  }
536  }
537  }
538  /* Flattens dependencies */
539  $flatdepends = array();
540  $errors = array();
541  foreach ($depends as $key => $value) {
542  static::flatDepends($flatdepends, $errors, $depends, $key);
543  }
544  return array($flatdepends, $errors);
545  }
546 
548  protected static function sortAttributes(array $attrs, array $flatdepends)
549  {
550  uksort($attrs, function ($k1, $k2) use ($flatdepends) {
551  if (in_array($k1, $flatdepends[$k2])) {
552  return -1;
553  } elseif (in_array($k2, $flatdepends[$k1])) {
554  return 1;
555  } else {
556  /* When no direct dependency, we sort by number of dependencies */
557  $c1 = count($flatdepends[$k1]);
558  $c2 = count($flatdepends[$k2]);
559  if ($c1 == $c2) {
560  return 0;
561  }
562  return (($c1 < $c2) ? -1 : 1);
563  }
564  });
565  return $attrs;
566  }
567 }
this class stores static methods used to parse templates LDAP data
static listFields($string)
Parse template masks in a single string and list the fields it needs.
static parseString($string, array $attrs, $escapeMethod=NULL)
Parse template masks in a single string.
static fetch($dn)
Fetch a template from LDAP and returns its attributes and dependencies information.
static flatDepends(&$cache, &$errors, $depends, $key, array $forbidden=array())
Flattens dependencies (if a depends of b which depends of c then a depends of c)
static parseMask($mask, array $attrs)
Parse a mask (without surrounding %) using $attrs attributes, apply modifiers and returns an array co...
static neededAttrs(array &$attrs, array $flatdepends)
Return attrs needed before applying template.
static fieldsFromLDAP(array $template_attrs)
Translate template attrs into $attrs as if taken from LDAP.
static fieldsToLDAP(array $template_attrs, array $attrs)
Translate $attrs into template attrs.
static sortAttributes(array $attrs, array $flatdepends)
Sort attrs depending of dependencies.
static attributesDependencies(array $attrs)
Computes dependencies between attributes: which attributes must be filled in order to compute each at...
static applyModifier($m, array $args, $str)
Apply a modifier.
static checkFields($attrs)
Check template fields.
static parseArray(array $attrs)
Parse template masks in an array.