Dotclear

source: inc/core/class.dc.modules.php @ 2245:5c98f11b35de

Revision 2245:5c98f11b35de, 14.4 KB checked in by franck <carnet.franck.paul@…>, 12 years ago (diff)

Ignore also .git and .hg subfolder in package installation

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

Sites map