init
[garradin.git] / include / lib.template.php
1 <?php
2
3 namespace Garradin;
4
5 require_once ROOT . '/include/libs/template_lite/class.template.php';
6
7 class Template extends \Template_Lite
8 {
9 static protected $_instance = null;
10
11 static public function getInstance()
12 {
13 return self::$_instance ?: self::$_instance = new Template;
14 }
15
16 private function __clone()
17 {
18 }
19
20 public function __construct()
21 {
22 parent::__construct();
23
24 $this->cache = false;
25
26 $this->compile_dir = DATA_ROOT . '/cache/compiled';
27 $this->template_dir = ROOT . '/templates';
28
29 $this->compile_check = true;
30
31 $this->reserved_template_varname = 'tpl';
32
33 $this->assign('www_url', WWW_URL);
34 $this->assign('self_url', utils::getSelfUrl());
35
36 $this->assign('is_logged', false);
37 }
38 }
39
40 $tpl = Template::getInstance();
41
42 function tpl_csrf_field($params)
43 {
44 $name = utils::CSRF_field_name($params['key']);
45 $value = utils::CSRF_create($params['key']);
46
47 return '<input type="hidden" name="'.$name.'" value="'.$value.'" />';
48 }
49
50 function tpl_form_field($params)
51 {
52 if (!isset($params['name']))
53 throw new \BadFunctionCallException('name argument is mandatory');
54
55 $name = $params['name'];
56
57 if (isset($_POST[$name]))
58 $value = $_POST[$name];
59 elseif (isset($params['data']) && isset($params['data'][$name]))
60 $value = $params['data'][$name];
61 elseif (isset($params['default']))
62 $value = $params['default'];
63 else
64 $value = '';
65
66 if (is_array($value))
67 {
68 return $value;
69 }
70
71 if (isset($params['checked']))
72 {
73 if ($value == $params['checked'])
74 return ' checked="checked" ';
75
76 return '';
77 }
78 elseif (isset($params['selected']))
79 {
80 if ($value == $params['selected'])
81 return ' selected="selected" ';
82
83 return '';
84 }
85
86 return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
87 }
88
89 function tpl_format_tel($n)
90 {
91 $n = preg_replace('![^\d\+]!', '', $n);
92
93 if (substr($n, 0, 1) == '+')
94 {
95 $n = preg_replace('!^\+(?:1|2[07]|2\d{2}|3[0-469]|3\d{2}|4[013-9]|'
96 . '4\d{2}|5[1-8]|5\d{2}|6[0-6]|6\d{2}|7\d|8[1-469]|8\d{2}|'
97 . '9[0-58]|9\d{2})!', '\\0 ', $n);
98 }
99 elseif (preg_match('/^\d{10}$/', $n))
100 {
101 $n = preg_replace('!(\d{2})!', '\\1 ', $n);
102 }
103
104 return $n;
105 }
106
107 function tpl_strftime_fr($ts, $format)
108 {
109 return utils::strftime_fr($format, $ts);
110 }
111
112 function tpl_date_fr($ts, $format)
113 {
114 return utils::date_fr($format, $ts);
115 }
116
117 function tpl_format_droits($params)
118 {
119 $droits = $params['droits'];
120
121 $out = ['connexion' => '', 'inscription' => '', 'membres' => '', 'compta' => '',
122 'wiki' => '', 'config' => ''];
123 $classes = [
124 Membres::DROIT_AUCUN => 'aucun',
125 Membres::DROIT_ACCES => 'acces',
126 Membres::DROIT_ECRITURE=> 'ecriture',
127 Membres::DROIT_ADMIN => 'admin',
128 ];
129
130 foreach ($droits as $cle=>$droit)
131 {
132 $cle = str_replace('droit_', '', $cle);
133
134 if (array_key_exists($cle, $out))
135 {
136
137 $class = $classes[$droit];
138 $desc = false;
139 $s = false;
140
141 if ($cle == 'connexion')
142 {
143 if ($droit == Membres::DROIT_AUCUN)
144 $desc = 'N\'a pas le droit de se connecter';
145 else
146 $desc = 'A le droit de se connecter';
147 }
148 elseif ($cle == 'inscription')
149 {
150 if ($droit == Membres::DROIT_AUCUN)
151 $desc = 'N\'a pas le droit de s\'inscrire seul';
152 else
153 $desc = 'A le droit de s\'inscrire seul';
154 }
155 elseif ($cle == 'config')
156 {
157 $s = '&#x2611;';
158
159 if ($droit == Membres::DROIT_AUCUN)
160 $desc = 'Ne peut modifier la configuration';
161 else
162 $desc = 'Peut modifier la configuration';
163 }
164 elseif ($cle == 'compta')
165 {
166 $s = '&euro;';
167 }
168
169 if (!$s)
170 $s = strtoupper($cle[0]);
171
172 if (!$desc)
173 {
174 $desc = ucfirst($cle). ' : ';
175
176 if ($droit == Membres::DROIT_AUCUN)
177 $desc .= 'Pas accès';
178 elseif ($droit == Membres::DROIT_ACCES)
179 $desc .= 'Lecture uniquement';
180 elseif ($droit == Membres::DROIT_ECRITURE)
181 $desc .= 'Lecture & écriture';
182 else
183 $desc .= 'Administration';
184 }
185
186 $out[$cle] = '<b class="'.$class.' '.$cle.'" title="'
187 .htmlspecialchars($desc, ENT_QUOTES, 'UTF-8').'">'.$s.'</b>';
188 }
189 }
190
191 return implode(' ', $out);
192 }
193
194 function tpl_format_wiki($str)
195 {
196 $str = utils::htmlLinksOnUrls($str);
197 $str = utils::htmlSpip($str);
198 $str = utils::htmlGarbage2xhtml($str);
199 return $str;
200 }
201
202 function tpl_liens_wiki($str, $prefix)
203 {
204 return preg_replace_callback('!<a href="([^/.:@]+)">!i', function ($matches) use ($prefix) {
205 return '<a href="' . $prefix . Wiki::transformTitleToURI($matches[1]) . '">';
206 }, $str);
207 }
208
209 function tpl_pagination($params)
210 {
211 if (!isset($params['url']) || !isset($params['page']) || !isset($params['bypage']) || !isset($params['total']))
212 throw new \BadFunctionCallException("Paramètre manquant pour pagination");
213
214 if ($params['total'] == -1)
215 return '';
216
217 $pagination = utils::getGenericPagination($params['page'], $params['total'], $params['bypage']);
218
219 if (empty($pagination))
220 return '';
221
222 $out = '<ul class="pagination">';
223
224 foreach ($pagination as &$page)
225 {
226 $attributes = '';
227
228 if (!empty($page['class']))
229 $attributes .= ' class="' . htmlspecialchars($page['class']) . '" ';
230
231 $out .= '<li'.$attributes.'>';
232
233 $attributes = '';
234
235 if (!empty($page['accesskey']))
236 $attributes .= ' accesskey="' . htmlspecialchars($page['accesskey']) . '" ';
237
238 $out .= '<a' . $attributes . ' href="' . str_replace('[ID]', htmlspecialchars($page['id']), $params['url']) . '">';
239 $out .= htmlspecialchars($page['label']);
240 $out .= '</a>';
241 $out .= '</li>' . "\n";
242 }
243
244 $out .= '</ul>';
245
246 return $out;
247 }
248
249 function tpl_diff($params)
250 {
251 if (!isset($params['old']) || !isset($params['new']))
252 {
253 throw new Template_Exception('Paramètres old et new requis.');
254 }
255
256 $old = $params['old'];
257 $new = $params['new'];
258
259 require_once ROOT . '/include/libs/diff/class.simplediff.php';
260 $diff = \simpleDiff::diff_to_array(false, $old, $new, 3);
261
262 $out = '<table class="diff">';
263 $prev = key($diff);
264
265 foreach ($diff as $i=>$line)
266 {
267 if ($i > $prev + 1)
268 {
269 $out .= '<tr><td colspan="5" class="separator"><hr /></td></tr>';
270 }
271
272 list($type, $old, $new) = $line;
273
274 $class1 = $class2 = '';
275 $t1 = $t2 = '';
276
277 if ($type == \simpleDiff::INS)
278 {
279 $class2 = 'ins';
280 $t2 = '<b class="icn">➕</b>';
281 $old = htmlspecialchars($old, ENT_QUOTES, 'UTF-8');
282 $new = htmlspecialchars($new, ENT_QUOTES, 'UTF-8');
283 }
284 elseif ($type == \simpleDiff::DEL)
285 {
286 $class1 = 'del';
287 $t1 = '<b class="icn">➖</b>';
288 $old = htmlspecialchars($old, ENT_QUOTES, 'UTF-8');
289 $new = htmlspecialchars($new, ENT_QUOTES, 'UTF-8');
290 }
291 elseif ($type == \simpleDiff::CHANGED)
292 {
293 $class1 = 'del';
294 $class2 = 'ins';
295 $t1 = '<b class="icn">➖</b>';
296 $t2 = '<b class="icn">➕</b>';
297
298 $lineDiff = \simpleDiff::wdiff($old, $new);
299 $lineDiff = htmlspecialchars($lineDiff, ENT_QUOTES, 'UTF-8');
300
301 // Don't show new things in deleted line
302 $old = preg_replace('!\{\+(?:.*)\+\}!U', '', $lineDiff);
303 $old = str_replace(' ', ' ', $old);
304 $old = str_replace('-] [-', ' ', $old);
305 $old = preg_replace('!\[-(.*)-\]!U', '<del>\\1</del>', $old);
306
307 // Don't show old things in added line
308 $new = preg_replace('!\[-(?:.*)-\]!U', '', $lineDiff);
309 $new = str_replace(' ', ' ', $new);
310 $new = str_replace('+} {+', ' ', $new);
311 $new = preg_replace('!\{\+(.*)\+\}!U', '<ins>\\1</ins>', $new);
312 }
313 else
314 {
315 $old = htmlspecialchars($old, ENT_QUOTES, 'UTF-8');
316 $new = htmlspecialchars($new, ENT_QUOTES, 'UTF-8');
317 }
318
319 $out .= '<tr>';
320 $out .= '<td class="line">'.($i+1).'</td>';
321 $out .= '<td class="leftChange">'.$t1.'</td>';
322 $out .= '<td class="leftText '.$class1.'">'.$old.'</td>';
323 $out .= '<td class="rightChange">'.$t2.'</td>';
324 $out .= '<td class="rightText '.$class2.'">'.$new.'</td>';
325 $out .= '</tr>';
326
327 $prev = $i;
328 }
329
330 $out .= '</table>';
331 return $out;
332 }
333
334 function tpl_select_compte($params)
335 {
336 $name = $params['name'];
337 $comptes = $params['comptes'];
338 $selected = isset($params['data'][$params['name']]) ? $params['data'][$params['name']] : utils::post($name);
339
340 $out = '<select name="'.$name.'" id="f_'.$name.'" class="large">';
341
342 foreach ($comptes as $compte)
343 {
344 // Ne pas montrer les comptes désactivés
345 if (!empty($compte['desactive']))
346 continue;
347
348 if (!isset($compte['id'][1]))
349 {
350 $out.= '<optgroup label="'.htmlspecialchars($compte['libelle'], ENT_QUOTES, 'UTF-8', false).'" class="niveau_1"></optgroup>';
351 }
352 elseif (!isset($compte['id'][2]) && empty($params['create']))
353 {
354 if ($compte['id'] > 10)
355 $out.= '</optgroup>';
356
357 $out.= '<optgroup label="'.htmlspecialchars($compte['id'] . ' - ' . $compte['libelle'], ENT_QUOTES, 'UTF-8', false).'" class="niveau_2">';
358 }
359 else
360 {
361 $out .= '<option value="'.htmlspecialchars($compte['id'], ENT_QUOTES, 'UTF-8', false).'" class="niveau_'.strlen($compte['id']).'"';
362
363 if ($selected == $compte['id'])
364 {
365 $out .= ' selected="selected"';
366 }
367
368 $out .= '>' . htmlspecialchars($compte['id'] . ' - ' . $compte['libelle'], ENT_QUOTES, 'UTF-8', false);
369 $out .= '</option>';
370 }
371 }
372
373 $out .= '</optgroup>';
374 $out .= '</select>';
375
376 return $out;
377 }
378
379 function escape_money($number)
380 {
381 return number_format((float)$number, 2, ',', ' ');
382 }
383
384 function tpl_html_money($number)
385 {
386 return '<b class="money">' . escape_money($number) . '</b>';
387 }
388
389 function tpl_html_champ_membre($params)
390 {
391 if (empty($params['config']) || empty($params['name']))
392 throw new \BadFunctionCallException('Paramètres type et name obligatoires.');
393
394 $config = $params['config'];
395 $type = $config['type'];
396
397 if ($params['name'] == 'passe' || (!empty($params['user_mode']) && !empty($config['private'])))
398 {
399 return '';
400 }
401
402 if ($type == 'select')
403 {
404 if (empty($config['options']))
405 throw new \BadFunctionCallException('Paramètre options obligatoire pour champ de type select.');
406 }
407 elseif ($type == 'country')
408 {
409 $type = 'select';
410 $config['options'] = utils::getCountryList();
411 $params['default'] = Config::getInstance()->get('pays');
412 }
413 elseif ($type == 'date')
414 {
415 $params['pattern'] = '\d{4}-\d{2}-\d{2}';
416 }
417 elseif ($type == 'multiple')
418 {
419 if (empty($config['options']))
420 throw new \BadFunctionCallException('Paramètre options obligatoire pour champ de type multiple.');
421 }
422
423 $field = '';
424 $value = tpl_form_field($params);
425 $attributes = 'name="' . htmlspecialchars($params['name'], ENT_QUOTES, 'UTF-8') . '" ';
426 $attributes .= 'id="f_' . htmlspecialchars($params['name'], ENT_QUOTES, 'UTF-8') . '" ';
427
428 if (!empty($params['disabled']))
429 {
430 $attributes .= 'disabled="disabled" ';
431 }
432
433 if (!empty($config['mandatory']))
434 {
435 $attributes .= 'required="required" ';
436 }
437
438 if (!empty($params['user_mode']) && empty($config['editable']))
439 {
440 $out = '<dt>' . htmlspecialchars($config['title'], ENT_QUOTES, 'UTF-8') . '</dt>';
441 $out .= '<dd>' . htmlspecialchars((trim($value) === '' ? 'Non renseigné' : $value), ENT_QUOTES, 'UTF-8') . '</dd>';
442 return $out;
443 }
444
445 if ($type == 'select')
446 {
447 $field .= '<select '.$attributes.'>';
448 foreach ($config['options'] as $k=>$v)
449 {
450 if (is_int($k))
451 $k = $v;
452
453 $field .= '<option value="' . htmlspecialchars($k, ENT_QUOTES, 'UTF-8') . '"';
454
455 if ($value == $k || empty($value) && !empty($params['default']))
456 $field .= ' selected="selected"';
457
458 $field .= '>' . htmlspecialchars($v, ENT_QUOTES, 'UTF-8') . '</option>';
459 }
460 $field .= '</select>';
461 }
462 elseif ($type == 'multiple')
463 {
464 if (is_array($value))
465 {
466 $binary = 0;
467
468 foreach ($value as $k => $v)
469 {
470 if (array_key_exists($k, $config['options']) && !empty($v))
471 {
472 $binary |= 0x01 << $k;
473 }
474 }
475
476 $value = $binary;
477 }
478
479 foreach ($config['options'] as $k=>$v)
480 {
481 $b = 0x01 << (int)$k;
482 $field .= '<label><input type="checkbox" name="'
483 . htmlspecialchars($params['name'], ENT_QUOTES, 'UTF-8') . '[' . (int)$k . ']" value="1" '
484 . (($value & $b) ? 'checked="checked"' : '') . ' ' . $attributes . '/> '
485 . htmlspecialchars($v, ENT_QUOTES, 'UTF-8') . '</label><br />';
486 }
487 }
488 elseif ($type == 'textarea')
489 {
490 $field .= '<textarea ' . $attributes . 'cols="30" rows="5">' . $value . '</textarea>';
491 }
492 else
493 {
494 if ($type == 'checkbox')
495 {
496 if (!empty($value))
497 {
498 $attributes .= 'checked="checked" ';
499 }
500
501 $value = '1';
502 }
503
504 $field .= '<input type="' . $type . '" ' . $attributes . ' value="' . $value . '" />';
505 }
506
507 $out = '
508 <dt>';
509
510 if ($type == 'checkbox')
511 {
512 $out .= $field . ' ';
513 }
514
515 $out .= '<label for="f_' . htmlspecialchars($params['name'], ENT_QUOTES, 'UTF-8') . '">'
516 . htmlspecialchars($config['title'], ENT_QUOTES, 'UTF-8') . '</label>';
517
518 if (!empty($config['mandatory']))
519 {
520 $out .= ' <b title="(Champ obligatoire)">obligatoire</b>';
521 }
522
523 $out .= '</dt>';
524
525 if (!empty($config['help']))
526 {
527 $out .= '
528 <dd class="help">' . htmlspecialchars($config['help'], ENT_QUOTES, 'UTF-8') . '</dd>';
529 }
530
531 if ($type != 'checkbox')
532 {
533 $out .= '
534 <dd>' . $field . '</dd>';
535 }
536
537 return $out;
538 }
539
540 $tpl->register_compiler('continue', function() { return 'continue;'; });
541
542 $tpl->register_function('csrf_field', 'Garradin\tpl_csrf_field');
543 $tpl->register_function('form_field', 'Garradin\tpl_form_field');
544 $tpl->register_function('select_compte', 'Garradin\tpl_select_compte');
545
546 $tpl->register_function('format_droits', 'Garradin\tpl_format_droits');
547
548 $tpl->register_function('pagination', 'Garradin\tpl_pagination');
549
550 $tpl->register_function('diff', 'Garradin\tpl_diff');
551 $tpl->register_function('html_champ_membre', 'Garradin\tpl_html_champ_membre');
552
553 $tpl->register_function('plugin_url', ['Garradin\utils', 'plugin_url']);
554
555 $tpl->register_modifier('get_country_name', ['Garradin\utils', 'getCountryName']);
556 $tpl->register_modifier('format_tel', 'Garradin\tpl_format_tel');
557 $tpl->register_modifier('format_wiki', 'Garradin\tpl_format_wiki');
558 $tpl->register_modifier('liens_wiki', 'Garradin\tpl_liens_wiki');
559 $tpl->register_modifier('escape_money', 'Garradin\escape_money');
560 $tpl->register_modifier('html_money', 'Garradin\tpl_html_money');
561 $tpl->register_modifier('abs', 'abs');
562
563 $tpl->register_modifier('display_champ_membre', function ($v, $config) {
564 if ($config['type'] == 'checkbox') {
565 return $v ? 'Oui' : 'Non';
566 } elseif ($config['type'] == 'email') {
567 return '<a href="mailto:' . $v . '">' . $v . '</a>';
568 } elseif ($config['type'] == 'tel') {
569 return '<a href="tel:' . $v . '">' . $v . '</a>';
570 } elseif ($config['type'] == 'url') {
571 return '<a href="' . $v . '">' . $v . '</a>';
572 } elseif ($config['type'] == 'country') {
573 return utils::getCountryName($v);
574 } elseif ($config['type'] == 'multiple') {
575 $out = [];
576
577 foreach ($config['options'] as $b => $name)
578 {
579 if ($v & (0x01 << $b))
580 $out[] = $name;
581 }
582
583 return implode(', ', $out);
584 } else {
585 return $v;
586 }
587
588 });
589
590 $tpl->register_modifier('format_sqlite_date_to_french', ['Garradin\utils', 'sqliteDateToFrench']);
591
592 $tpl->register_modifier('format_bytes', function ($size) {
593 if ($size > (1024 * 1024))
594 return round($size / 1024 / 1024, 2) . ' Mo';
595 elseif ($size > 1024)
596 return round($size / 1024, 2) . ' Ko';
597 else
598 return $size . ' ob_get_contents(oid)';
599 });
600
601 $tpl->register_modifier('strftime_fr', 'Garradin\tpl_strftime_fr');
602 $tpl->register_modifier('date_fr', 'Garradin\tpl_date_fr');
603
604 ?>