Dotclear

source: inc/core/class.dc.modules.php @ 2708:e8b17b3a7413

Revision 2708:e8b17b3a7413, 14.5 KB checked in by Dsls, 11 years ago (diff)

1st attempt for admin url handler. See #1645, see #1959

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

Sites map