8 protected $plugin = null;
14 'html' => 'text/html',
15 'ico' => 'image/x-ico',
16 'jpe' => 'image/jpeg',
17 'jpg' => 'image/jpeg',
18 'jpeg' => 'image/jpeg',
19 'js' => 'application/x-javascript',
20 'pdf' => 'application/pdf',
22 'swf' => 'application/shockwave-flash',
24 'svg' => 'image/svg+xml',
28 * Construire un objet Plugin pour un plugin
29 * @param string $id Identifiant du plugin
30 * @throws UserException Si le plugin n'est pas installé (n'existe pas en DB)
32 public function __construct($id)
34 $db = DB
::getInstance();
35 $this->plugin
= $db->simpleQuerySingle('SELECT * FROM plugins WHERE id = ?;', true, $id);
39 throw new UserException('Ce plugin n\'existe pas ou n\'est pas installé correctement.');
42 $this->plugin
['config'] = json_decode($this->plugin
['config'], true);
44 if (!is_array($this->plugin
['config']))
46 $this->plugin
['config'] = [];
53 * Renvoie le chemin absolu vers l'archive du plugin
54 * @return string Chemin PHAR vers l'archive
56 public function path()
58 return 'phar://' . PLUGINS_ROOT
. '/' . $this->id
. '.tar.gz';
62 * Renvoie une entrée de la configuration ou la configuration complète
63 * @param string $key Clé à rechercher, ou NULL si on désire toutes les entrées de la
64 * @return mixed L'entrée demandée (mixed), ou l'intégralité de la config (array),
65 * ou NULL si l'entrée demandée n'existe pas.
67 public function getConfig($key = null)
71 return $this->plugin
['config'];
74 if (array_key_exists($key, $this->plugin
['config']))
76 return $this->plugin
['config'][$key];
83 * Enregistre une entrée dans la configuration du plugin
84 * @param string $key Clé à modifier
85 * @param mixed $value Valeur à enregistrer, choisir NULL pour effacer cette clé de la configuration
86 * @return boolean TRUE si tout se passe bien
88 public function setConfig($key, $value = null)
92 unset($this->plugin
['config'][$key]);
96 $this->plugin
['config'][$key] = $value;
99 $db = DB
::getInstance();
100 $db->simpleUpdate('plugins',
101 ['config' => json_encode($this->plugin
['config'])],
102 'id = \'' . $this->id
. '\'');
108 * Renvoie une information ou toutes les informations sur le plugin
109 * @param string $key Clé de l'info à retourner, ou NULL pour recevoir toutes les infos
110 * @return mixed Info demandée ou tableau des infos.
112 public function getInfos($key = null)
116 return $this->plugin
;
119 if (array_key_exists($key, $this->plugin
))
121 return $this->plugin
[$key];
128 * Renvoie l'identifiant du plugin
129 * @return string Identifiant du plugin
137 * Inclure un fichier depuis le plugin (dynamique ou statique)
138 * @param string $file Chemin du fichier à aller chercher : si c'est un .php il sera inclus,
139 * sinon il sera juste affiché
141 * @throws UserException Si le fichier n'existe pas ou fait partie des fichiers qui ne peuvent
142 * être appelés que par des méthodes de Plugin.
143 * @throws RuntimeException Si le chemin indiqué tente de sortir du contexte du PHAR
145 public function call($file)
147 $file = preg_replace('!^[./]*!', '', $file);
149 if (preg_match('!(?:\.\.|[/\\\\]\.|\.[/\\\\])!', $file))
151 throw new \
RuntimeException('Chemin de fichier incorrect.');
154 $forbidden = ['install.php', 'garradin_plugin.ini', 'upgrade.php', 'uninstall.php', 'signals.php'];
156 if (in_array($file, $forbidden))
158 throw new UserException('Le fichier ' . $file . ' ne peut être appelé par cette méthode.');
161 if (!file_exists($this->path() . '/www/' . $file))
163 throw new UserException('Le fichier ' . $file . ' n\'existe pas dans le plugin ' . $this->id
);
167 global $tpl, $config, $user, $membres;
169 if (substr($file, -4) === '.php')
171 include $this->path() . '/www/' . $file;
175 // Récupération du type MIME à partir de l'extension
176 $ext = substr($file, strrpos($file, '.')+
1);
178 if (isset($this->mimes
[$ext]))
180 $mime = $this->mimes
[$ext];
184 $mime = 'text/plain';
187 header('Content-Type: ' .$this->mimes
[$ext]);
188 header('Content-Length: ' . filesize($this->path() . '/www/' . $file));
190 readfile($this->path() . '/www/' . $file);
195 * Désinstaller le plugin
196 * @return boolean TRUE si la suppression a fonctionné
198 public function uninstall()
200 if (file_exists($this->path() . '/uninstall.php'))
202 include $this->path() . '/uninstall.php';
205 unlink(PLUGINS_ROOT
. '/' . $this->id
. '.tar.gz');
207 $db = DB
::getInstance();
208 return $db->simpleExec('DELETE FROM plugins WHERE id = ?;', $this->id
);
212 * Renvoie TRUE si le plugin a besoin d'être mis à jour
213 * (si la version notée dans la DB est différente de la version notée dans garradin_plugin.ini)
214 * @return boolean TRUE si le plugin doit être mis à jour, FALSE sinon
216 public function needUpgrade()
218 $infos = parse_ini_file($this->path() . '/garradin_plugin.ini', false);
220 if (version_compare($this->plugin
['version'], $infos['version'], '!='))
227 * Mettre à jour le plugin
228 * Appelle le fichier upgrade.php dans l'archive si celui-ci existe.
229 * @return boolean TRUE si tout a fonctionné
231 public function upgrade()
233 if (file_exists($this->path() . '/upgrade.php'))
235 include $this->path() . '/upgrade.php';
238 $db = DB
::getInstance();
239 return $db->simpleUpdate('plugins',
240 'id = \''.$db->escapeString($this->id
).'\'',
241 ['version' => $infos['version']]);
245 * Liste des plugins installés (en DB)
246 * @return array Liste des plugins triés par nom
248 static public function listInstalled()
250 $db = DB
::getInstance();
251 $plugins = $db->simpleStatementFetchAssocKey('SELECT id, * FROM plugins ORDER BY nom;');
252 $system = explode(',', PLUGINS_SYSTEM
);
254 foreach ($plugins as &$row)
256 $row['system'] = in_array($row['id'], $system);
263 * Liste les plugins qui doivent être affichés dans le menu
264 * @return array Tableau associatif id => nom (ou un tableau vide si aucun plugin ne doit être affiché)
266 static public function listMenu()
268 $db = DB
::getInstance();
269 return $db->simpleStatementFetchAssoc('SELECT id, nom FROM plugins WHERE menu = 1 ORDER BY nom;');
273 * Liste les plugins téléchargés mais non installés
274 * @return array Liste des plugins téléchargés
276 static public function listDownloaded()
278 $installed = self
::listInstalled();
281 $dir = dir(PLUGINS_ROOT
);
283 while ($file = $dir->read())
285 if (substr($file, 0, 1) == '.')
288 if (!preg_match('!^([a-z0-9_.-]+)\.tar\.gz$!', $file, $match))
291 if (array_key_exists($match[1], $installed))
294 $list[$match[1]] = parse_ini_file('phar://' . PLUGINS_ROOT
. '/' . $match[1] . '.tar.gz/garradin_plugin.ini', false);
303 * Liste des plugins officiels depuis le repository signé
304 * @return array Liste des plugins
306 static public function listOfficial()
308 // La liste est stockée en cache une heure pour ne pas tuer le serveur distant
309 if (Static_Cache
::expired('plugins_list', 3600 * 24))
311 $url = parse_url(PLUGINS_URL
);
315 'verify_peer' => TRUE,
316 // On vérifie en utilisant le certificat maître de CACert
317 'cafile' => ROOT
. '/include/data/cacert.pem',
319 'CN_match' => $url['host'],
320 'SNI_enabled' => true,
321 'SNI_server_name' => $url['host'],
322 'disable_compression' => true,
326 $context = stream_context_create($context_options);
329 $result = file_get_contents(PLUGINS_URL
, NULL, $context);
331 catch (\Exception
$e)
333 throw new UserException('Le téléchargement de la liste des plugins a échoué : ' . $e->getMessage());
336 Static_Cache
::store('plugins_list', $result);
340 $result = Static_Cache
::get('plugins_list');
343 $list = json_decode($result, true);
348 * Vérifier le hash du plugin $id pour voir s'il correspond au hash du fichier téléchargés
349 * @param string $id Identifiant du plugin
350 * @return boolean TRUE si le hash correspond (intégrité OK), sinon FALSE
352 static public function checkHash($id)
354 $list = self
::fetchOfficialList();
356 if (!array_key_exists($id, $list))
359 $hash = sha1_file(PLUGINS_ROOT
. '/' . $id . '.tar.gz');
361 return ($hash === $list[$id]['hash']);
365 * Est-ce que le plugin est officiel ?
366 * @param string $id Identifiant du plugin
367 * @return boolean TRUE si le plugin est officiel, FALSE sinon
369 static public function isOfficial($id)
371 $list = self
::fetchOfficialList();
372 return array_key_exists($id, $list);
376 * Télécharge un plugin depuis le repository officiel, et l'installe
377 * @param string $id Identifiant du plugin
378 * @return boolean TRUE si ça marche
379 * @throws LogicException Si le plugin n'est pas dans la liste des plugins officiels
380 * @throws UserException Si le plugin est déjà installé ou que le téléchargement a échoué
381 * @throws RuntimeException Si l'archive téléchargée est corrompue (intégrité du hash ne correspond pas)
383 static public function download($id)
385 $list = self
::fetchOfficialList();
387 if (!array_key_exists($id, $list))
389 throw new \
LogicException($id . ' n\'est pas un plugin officiel (absent de la liste)');
392 if (file_exists(PLUGINS_ROOT
. '/' . $id . '.tar.gz'))
394 throw new UserException('Le plugin '.$id.' existe déjà.');
397 $url = parse_url(PLUGINS_URL
);
401 'verify_peer' => TRUE,
402 'cafile' => ROOT
. '/include/data/cacert.pem',
404 'CN_match' => $url['host'],
405 'SNI_enabled' => true,
406 'SNI_server_name' => $url['host'],
407 'disable_compression' => true,
411 $context = stream_context_create($context_options);
414 copy($list[$id]['phar'], PLUGINS_ROOT
. '/' . $id . '.tar.gz', $context);
416 catch (\Exception
$e)
418 throw new UserException('Le téléchargement du plugin '.$id.' a échoué : ' . $e->getMessage());
421 if (!self
::checkHash($id))
423 unlink(PLUGINS_ROOT
. '/' . $id . '.tar.gz');
424 throw new \
RuntimeException('L\'archive du plugin '.$id.' est corrompue (le hash SHA1 ne correspond pas).');
427 self
::install($id, true);
433 * Installer un plugin
434 * @param string $id Identifiant du plugin
435 * @param boolean $official TRUE si le plugin est officiel
436 * @return boolean TRUE si tout a fonctionné
438 static public function install($id, $official = false)
440 if (!file_exists('phar://' . PLUGINS_ROOT
. '/' . $id . '.tar.gz'))
442 throw new \
RuntimeException('Le plugin ' . $id . ' ne semble pas exister et ne peut donc être installé.');
445 if (!file_exists('phar://' . PLUGINS_ROOT
. '/' . $id . '.tar.gz/garradin_plugin.ini'))
447 throw new UserException('L\'archive '.$id.'.tar.gz n\'est pas une extension Garradin : fichier garradin_plugin.ini manquant.');
450 $infos = parse_ini_file('phar://' . PLUGINS_ROOT
. '/' . $id . '.tar.gz/garradin_plugin.ini', false);
452 $required = ['nom', 'description', 'auteur', 'url', 'version', 'menu', 'config'];
454 foreach ($required as $key)
456 if (!array_key_exists($key, $infos))
458 throw new \
RuntimeException('Le fichier garradin_plugin.ini ne contient pas d\'entrée "'.$key.'".');
462 if (!empty($infos['menu']) && !file_exists('phar://' . PLUGINS_ROOT
. '/' . $id . '.tar.gz/www/admin/index.php'))
464 throw new \
RuntimeException('Le plugin '.$id.' ne comporte pas de fichier www/admin/index.php alors qu\'il demande à figurer au menu.');
469 if ((bool)$infos['config'])
471 if (!file_exists('phar://' . PLUGINS_ROOT
. '/' . $id . '.tar.gz/config.json'))
473 throw new \
RuntimeException('L\'archive '.$id.'.tar.gz ne comporte pas de fichier config.json
474 alors que le plugin nécessite le stockage d\'une configuration.');
477 if (!file_exists('phar://' . PLUGINS_ROOT
. '/' . $id . '.tar.gz/www/admin/config.php'))
479 throw new \
RuntimeException('L\'archive '.$id.'.tar.gz ne comporte pas de fichier www/admin/config.php
480 alors que le plugin nécessite le stockage d\'une configuration.');
483 $config = json_decode(file_get_contents('phar://' . PLUGINS_ROOT
. '/' . $id . '.tar.gz/config.json'), true);
485 if (is_null($config))
487 throw new \
RuntimeException('config.json invalide. Code erreur JSON: ' . json_last_error());
490 $config = json_encode($config);
493 $db = DB
::getInstance();
494 $db->simpleInsert('plugins', [
496 'officiel' => (int)(bool)$official,
497 'nom' => $infos['nom'],
498 'description'=> $infos['description'],
499 'auteur' => $infos['auteur'],
500 'url' => $infos['url'],
501 'version' => $infos['version'],
502 'menu' => (int)(bool)$infos['menu'],
506 if (file_exists('phar://' . PLUGINS_ROOT
. '/' . $id . '.tar.gz/install.php'))
508 include 'phar://' . PLUGINS_ROOT
. '/' . $id . '.tar.gz/install.php';
515 * Renvoie la version installée d'un plugin ou FALSE s'il n'est pas installé
516 * @param string $id Identifiant du plugin
517 * @return mixed Numéro de version du plugin ou FALSE
519 static public function getInstalledVersion($id)
521 return DB
::getInstance()->simpleQuerySingle('SELECT version FROM plugins WHERE id = ?;');