Dotclear

source: inc/core/class.dc.modules.php @ 17:6fc4226eaebd

Revision 17:6fc4226eaebd, 12.7 KB checked in by Dsls <dsls@…>, 14 years ago (diff)

Temporarily removed safe_mode on stable branch, will work on it on dedicated branch before further merge.

Line 
1<?php
2# -- BEGIN LICENSE BLOCK ---------------------------------------
3#
4# This file is part of Dotclear 2.
5#
6# Copyright (c) 2003-2010 Olivier Meunier & Association Dotclear
7# Licensed under the GPL version 2.0 license.
8# See LICENSE file or
9# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
10#
11# -- END LICENSE BLOCK -----------------------------------------
12if (!defined('DC_RC_PATH')) { return; }
13
14/**
15@ingroup DC_CORE
16@brief Modules handler
17
18Provides an object to handle modules (themes or plugins). An instance of this
19class is provided by dcCore $plugins property and used for plugins.
20*/
21class dcModules
22{
23     protected $path;
24     protected $ns;
25     protected $modules = array();
26     protected $disabled = array();
27     protected $errors = array();
28     protected $modules_names = array();
29     
30     protected $id;
31     protected $mroot;
32     
33     # Inclusion variables
34     protected static $superglobals = array('GLOBALS','_SERVER','_GET','_POST','_COOKIE','_FILES','_ENV','_REQUEST','_SESSION');
35     protected static $_k;
36     protected static $_n;
37     
38     public $core;  ///< <b>dcCore</b>  dcCore instance
39     
40     /**
41     Object constructor.
42     
43     @param    core      <b>dcCore</b>  dcCore instance
44     */
45     public function __construct($core)
46     {
47          $this->core =& $core;
48     }
49     
50     /**
51     Loads modules. <var>$path</var> could be a separated list of paths
52     (path separator depends on your OS).
53     
54     <var>$ns</var> indicates if an additionnal file needs to be loaded on plugin
55     load, value could be:
56     - admin (loads module's _admin.php)
57     - public (loads module's _public.php)
58     - xmlrpc (loads module's _xmlrpc.php)
59     
60     <var>$lang</var> indicates if we need to load a lang file on plugin
61     loading.
62     */
63     public function loadModules($path,$ns=null,$lang=null)
64     {
65          $this->path = explode(PATH_SEPARATOR,$path);
66          $this->ns = $ns;
67         
68          foreach ($this->path as $root)
69          {
70               if (!is_dir($root) || !is_readable($root)) {
71                    continue;
72               }
73               
74               if (substr($root,-1) != '/') {
75                    $root .= '/';
76               }
77               
78               if (($d = @dir($root)) === false) {
79                    continue;
80               }
81               
82               while (($entry = $d->read()) !== false)
83               {
84                    $full_entry = $root.'/'.$entry;
85                   
86                    if ($entry != '.' && $entry != '..' && is_dir($full_entry)
87                    && file_exists($full_entry.'/_define.php'))
88                    {
89                         if (!file_exists($full_entry.'/_disabled'))
90                         {
91                              $this->id = $entry;
92                              $this->mroot = $full_entry;
93                              require $full_entry.'/_define.php';
94                              $this->id = null;
95                              $this->mroot = null;
96                         }
97                         else
98                         {
99                              $this->disabled[$entry] = array(
100                                   'root' => $full_entry,
101                                   'root_writable' => is_writable($full_entry)
102                              );
103                         }
104                    }
105               }
106               $d->close();
107          }
108         
109          # Sort plugins
110          uasort($this->modules,array($this,'sortModules'));
111         
112          # Load translation, _prepend and ns_file
113          foreach ($this->modules as $id => $m)
114          {
115               if (file_exists($m['root'].'/_prepend.php'))
116               {
117                    $r = require $m['root'].'/_prepend.php';
118                   
119                    # If _prepend.php file returns null (ie. it has a void return statement)
120                    if (is_null($r)) {
121                         continue;
122                    }
123                    unset($r);
124               }
125               
126               $this->loadModuleL10N($id,$lang,'main');
127               if ($ns == 'admin') {
128                    $this->loadModuleL10Nresources($id,$lang);
129               }
130               $this->loadNsFile($id,$ns);
131          }
132     }
133     
134     public function requireDefine($dir,$id)
135     {
136          if (file_exists($dir.'/_define.php')) {
137               $this->id = $id;
138               require $dir.'/_define.php';
139               $this->id = null;
140          }
141     }   
142     
143     /**
144     This method registers a module in modules list. You should use this to
145     register a new module.
146     
147     <var>$permissions</var> is a comma separated list of permissions for your
148     module. If <var>$permissions</var> is null, only super admin has access to
149     this module.
150     
151     <var>$priority</var> is an integer. Modules are sorted by priority and name.
152     Lowest priority comes first.
153     
154     @param    name           <b>string</b>       Module name
155     @param    desc           <b>string</b>       Module description
156     @param    author         <b>string</b>       Module author name
157     @param    version        <b>string</b>       Module version
158     @param    permissions    <b>string</b>       Module permissions
159     @param    priority       <b>integer</b>      Module priority
160     */
161     public function registerModule($name,$desc,$author,$version,$permissions=null,$priority=1000)
162     {
163          if ($this->ns == 'admin') {
164               if ($permissions == '' && !$this->core->auth->isSuperAdmin()) {
165                    return;
166               } elseif (!$this->core->auth->check($permissions,$this->core->blog->id)) {
167                    return;
168               }
169          }
170         
171          if ($this->id) {
172               $module_exists = array_key_exists($name,$this->modules_names);
173               $module_overwrite = $module_exists ? version_compare($this->modules_names[$name],$version,'<') : false;
174               if (!$module_exists || ($module_exists && $module_overwrite)) {
175                    $this->modules_names[$name] = $version;
176                    $this->modules[$this->id] = array(
177                         'root' => $this->mroot,
178                         'name' => $name,
179                         'desc' => $desc,
180                         'author' => $author,
181                         'version' => $version,
182                         'permissions' => $permissions,
183                         'priority' => $priority === null ? 1000 : (integer) $priority,
184                         'root_writable' => is_writable($this->mroot)
185                    );
186               }
187               else {
188                    $path1 = path::real($this->moduleInfo($name,'root'));
189                    $path2 = path::real($this->mroot);
190                    $this->errors[] = sprintf(
191                         __('%s: in [%s] and [%s]'),
192                         '<strong>'.$name.'</strong>',
193                         '<em>'.$path1.'</em>',
194                         '<em>'.$path2.'</em>'
195                    );
196               }
197          }
198     }
199     
200     public function resetModulesList()
201     {
202          $this->modules = array();
203          $this->modules_names = array();
204     }   
205     
206     public static function installPackage($zip_file,dcModules &$modules)
207     {
208          $zip = new fileUnzip($zip_file);
209          $zip->getList(false,'#(^|/)(__MACOSX|\.svn|\.DS_Store|\.directory|Thumbs\.db)(/|$)#');
210         
211          $zip_root_dir = $zip->getRootDir();
212          $define = '';
213          if ($zip_root_dir != false) {
214               $target = dirname($zip_file);
215               $destination = $target.'/'.$zip_root_dir;
216               $define = $zip_root_dir.'/_define.php';
217               $has_define = $zip->hasFile($define);
218          } else {
219               $target = dirname($zip_file).'/'.preg_replace('/\.([^.]+)$/','',basename($zip_file));
220               $destination = $target;
221               $define = '_define.php';
222               $has_define = $zip->hasFile($define);
223          }
224         
225          if ($zip->isEmpty()) {
226               $zip->close();
227               unlink($zip_file);
228               throw new Exception(__('Empty module zip file.'));
229          }
230         
231          if (!$has_define) {
232               $zip->close();
233               unlink($zip_file);
234               throw new Exception(__('The zip file does not appear to be a valid Dotclear module.'));
235          }
236         
237          $ret_code = 1;
238         
239          if (is_dir($destination))
240          {
241               # test for update
242               $sandbox = clone $modules;
243               $zip->unzip($define, $target.'/_define.php');
244               
245               $sandbox->resetModulesList();
246               $sandbox->requireDefine($target,basename($destination));
247               unlink($target.'/_define.php');
248               $new_modules = $sandbox->getModules();
249               
250               if (!empty($new_modules))
251               {
252                    $tmp = array_keys($new_modules);
253                    $id = $tmp[0];
254                    $cur_module = $modules->getModules($id);
255                    if (!empty($cur_module) && $new_modules[$id]['version'] != $cur_module['version'])
256                    {
257                         # delete old module
258                         if (!files::deltree($destination)) {
259                              throw new Exception(__('An error occurred during module deletion.'));
260                         }
261                         $ret_code = 2;
262                    }
263                    else
264                    {
265                         $zip->close();
266                         unlink($zip_file);
267                         throw new Exception(sprintf(__('Unable to upgrade "%s". (same version)'),basename($destination)));       
268                    }
269               }
270               else
271               {
272                    $zip->close();
273                    unlink($zip_file);
274                    throw new Exception(sprintf(__('Unable to read new _define.php file')));             
275               }
276          }
277          $zip->unzipAll($target);
278          $zip->close();
279          unlink($zip_file);
280          return $ret_code;
281     }
282     
283     /**
284     This method installs all modules having a _install file.
285     
286     @see dcModules::installModule
287     */
288     public function installModules()
289     {
290          $res = array('success'=>array(),'failure'=>array());
291          foreach ($this->modules as $id => &$m)
292          {
293               $i = $this->installModule($id,$msg);
294               if ($i === true) {
295                    $res['success'][$id] = true;
296               } elseif ($i === false) {
297                    $res['failure'][$id] = $msg;
298               }
299          }
300         
301          return $res;
302     }
303     
304     /**
305     This method installs module with ID <var>$id</var> and having a _install
306     file. This file should throw exception on failure or true if it installs
307     successfully.
308     
309     <var>$msg</var> is an out parameter that handle installer message.
310     
311     @param    id        <b>string</b>       Module ID
312     @param    msg       <b>string</b>       Module installer message
313     @return   <b>boolean</b>
314     */
315     public function installModule($id,&$msg)
316     {
317          try {
318               $i = $this->loadModuleFile($this->modules[$id]['root'].'/_install.php');
319               if ($i === true) {
320                    return true;
321               }
322          } catch (Exception $e) {
323               $msg = $e->getMessage();
324               return false;
325          }
326         
327          return null;
328     }
329     
330     public function deleteModule($id,$disabled=false)
331     {
332          if ($disabled) {
333               $p =& $this->disabled;
334          } else {
335               $p =& $this->modules;
336          }
337         
338          if (!isset($p[$id])) {
339               throw new Exception(__('No such module.'));
340          }
341         
342          if (!files::deltree($p[$id]['root'])) {
343               throw new Exception(__('Cannot remove module files'));
344          }
345     }
346     
347     public function deactivateModule($id)
348     {
349          if (!isset($this->modules[$id])) {
350               throw new Exception(__('No such module.'));
351          }
352         
353          if (!$this->modules[$id]['root_writable']) {
354               throw new Exception(__('Cannot deactivate plugin.'));
355          }
356         
357          if (@file_put_contents($this->modules[$id]['root'].'/_disabled','')) {
358               throw new Exception(__('Cannot deactivate plugin.'));
359          }
360     }
361     
362     public function activateModule($id)
363     {
364          if (!isset($this->disabled[$id])) {
365               throw new Exception(__('No such module.'));
366          }
367         
368          if (!$this->disabled[$id]['root_writable']) {
369               throw new Exception(__('Cannot activate plugin.'));
370          }
371         
372          if (@unlink($this->disabled[$id]['root'].'/_disabled') === false) {
373               throw new Exception(__('Cannot activate plugin.'));
374          }
375     }
376     
377     /**
378     This method will search for file <var>$file</var> in language
379     <var>$lang</var> for module <var>$id</var>.
380     
381     <var>$file</var> should not have any extension.
382     
383     @param    id        <b>string</b>       Module ID
384     @param    lang      <b>string</b>       Language code
385     @param    file      <b>string</b>       File name (without extension)
386     */
387     public function loadModuleL10N($id,$lang,$file)
388     {
389          if (!$lang || !isset($this->modules[$id])) {
390               return;
391          }
392         
393          $lfile = $this->modules[$id]['root'].'/locales/%s/%s';
394          if (l10n::set(sprintf($lfile,$lang,$file)) === false && $lang != 'en') {
395               l10n::set(sprintf($lfile,'en',$file));
396          }
397     }
398     
399     public function loadModuleL10Nresources($id,$lang)
400     {
401          if (!$lang || !isset($this->modules[$id])) {
402               return;
403          }
404         
405          $f = l10n::getFilePath($this->modules[$id]['root'].'/locales','resources.php',$lang);
406          if ($f) {
407               $this->loadModuleFile($f);
408          }
409     }
410     
411     /**
412     Returns all modules associative array or only one module if <var>$id</var>
413     is present.
414     
415     @param    id        <b>string</b>       Optionnal module ID
416     @return   <b>array</b>
417     */
418     public function getModules($id=null)
419     {
420          if ($id && isset($this->modules[$id])) {
421               return $this->modules[$id];
422          }
423          return $this->modules;
424     }
425     
426     /**
427     Returns true if the module with ID <var>$id</var> exists.
428     
429     @param    id        <b>string</b>       Module ID
430     @return   <b>boolean</b>
431     */
432     public function moduleExists($id)
433     {
434          return isset($this->modules[$id]);
435     }
436     
437     /**
438     Returns all disabled modules in an array
439     
440     @return   <b>array</b>
441     */
442     public function getDisabledModules()
443     {
444          return $this->disabled;
445     }
446     
447     /**
448     Returns root path for module with ID <var>$id</var>.
449     
450     @param    id        <b>string</b>       Module ID
451     @return   <b>string</b>
452     */
453     public function moduleRoot($id)
454     {
455          return $this->moduleInfo($id,'root');
456     }
457     
458     /**
459     Returns a module information that could be:
460     - root
461     - name
462     - desc
463     - author
464     - version
465     - permissions
466     - priority
467     
468     @param    id        <b>string</b>       Module ID
469     @param    info      <b>string</b>       Information to retrieve
470     @return   <b>string</b>
471     */
472     public function moduleInfo($id,$info)
473     {
474          return isset($this->modules[$id][$info]) ? $this->modules[$id][$info] : null;
475     }
476     
477     /**
478     Loads namespace <var>$ns</var> specific files for all modules.
479     
480     @param    ns        <b>string</b>       Namespace name
481     */
482     public function loadNsFiles($ns=null)
483     {
484          foreach ($this->modules as $k => $v) {
485               $this->loadNsFile($k,$ns);
486          }
487     }
488     
489     /**
490     Loads namespace <var>$ns</var> specific file for module with ID
491     <var>$id</var>
492     
493     @param    id        <b>string</b>       Module ID
494     @param    ns        <b>string</b>       Namespace name
495     */
496     public function loadNsFile($id,$ns=null)
497     {
498          switch ($ns) {
499               case 'admin':
500                    $this->loadModuleFile($this->modules[$id]['root'].'/_admin.php');
501                    break;
502               case 'public':
503                    $this->loadModuleFile($this->modules[$id]['root'].'/_public.php');
504                    break;
505               case 'xmlrpc':
506                    $this->loadModuleFile($this->modules[$id]['root'].'/_xmlrpc.php');
507                    break;
508          }
509     }
510     
511     public function getErrors()
512     {
513          return $this->errors;
514     }
515     
516     protected function loadModuleFile($________)
517     {
518          if (!file_exists($________)) {
519               return;
520          }
521         
522          self::$_k = array_keys($GLOBALS);
523         
524          foreach (self::$_k as self::$_n) {
525               if (!in_array(self::$_n,self::$superglobals)) {
526                    global ${self::$_n};
527               }
528          }
529         
530          return require $________;
531     }
532     
533     private function sortModules($a,$b)
534     {
535          if ($a['priority'] == $b['priority']) {
536               return strcasecmp($a['name'],$b['name']);
537          }
538         
539          return ($a['priority'] < $b['priority']) ? -1 : 1;
540     }
541}
542?>
Note: See TracBrowser for help on using the repository browser.

Sites map