5 require_once ROOT
. '/include/libs/miniskel/class.miniskel.php';
7 class Squelette_Snippet
14 protected $_content = [];
16 protected function _getType($type, $value)
18 if ($type == self
::GUESS
)
20 if ($value instanceof Squelette_Snippet
)
29 public function __construct($type = self
::TEXT
, $value = '')
31 $type = $this->_getType($type, $value);
33 if ($type == self
::OBJ
)
35 $this->_content
= $value->get();
39 $this->_content
[] = (string) (int) $type . $value;
45 public function prepend($type = self
::TEXT
, $value, $pos = false)
47 $type = $this->_getType($type, $value);
49 if ($type == self
::OBJ
)
53 array_splice($this->_content
, $pos, 0, $value->get());
57 $this->_content
= array_merge($value->get(), $this->_content
);
62 $value = (string) (int) $type . $value;
66 array_splice($this->_content
, $pos, 0, $value);
70 array_unshift($this->_content
, $value);
77 public function append($type = self
::TEXT
, $value, $pos = false)
79 $type = $this->_getType($type, $value);
81 if ($type == self
::OBJ
)
85 array_splice($this->_content
, $pos +
1, 0, $value->get());
89 $this->_content
= array_merge($this->_content
, $value->get());
94 $value = (string) (int) $type . $value;
98 array_splice($this->_content
, $pos +
1, 0, $value);
102 array_push($this->_content
, $value);
109 public function output($in_php = false)
112 $php = $in_php ?
: false;
114 foreach ($this->_content
as $line)
116 if ($line[0] == self
::PHP
&& !$php)
121 elseif ($line[0] == self
::TEXT
&& $php)
127 $out .= substr($line, 1);
129 if ($line[0] == self
::PHP
)
135 if ($php && !$in_php)
140 $this->_content
= [];
145 public function __toString()
147 return $this->output(false);
150 public function get()
152 return $this->_content
;
155 public function replace($key, $type = self
::TEXT
, $value)
157 $type = $this->_getType($type, $value);
159 if ($type == self
::OBJ
)
161 array_splice($this->_content
, $key, 1, $value->get());
165 $this->_content
[$key] = (string) (int) $type . $value;
172 class Squelette
extends \miniSkel
174 private $parent = null;
175 private $current = null;
178 private function _registerDefaultModifiers()
180 foreach (Squelette_Filtres
::$filtres_php as $func=>$name)
182 if (is_string($func))
183 $this->register_modifier($name, $func);
185 $this->register_modifier($name, $name);
188 foreach (get_class_methods('Garradin\Squelette_Filtres') as $name)
190 $this->register_modifier($name, ['Garradin\Squelette_Filtres', $name]);
193 foreach (Squelette_Filtres
::$filtres_alias as $name=>$func)
195 $this->register_modifier($name, ['Garradin\Squelette_Filtres', $func]);
199 public function __construct()
201 $this->_registerDefaultModifiers();
203 $config = Config
::getInstance();
205 $this->assign('nom_asso', $config->get('nom_asso'));
206 $this->assign('adresse_asso', $config->get('adresse_asso'));
207 $this->assign('email_asso', $config->get('email_asso'));
208 $this->assign('site_asso', $config->get('site_asso'));
210 $this->assign('url_racine', WWW_URL
);
211 $this->assign('url_site', WWW_URL
);
212 $this->assign('url_atom', WWW_URL
. 'feed/atom/');
213 $this->assign('url_elements', WWW_URL
. 'squelettes/');
214 $this->assign('url_admin', WWW_URL
. 'admin/');
217 protected function processInclude($args)
220 throw new \
miniSkelMarkupException("Le tag INCLURE demande à préciser le fichier à inclure.");
224 if (empty($file) ||
!preg_match('!^[\w\d_-]+(?:\.[\w\d_-]+)*$!', $file))
225 throw new \
miniSkelMarkupException("INCLURE: le nom de fichier ne peut contenir que des caractères alphanumériques.");
227 return new Squelette_Snippet(1, '$this->fetch("'.$file.'", false);');
230 protected function processVariable($name, $value, $applyDefault, $modifiers, $pre, $post, $context)
232 if ($context == self
::CONTEXT_IN_ARG
)
234 $out = new Squelette_Snippet(1, '$this->getVariable(\''.$name.'\')');
238 $out->prepend(2, $pre);
243 $out->append(2, $post);
249 $out = new Squelette_Snippet(1, '$value = $this->getVariable(\''.$name.'\');');
251 // We process modifiers
252 foreach ($modifiers as &$modifier)
254 if (!isset($this->modifiers
[$modifier['name']]))
256 throw new \
miniSkelMarkupException('Filtre '.$modifier['name'].' inconnu !');
259 $out->append(1, '$value = call_user_func_array('.var_export($this->modifiers
[$modifier['name']], true).', [$value, ');
261 foreach ($modifier['arguments'] as $arg)
263 if ($arg == 'debut_liste')
265 $out->append(1, '$this->getVariable(\'debut_liste\')');
267 elseif ($arg instanceOf Squelette_Snippet
)
269 $out->append(3, $arg);
273 //if (preg_match('!getVariable!', $arg)) throw new Exception("lol");
274 $out->append(1, '"'.str_replace('"', '\\"', $arg).'"');
277 $out->append(1, ', ');
280 $out->append(1, ']);');
282 if (in_array($modifier['name'], Squelette_Filtres
::$desactiver_defaut))
284 $applyDefault = false;
290 $out->append(1, 'if (is_string($value) && trim($value)) $value = htmlspecialchars($value, ENT_QUOTES, \'UTF-8\', false);');
293 $out->append(1, 'if ($value === true || trim($value) !== \'\'):');
295 // Getting pre-content
298 $out->append(2, $pre);
301 $out->append(1, 'echo is_bool($value) ? "" : $value;');
303 // Getting post-content
306 $out->append(2, $post);
309 $out->append(1, 'endif;');
314 protected function processLoop($loopName, $loopType, $loopCriterias, $loopContent, $preContent, $postContent, $altContent)
316 if ($loopType != 'articles' && $loopType != 'rubriques' && $loopType != 'pages')
318 throw new \
miniSkelMarkupException("Le type de boucle '".$loopType."' est inconnu.");
322 $query = $where = $order = '';
325 $query = 'SELECT w.*, strftime(\\\'%s\\\', w.date_creation) AS date_creation, strftime(\\\'%s\\\', w.date_modification) AS date_modification';
327 if (trim($loopContent))
329 $query .= ', r.contenu AS texte FROM wiki_pages AS w LEFT JOIN wiki_revisions AS r ON (w.id = r.id_page AND w.revision = r.revision) ';
333 $query .= '\'\' AS texte ';
336 $where = 'WHERE w.droit_lecture = -1 ';
338 if ($loopType == 'articles')
340 $where .= 'AND (SELECT COUNT(id) FROM wiki_pages WHERE parent = w.id) = 0 ';
342 elseif ($loopType == 'rubriques')
344 $where .= 'AND (SELECT COUNT(id) FROM wiki_pages WHERE parent = w.id) > 0 ';
347 $allowed_fields = ['id', 'uri', 'titre', 'date', 'date_creation', 'date_modification',
348 'parent', 'rubrique', 'revision', 'points', 'recherche', 'texte'];
349 $search = $search_rank = false;
351 foreach ($loopCriterias as $criteria)
353 if (isset($criteria['field']))
355 if (!in_array($criteria['field'], $allowed_fields))
357 throw new \
miniSkelMarkupException("Critère '".$criteria['field']."' invalide pour la boucle '$loopName' de type '$loopType'.");
359 elseif ($criteria['field'] == 'rubrique')
361 $criteria['field'] = 'parent';
363 elseif ($criteria['field'] == 'date')
365 $criteria['field'] = 'date_creation';
367 elseif ($criteria['field'] == 'points')
369 if ($criteria['action'] != \miniSkel
::ACTION_ORDER_BY
)
371 throw new \
miniSkelMarkupException("Le critère 'points' n\'est pas valide dans ce contexte.");
378 switch ($criteria['action'])
380 case \miniSkel
::ACTION_ORDER_BY
:
382 $order = 'ORDER BY '.$criteria['field'].'';
384 $order .= ', '.$criteria['field'].'';
386 case \miniSkel
::ACTION_ORDER_DESC
:
390 case \miniSkel
::ACTION_LIMIT
:
391 $begin = $criteria['begin'];
392 $limit = $criteria['number'];
394 case \miniSkel
::ACTION_MATCH_FIELD_BY_VALUE
:
395 $where .= ' AND '.$criteria['field'].' '.$criteria['comparison'].' \\\'\'.$db->escapeString(\''.$criteria['value'].'\').\'\\\'';
397 case \miniSkel
::ACTION_MATCH_FIELD
:
399 if ($criteria['field'] == 'recherche')
401 $query = 'SELECT w.*, r.contenu AS texte, rank(matchinfo(wiki_recherche), 0, 1.0, 1.0) AS points FROM wiki_pages AS w INNER JOIN wiki_recherche AS r ON (w.id = r.id) ';
402 $where .= ' AND wiki_recherche MATCH \\\'\'.$db->escapeString($this->getVariable(\''.$criteria['field'].'\')).\'\\\'';
407 if ($criteria['field'] == 'parent')
410 $field = $criteria['field'];
412 $where .= ' AND '.$criteria['field'].' = \\\'\'.$db->escapeString($this->getVariable(\''.$field.'\')).\'\\\'';
421 if ($search_rank && !$search)
423 throw new \
miniSkelMarkupException("Le critère par points n'est possible que dans les boucles de recherche.");
426 if (trim($loopContent))
428 $loopStart .= '$row[\'url\'] = WWW_URL . $row[\'uri\']; ';
431 $query .= $where . ' ' . $order;
433 if (!$limit ||
$limit > 100)
438 $query .= ' LIMIT '.(is_numeric($begin) ?
(int) $begin : '\'.$this->variables[\'debut_liste\'].\'').','.(int)$limit;
441 $hash = sha1(uniqid(mt_rand(), true));
442 $out = new Squelette_Snippet();
443 $out->append(1, '$parent_hash = $this->current[\'_self_hash\'];');
444 $out->append(1, '$this->parent =& $parent_hash ? $this->_vars[$parent_hash] : null;');
448 $out->append(1, 'if (trim($this->getVariable(\'recherche\'))) { ');
451 $out->append(1, '$statement = $db->prepare(\''.$query.'\'); ');
452 // Sécurité anti injection
453 $out->append(1, 'if (!$statement->readOnly()) { throw new \\miniSkelMarkupException("Requête en écriture illégale: '.$query.'"); } ');
454 $out->append(1, '$result_'.$hash.' = $statement->execute(); ');
455 $out->append(1, '$nb_rows = $db->countRows($result_'.$hash.'); ');
459 $out->append(1, '} else { $result_'.$hash.' = false; $nb_rows = 0; }');
462 $out->append(1, '$this->_vars[\''.$hash.'\'] = [\'_self_hash\' => \''.$hash.'\', \'_parent_hash\' => $parent_hash, \'total_boucle\' => $nb_rows, \'compteur_boucle\' => 0];');
463 $out->append(1, '$this->current =& $this->_vars[\''.$hash.'\']; ');
464 $out->append(1, 'if ($nb_rows > 0):');
468 $out->append(2, $this->parse($preContent, $loopName, self
::PRE_CONTENT
));
471 $out->append(1, 'while ($row = $result_'.$hash.'->fetchArray(SQLITE3_ASSOC)): ');
472 $out->append(1, '$this->_vars[\''.$hash.'\'][\'compteur_boucle\'] += 1; ');
473 $out->append(1, $loopStart);
474 $out->append(1, '$this->_vars[\''.$hash.'\'] = array_merge($this->_vars[\''.$hash.'\'], $row); ');
476 $out->append(2, $this->parseVariables($loopContent));
478 $out->append(1, 'endwhile;');
480 // we put the post-content after the loop content
483 $out->append(2, $this->parse($postContent, $loopName, self
::POST_CONTENT
));
488 $out->append(1, 'else:');
489 $out->append(2, $this->parse($altContent, $loopName, self
::ALT_CONTENT
));
492 $out->append(1, 'endif; ');
493 $out->append(1, '$parent_hash = $this->_vars[\''.$hash.'\'][\'_parent_hash\']; ');
494 $out->append(1, 'unset($result_'.$hash.', $nb_rows, $this->_vars[\''.$hash.'\']); ');
495 $out->append(1, 'if ($parent_hash) { $this->current =& $this->_vars[$parent_hash]; $parent_hash = $this->current[\'_parent_hash\']; } ');
496 $out->append(1, 'else { $this->current = null; }');
497 $out->append(1, '$this->parent =& $parent_hash ? $this->_vars[$_parent_hash] : null;');
502 public function fetch($template, $no_display = false)
504 $this->currentTemplate
= $template;
506 $path = file_exists(DATA_ROOT
. '/www/squelettes/' . $template)
507 ? DATA_ROOT
. '/www/squelettes/' . $template
508 : ROOT
. '/www/squelettes-dist/' . $template;
510 $tpl_id = basename(dirname($path)) . '/' . $template;
512 if (!self
::compile_check($tpl_id, $path))
514 if (!file_exists($path))
516 throw new \
miniSkelMarkupException('Le squelette "'.$tpl_id.'" n\'existe pas.');
519 $content = file_get_contents($path);
520 $content = strtr($content, ['<?php' => '<?php', '<?' => '<?php echo \'<?\'; ?>']);
522 $out = new Squelette_Snippet(2, $this->parse($content));
523 $out->prepend(1, '/* '.$tpl_id.' */ '.
524 'namespace Garradin; $db = DB::getInstance(); '.
525 'if ($this->parent) $parent_hash = $this->parent[\'_self_hash\']; '. // For included files
526 'else $parent_hash = false;');
530 self
::compile_store($tpl_id, $out);
536 require self
::compile_get_path($tpl_id);
546 public function dispatchURI()
548 $uri = !empty($_SERVER['REQUEST_URI']) ?
$_SERVER['REQUEST_URI'] : '/';
550 header('HTTP/1.1 200 OK', 200, true);
552 if ($pos = strpos($uri, '?'))
554 $uri = substr($uri, 0, $pos);
558 // WWW_URI inclus toujours le slash final, mais on veut le conserver ici
559 $uri = substr($uri, strlen(WWW_URI
) - 1);
564 $skel = 'sommaire.html';
566 elseif ($uri == '/feed/atom/')
568 header('Content-Type: application/atom+xml');
571 elseif (substr($uri, -1) == '/')
573 $skel = 'rubrique.html';
574 $_GET['uri'] = $_REQUEST['uri'] = substr($uri, 1, -1);
576 elseif (preg_match('!^/admin/!', $uri))
578 throw new UserException('Cette page n\'existe pas.');
582 $_GET['uri'] = $_REQUEST['uri'] = substr($uri, 1);
584 if (preg_match('!^[\w\d_-]+$!i', $_GET['uri'])
585 && file_exists(DATA_ROOT
. '/www/squelettes/' . strtolower($_GET['uri']) . '.html'))
587 $skel = strtolower($_GET['uri']) . '.html';
591 $skel = 'article.html';
595 $this->display($skel);
598 static private function compile_get_path($path)
601 return DATA_ROOT
. '/cache/compiled/s_' . $hash . '.php';
604 static private function compile_check($tpl, $check)
606 if (!file_exists(self
::compile_get_path($tpl)))
609 $time = filemtime(self
::compile_get_path($tpl));
616 if ($time < filemtime($check))
621 static private function compile_store($tpl, $content)
623 $path = self
::compile_get_path($tpl);
625 if (!file_exists(dirname($path)))
627 mkdir(dirname($path));
630 file_put_contents($path, $content);
634 static public function compile_clear($tpl)
636 $path = self
::compile_get_path($tpl);
638 if (file_exists($path))
644 protected function getVariable($var)
646 if (isset($this->current
[$var]))
648 return $this->current
[$var];
650 elseif (isset($this->parent
[$var]))
652 return $this->parent
[$var];
654 elseif (isset($this->variables
[$var]))
656 return $this->variables
[$var];
658 elseif (isset($_REQUEST[$var]))
660 return $_REQUEST[$var];
668 static public function getSource($template)
670 if (!preg_match('!^[\w\d_-]+(?:\.[\w\d_-]+)*$!', $template))
673 $path = file_exists(DATA_ROOT
. '/www/squelettes/' . $template)
674 ? DATA_ROOT
. '/www/squelettes/' . $template
675 : ROOT
. '/www/squelettes-dist/' . $template;
677 if (!file_exists($path))
680 return file_get_contents($path);
683 static public function editSource($template, $content)
685 if (!preg_match('!^[\w\d_-]+(?:\.[\w\d_-]+)*$!', $template))
688 $path = DATA_ROOT
. '/www/squelettes/' . $template;
690 return file_put_contents($path, $content);
693 static public function resetSource($template)
695 if (!preg_match('!^[\w\d_-]+(?:\.[\w\d_-]+)*$!', $template))
698 if (file_exists(DATA_ROOT
. '/www/squelettes/' . $template))
700 unlink(DATA_ROOT
. '/www/squelettes/' . $template);
706 static public function listSources()
708 if (!file_exists(DATA_ROOT
. '/www/squelettes'))
710 mkdir(DATA_ROOT
. '/www/squelettes');
715 $dir = dir(ROOT
. '/www/squelettes-dist');
717 while ($file = $dir->read())
722 if (!preg_match('/\.(?:css|x?html?|atom|rss|xml|svg|txt)$/i', $file))
730 $dir = dir(DATA_ROOT
. '/www/squelettes');
732 while ($file = $dir->read())
737 if (!preg_match('/\.(?:css|x?html?|atom|rss|xml|svg|txt)$/i', $file))
745 $sources = array_unique($sources);