Dotclear

source: inc/core/class.dc.modules.php @ 1179:a43a29427ef3

Revision 1179:a43a29427ef3, 13.2 KB checked in by franck <carnet.franck.paul@…>, 12 years ago (diff)

Update copyright notice

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

Sites map