Dotclear

source: inc/core/class.dc.modules.php @ 2945:d72e6de6395d

Revision 2945:d72e6de6395d, 14.8 KB checked in by franck <carnet.franck.paul@…>, 11 years ago (diff)

Should also not load _admin.php/_public.php/_xmlrpc.php if _prepend.php of module returns null

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

Sites map