Dotclear

source: inc/core/class.dc.modules.php @ 2566:9bf417837888

Revision 2566:9bf417837888, 14.5 KB checked in by franck <carnet.franck.paul@…>, 12 years ago (diff)

Add some people in CREDITS, remove trailing spaces and tabs.

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

Sites map