Dotclear

source: inc/core/class.dc.modules.php @ 2900:d12215a1a1e7

Revision 2900:d12215a1a1e7, 15.0 KB checked in by Dsls, 11 years ago (diff)

First shot for per_blog settings for plugins, see #2041

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

Sites map