Overview
Comment:Documentation et lecture simple des fichiers non-PHP plutôt qu'inclusion
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 9577053997ca390083fee3c1e079a190f3b95653
User & Date: bohwaz on 2014-03-20 20:06:19
Other Links: manifest | tags
Context
2014-03-21
03:43
Mise en avant des numéros dans les listes check-in: 84fea36049 user: bohwaz tags: trunk
2014-03-20
20:06
Documentation et lecture simple des fichiers non-PHP plutôt qu'inclusion check-in: 9577053997 user: bohwaz tags: trunk
19:39
Suppression de plugin + vérifications check-in: 1cb7db96ca user: bohwaz tags: trunk
Changes

Modified src/include/class.plugin.php from [f5203581aa] to [d045d686cc].

     3      3   namespace Garradin;
     4      4   
     5      5   class Plugin
     6      6   {
     7      7   	protected $id = null;
     8      8   	protected $plugin = null;
     9      9   
           10  +	protected $mimes = [
           11  +		'css' => 'text/css',
           12  +		'gif' => 'image/gif',
           13  +		'htm' => 'text/html',
           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',
           21  +		'png' => 'image/png',
           22  +		'swf' => 'application/shockwave-flash',
           23  +		'xml' => 'text/xml',
           24  +		'svg' => 'image/svg+xml',
           25  +	];
           26  +
           27  +	/**
           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)
           31  +	 */
    10     32   	public function __construct($id)
    11     33   	{
    12     34   		$db = DB::getInstance();
    13     35   		$this->plugin = $db->simpleQuerySingle('SELECT * FROM plugins WHERE id = ?;', true, $id);
    14     36   
    15     37   		if (!$this->plugin)
    16     38   		{
................................................................................
    23     45   		{
    24     46   			$this->plugin['config'] = [];
    25     47   		}
    26     48   
    27     49   		$this->id = $id;
    28     50   	}
    29     51   
           52  +	/**
           53  +	 * Renvoie une entrée de la configuration ou la configuration complète
           54  +	 * @param  string $key Clé à rechercher, ou NULL si on désire toutes les entrées de la
           55  +	 * @return mixed       L'entrée demandée (mixed), ou l'intégralité de la config (array),
           56  +	 * ou NULL si l'entrée demandée n'existe pas.
           57  +	 */
    30     58   	public function getConfig($key = null)
    31     59   	{
    32     60   		if (is_null($key))
    33     61   		{
    34     62   			return $this->plugin['config'];
    35     63   		}
    36     64   
................................................................................
    38     66   		{
    39     67   			return $this->plugin['config'][$key];
    40     68   		}
    41     69   
    42     70   		return null;
    43     71   	}
    44     72   
           73  +	/**
           74  +	 * Enregistre une entrée dans la configuration du plugin
           75  +	 * @param string $key   Clé à modifier
           76  +	 * @param mixed  $value Valeur à enregistrer, choisir NULL pour effacer cette clé de la configuration
           77  +	 * @return boolean 		TRUE si tout se passe bien
           78  +	 */
    45     79   	public function setConfig($key, $value = null)
    46     80   	{
    47     81   		if (is_null($value))
    48     82   		{
    49     83   			unset($this->plugin['config'][$key]);
    50     84   		}
    51     85   		else
................................................................................
    57     91   		$db->simpleUpdate('plugins', 
    58     92   			['config' => json_encode($this->plugin['config'])],
    59     93   			'id = \'' . $this->id . '\'');
    60     94   
    61     95   		return true;
    62     96   	}
    63     97   
           98  +	/**
           99  +	 * Renvoie une information ou toutes les informations sur le plugin
          100  +	 * @param  string $key Clé de l'info à retourner, ou NULL pour recevoir toutes les infos
          101  +	 * @return mixed       Info demandée ou tableau des infos.
          102  +	 */
    64    103   	public function getInfos($key = null)
    65    104   	{
    66    105   		if (is_null($key))
    67    106   		{
    68    107   			return $this->plugin;
    69    108   		}
    70    109   
................................................................................
    72    111   		{
    73    112   			return $this->plugin[$key];
    74    113   		}
    75    114   
    76    115   		return null;
    77    116   	}
    78    117   
          118  +	/**
          119  +	 * Renvoie l'identifiant du plugin
          120  +	 * @return string Identifiant du plugin
          121  +	 */
    79    122   	public function id()
    80    123   	{
    81    124   		return $this->id;
    82    125   	}
    83    126   
          127  +	/**
          128  +	 * Inclure un fichier depuis le plugin (dynamique ou statique)
          129  +	 * @param  string $file Chemin du fichier à aller chercher : si c'est un .php il sera inclus,
          130  +	 * sinon il sera juste affiché
          131  +	 * @return void
          132  +	 * @throws UserException Si le fichier n'existe pas ou fait partie des fichiers qui ne peuvent
          133  +	 * être appelés que par des méthodes de Plugin.
          134  +	 * @throws RuntimeException Si le chemin indiqué tente de sortir du contexte du PHAR
          135  +	 */
    84    136   	public function call($file)
    85    137   	{
          138  +		$file = preg_replace('!^[./]*!', '', $file);
          139  +
          140  +		if (preg_match('!(?:\.\.|[/\\]\.|\.[/\\])!', $file))
          141  +		{
          142  +			throw new \RuntimeException('Chemin de fichier incorrect.');
          143  +		}
          144  +
    86    145   		$forbidden = ['install.php', 'garradin_plugin.ini', 'upgrade.php', 'uninstall.php', 'signals.php'];
    87    146   
    88    147   		if (in_array($file, $forbidden))
    89    148   		{
    90    149   			throw new UserException('Le fichier ' . $file . ' ne peut être appelé par cette méthode.');
    91    150   		}
    92    151   
................................................................................
    94    153   		{
    95    154   			throw new UserException('Le fichier ' . $file . ' n\'existe pas dans le plugin ' . $this->id);
    96    155   		}
    97    156   
    98    157   		$plugin = $this;
    99    158   		global $tpl, $config, $user, $membres;
   100    159   
   101         -		include 'phar://' . PLUGINS_PATH . '/' . $this->id . '.phar/' . $file;
          160  +		if (substr($file, -4) === '.php')
          161  +		{
          162  +			include 'phar://' . PLUGINS_PATH . '/' . $this->id . '.phar/' . $file;
          163  +		}
          164  +		else
          165  +		{
          166  +			// Récupération du type MIME à partir de l'extension
          167  +			$ext = substr($file, strrpos($file, '.')+1);
          168  +
          169  +			if (isset($this->mimes[$ext]))
          170  +			{
          171  +				$mime = $this->mimes[$ext];
          172  +			}
          173  +			else
          174  +			{
          175  +				$mime = 'text/plain';
          176  +			}
          177  +
          178  +			header('Content-Type: ' .$this->mimes[$ext]);
          179  +			header('Content-Length: ' . filesize('phar://' . PLUGINS_PATH . '/' . $this->id . '.phar/' . $file));
          180  +
          181  +			readfile('phar://' . PLUGINS_PATH . '/' . $this->id . '.phar/' . $file);
          182  +		}
   102    183   	}
   103    184   
          185  +	/**
          186  +	 * Désinstaller le plugin
          187  +	 * @return boolean TRUE si la suppression a fonctionné
          188  +	 */
   104    189   	public function uninstall()
   105    190   	{
   106    191   		if (file_exists('phar://' . PLUGINS_PATH . '/' . $this->id . '.phar/uninstall.php'))
   107    192   		{
   108    193   			include 'phar://' . PLUGINS_PATH . '/' . $this->id . '.phar/uninstall.php';
   109    194   		}
   110    195   		
   111    196   		unlink(PLUGINS_PATH . '/' . $this->id . '.phar');
   112    197   
   113    198   		$db = DB::getInstance();
   114    199   		return $db->simpleExec('DELETE FROM plugins WHERE id = ?;', $this->id);
   115    200   	}
   116    201   
          202  +	/**
          203  +	 * Renvoie TRUE si le plugin a besoin d'être mis à jour
          204  +	 * (si la version notée dans la DB est différente de la version notée dans garradin_plugin.ini)
          205  +	 * @return boolean TRUE si le plugin doit être mis à jour, FALSE sinon
          206  +	 */
   117    207   	public function needUpgrade()
   118    208   	{
   119    209   		$infos = parse_ini_file('phar://' . PLUGINS_PATH . '/' . $this->id . '.phar/garradin_plugin.ini', false);
   120    210   		
   121    211   		if (version_compare($this->plugin['version'], $infos['version'], '!='))
   122    212   			return true;
   123    213   
   124    214   		return false;
   125    215   	}
   126    216   
          217  +	/**
          218  +	 * Mettre à jour le plugin
          219  +	 * Appelle le fichier upgrade.php dans l'archive si celui-ci existe.
          220  +	 * @return boolean TRUE si tout a fonctionné
          221  +	 */
   127    222   	public function upgrade()
   128    223   	{
   129    224   		if (file_exists('phar://' . PLUGINS_PATH . '/' . $this->id . '.phar/upgrade.php'))
   130    225   		{
   131    226   			include 'phar://' . PLUGINS_PATH . '/' . $this->id . '.phar/upgrade.php';
   132    227   		}
   133    228   
   134    229   		$db = DB::getInstance();
   135    230   		return $db->simpleUpdate('plugins', 
   136    231   			'id = \''.$db->escapeString($this->id).'\'', 
   137    232   			['version' => $infos['version']]);
   138    233   	}
   139    234   
          235  +	/**
          236  +	 * Liste des plugins installés (en DB)
          237  +	 * @return array Liste des plugins triés par nom
          238  +	 */
   140    239   	static public function listInstalled()
   141    240   	{
   142    241   		$db = DB::getInstance();
   143    242   		return $db->simpleStatementFetchAssocKey('SELECT id, * FROM plugins ORDER BY nom;');
   144    243   	}
   145    244   
          245  +	/**
          246  +	 * Liste les plugins qui doivent être affichés dans le menu
          247  +	 * @return array Tableau associatif id => nom (ou un tableau vide si aucun plugin ne doit être affiché)
          248  +	 */
   146    249   	static public function listMenu()
   147    250   	{
   148    251   		$db = DB::getInstance();
   149    252   		return $db->simpleStatementFetchAssoc('SELECT id, nom FROM plugins WHERE menu = 1 ORDER BY nom;');
   150    253   	}
   151    254   
          255  +	/**
          256  +	 * Liste les plugins téléchargés mais non installés
          257  +	 * @return array Liste des plugins téléchargés
          258  +	 */
   152    259   	static public function listDownloaded()
   153    260   	{
   154    261   		$installed = self::listInstalled();
   155    262   
   156    263   		$list = [];
   157    264   		$dir = dir(PLUGINS_PATH);
   158    265   
................................................................................
   171    278   		}
   172    279   
   173    280   		$dir->close();
   174    281   
   175    282   		return $list;
   176    283   	}
   177    284   
          285  +	/**
          286  +	 * Liste des plugins officiels depuis le repository signé
          287  +	 * @return array Liste des plugins
          288  +	 */
   178    289   	static public function listOfficial()
   179    290   	{
          291  +		// La liste est stockée en cache une heure pour ne pas tuer le serveur distant
   180    292   		if (Static_Cache::expired('plugins_list', 3600 * 24))
   181    293   		{
   182    294   			$url = parse_url(PLUGINS_URL);
   183    295   
   184    296   			$context_options = [
   185    297   				'ssl' => [
   186    298   					'verify_peer'   => TRUE,
          299  +					// On vérifie en utilisant le certificat maître de CACert
   187    300   					'cafile'        => ROOT . '/include/data/cacert.pem',
   188    301   					'verify_depth'  => 5,
   189    302   					'CN_match'      => $url['host'],
   190    303   				]
   191    304   			];
   192    305   
   193    306   			$context = stream_context_create($context_options);
................................................................................
   207    320   			$result = Static_Cache::get('plugins_list');
   208    321   		}
   209    322   
   210    323   		$list = json_decode($result, true);
   211    324   		return $list;
   212    325   	}
   213    326   
          327  +	/**
          328  +	 * Vérifier le hash du plugin $id pour voir s'il correspond au hash du fichier téléchargés
          329  +	 * @param  string $id Identifiant du plugin
          330  +	 * @return boolean    TRUE si le hash correspond (intégrité OK), sinon FALSE
          331  +	 */
   214    332   	static public function checkHash($id)
   215    333   	{
   216    334   		$list = self::fetchOfficialList();
   217    335   
   218    336   		if (!array_key_exists($id, $list))
   219    337   			return null;
   220    338   
   221    339   		$hash = sha1_file(PLUGINS_PATH . '/' . $id . '.phar');
   222    340   
   223    341   		return ($hash === $list[$id]['hash']);
   224    342   	}
   225    343   
          344  +	/**
          345  +	 * Est-ce que le plugin est officiel ?
          346  +	 * @param  string  $id Identifiant du plugin
          347  +	 * @return boolean     TRUE si le plugin est officiel, FALSE sinon
          348  +	 */
   226    349   	static public function isOfficial($id)
   227    350   	{
   228    351   		$list = self::fetchOfficialList();
   229    352   		return array_key_exists($id, $list);
   230    353   	}
   231    354   
          355  +	/**
          356  +	 * Télécharge un plugin depuis le repository officiel, et l'installe
          357  +	 * @param  string $id Identifiant du plugin
          358  +	 * @return boolean    TRUE si ça marche
          359  +	 * @throws LogicException Si le plugin n'est pas dans la liste des plugins officiels
          360  +	 * @throws UserException Si le plugin est déjà installé ou que le téléchargement a échoué
          361  +	 * @throws RuntimeException Si l'archive téléchargée est corrompue (intégrité du hash ne correspond pas)
          362  +	 */
   232    363   	static public function download($id)
   233    364   	{
   234    365   		$list = self::fetchOfficialList();
   235    366   
   236    367   		if (!array_key_exists($id, $list))
   237    368   		{
   238    369   			throw new \LogicException($id . ' n\'est pas un plugin officiel (absent de la liste)');
................................................................................
   271    402   		}
   272    403   
   273    404   		self::install($id, true);
   274    405   
   275    406   		return true;
   276    407   	}
   277    408   
   278         -	static public function install($id, $official = true)
          409  +	/**
          410  +	 * Installer un plugin
          411  +	 * @param  string  $id       Identifiant du plugin
          412  +	 * @param  boolean $official TRUE si le plugin est officiel
          413  +	 * @return boolean           TRUE si tout a fonctionné
          414  +	 */
          415  +	static public function install($id, $official = false)
   279    416   	{
   280    417   		if (!file_exists('phar://' . PLUGINS_PATH . '/' . $id . '.phar'))
   281    418   		{
   282    419   			throw new \RuntimeException('Le plugin ' . $id . ' ne semble pas exister et ne peut donc être installé.');
   283    420   		}
   284    421   
   285         -		if ($official && !self::checkHash($id))
   286         -		{
   287         -			throw new \RuntimeException('L\'archive du plugin '.$id.' est corrompue (le hash SHA1 ne correspond pas).');
   288         -		}
   289         -
   290    422   		if (!file_exists('phar://' . PLUGINS_PATH . '/' . $id . '.phar/garradin_plugin.ini'))
   291    423   		{
   292    424   			throw new UserException('L\'archive '.$id.'.phar n\'est pas une extension Garradin : fichier garradin_plugin.ini manquant.');
   293    425   		}
   294    426   
   295    427   		if (!file_exists('phar://' . PLUGINS_PATH . '/' . $id . '.phar/index.php'))
   296    428   		{