| [0] | 1 | <?php | 
|---|
| [3731] | 2 | /** | 
|---|
|  | 3 | * @brief Modules handler | 
|---|
|  | 4 | * | 
|---|
|  | 5 | * Provides an object to handle modules (themes or plugins). | 
|---|
|  | 6 | * | 
|---|
|  | 7 | * @package Dotclear | 
|---|
|  | 8 | * @subpackage Core | 
|---|
|  | 9 | * | 
|---|
|  | 10 | * @copyright Olivier Meunier & Association Dotclear | 
|---|
|  | 11 | * @copyright GPL-2.0-only | 
|---|
|  | 12 | */ | 
|---|
|  | 13 |  | 
|---|
| [3730] | 14 | if (!defined('DC_RC_PATH')) {return;} | 
|---|
| [0] | 15 |  | 
|---|
|  | 16 | class dcModules | 
|---|
|  | 17 | { | 
|---|
| [3730] | 18 | protected $path; | 
|---|
|  | 19 | protected $ns; | 
|---|
| [3874] | 20 | protected $modules       = []; | 
|---|
|  | 21 | protected $disabled      = []; | 
|---|
|  | 22 | protected $errors        = []; | 
|---|
|  | 23 | protected $modules_names = []; | 
|---|
|  | 24 | protected $all_modules   = []; | 
|---|
| [3730] | 25 | protected $disabled_mode = false; | 
|---|
| [3874] | 26 | protected $disabled_meta = []; | 
|---|
|  | 27 | protected $to_disable    = []; | 
|---|
| [2566] | 28 |  | 
|---|
| [3730] | 29 | protected $id; | 
|---|
|  | 30 | protected $mroot; | 
|---|
| [2566] | 31 |  | 
|---|
| [3730] | 32 | # Inclusion variables | 
|---|
| [3874] | 33 | protected static $superglobals = ['GLOBALS', '_SERVER', '_GET', '_POST', '_COOKIE', '_FILES', '_ENV', '_REQUEST', '_SESSION']; | 
|---|
| [3730] | 34 | protected static $_k; | 
|---|
|  | 35 | protected static $_n; | 
|---|
| [2239] | 36 |  | 
|---|
| [3730] | 37 | protected static $type = null; | 
|---|
| [2566] | 38 |  | 
|---|
| [3730] | 39 | public $core; ///< <b>dcCore</b>    dcCore instance | 
|---|
| [2566] | 40 |  | 
|---|
| [3730] | 41 | /** | 
|---|
|  | 42 | Object constructor. | 
|---|
| [2566] | 43 |  | 
|---|
| [3730] | 44 | @param    core        <b>dcCore</b>    dcCore instance | 
|---|
|  | 45 | */ | 
|---|
|  | 46 | public function __construct($core) | 
|---|
|  | 47 | { | 
|---|
|  | 48 | $this->core = &$core; | 
|---|
|  | 49 | } | 
|---|
| [2566] | 50 |  | 
|---|
| [3730] | 51 | /** | 
|---|
|  | 52 | * Checks all modules dependencies | 
|---|
|  | 53 | *     Fills in the following information in module : | 
|---|
|  | 54 | *       * cannot_enable : list reasons why module cannot be enabled. Not set if module can be enabled | 
|---|
|  | 55 | *       * cannot_disable : list reasons why module cannot be disabled. Not set if module can be disabled | 
|---|
|  | 56 | *       * implies : reverse dependencies | 
|---|
|  | 57 | * @return array list of enabled modules with unmet dependencies, and that must be disabled. | 
|---|
|  | 58 | */ | 
|---|
|  | 59 | public function checkDependencies() | 
|---|
|  | 60 | { | 
|---|
|  | 61 | $dc_version       = preg_replace('/\-dev$/', '', DC_VERSION); | 
|---|
| [3874] | 62 | $this->to_disable = []; | 
|---|
| [3730] | 63 | foreach ($this->all_modules as $k => &$m) { | 
|---|
|  | 64 | if (isset($m['requires'])) { | 
|---|
| [3874] | 65 | $missing = []; | 
|---|
| [3730] | 66 | foreach ($m['requires'] as &$dep) { | 
|---|
|  | 67 | if (!is_array($dep)) { | 
|---|
| [3874] | 68 | $dep = [$dep]; | 
|---|
| [3730] | 69 | } | 
|---|
|  | 70 | // grab missing dependencies | 
|---|
|  | 71 | if (!isset($this->all_modules[$dep[0]]) && ($dep[0] != 'core')) { | 
|---|
|  | 72 | // module not present | 
|---|
|  | 73 | $missing[$dep[0]] = sprintf(__("Requires %s module which is not installed"), $dep[0]); | 
|---|
|  | 74 | } elseif ((count($dep) > 1) && | 
|---|
|  | 75 | version_compare(($dep[0] == 'core' ? $dc_version : $this->all_modules[$dep[0]]['version']), $dep[1]) == -1) { | 
|---|
|  | 76 | // module present, but version missing | 
|---|
|  | 77 | if ($dep[0] == 'core') { | 
|---|
|  | 78 | $missing[$dep[0]] = sprintf(__("Requires Dotclear version %s, but version %s is installed"), | 
|---|
|  | 79 | $dep[1], $dc_version); | 
|---|
|  | 80 | } else { | 
|---|
|  | 81 | $missing[$dep[0]] = sprintf(__("Requires %s module version %s, but version %s is installed"), | 
|---|
|  | 82 | $dep[0], $dep[1], $this->all_modules[$dep[0]]['version']); | 
|---|
|  | 83 | } | 
|---|
|  | 84 | } elseif (($dep[0] != 'core') && !$this->all_modules[$dep[0]]['enabled']) { | 
|---|
|  | 85 | // module disabled | 
|---|
|  | 86 | $missing[$dep[0]] = sprintf(__("Requires %s module which is disabled"), $dep[0]); | 
|---|
|  | 87 | } | 
|---|
|  | 88 | $this->all_modules[$dep[0]]['implies'][] = $k; | 
|---|
|  | 89 | } | 
|---|
|  | 90 | if (count($missing)) { | 
|---|
|  | 91 | $m['cannot_enable'] = $missing; | 
|---|
|  | 92 | if ($m['enabled']) { | 
|---|
| [3874] | 93 | $this->to_disable[] = ['name' => $k, 'reason' => $missing]; | 
|---|
| [3730] | 94 | } | 
|---|
|  | 95 | } | 
|---|
|  | 96 | } | 
|---|
|  | 97 | } | 
|---|
|  | 98 | // Check modules that cannot be disabled | 
|---|
|  | 99 | foreach ($this->modules as $k => &$m) { | 
|---|
|  | 100 | if (isset($m['implies']) && $m['enabled']) { | 
|---|
|  | 101 | foreach ($m['implies'] as $im) { | 
|---|
|  | 102 | if (isset($this->all_modules[$im]) && $this->all_modules[$im]['enabled']) { | 
|---|
|  | 103 | $m['cannot_disable'][] = $im; | 
|---|
|  | 104 | } | 
|---|
|  | 105 | } | 
|---|
|  | 106 | } | 
|---|
|  | 107 | } | 
|---|
|  | 108 | } | 
|---|
| [3066] | 109 |  | 
|---|
| [3730] | 110 | /** | 
|---|
|  | 111 | * Checks all modules dependencies, and disable unmet dependencies | 
|---|
|  | 112 | * @param  string $redir_url URL to redirect if modules are to disable | 
|---|
|  | 113 | * @return boolean, true if a redirection has been performed | 
|---|
|  | 114 | */ | 
|---|
|  | 115 | public function disableDepModules($redir_url) | 
|---|
|  | 116 | { | 
|---|
|  | 117 | if (isset($_GET['dep'])) { | 
|---|
|  | 118 | // Avoid infinite redirects | 
|---|
|  | 119 | return false; | 
|---|
|  | 120 | } | 
|---|
| [3874] | 121 | $reason = []; | 
|---|
| [3730] | 122 | foreach ($this->to_disable as $module) { | 
|---|
|  | 123 | try { | 
|---|
|  | 124 | $this->deactivateModule($module['name']); | 
|---|
|  | 125 | $reason[] = sprintf("<li>%s : %s</li>", $module['name'], join(',', $module['reason'])); | 
|---|
|  | 126 | } catch (Exception $e) { | 
|---|
|  | 127 | } | 
|---|
|  | 128 | } | 
|---|
|  | 129 | if (count($reason)) { | 
|---|
|  | 130 | $message = sprintf("<p>%s</p><ul>%s</ul>", | 
|---|
|  | 131 | __('The following extensions have been disabled :'), | 
|---|
|  | 132 | join('', $reason) | 
|---|
|  | 133 | ); | 
|---|
| [3874] | 134 | dcPage::addWarningNotice($message, ['divtag' => true, 'with_ts' => false]); | 
|---|
| [3730] | 135 | $url = $redir_url . (strpos($redir_url, "?") ? '&' : '?') . 'dep=1'; | 
|---|
|  | 136 | http::redirect($url); | 
|---|
|  | 137 | return true; | 
|---|
|  | 138 | } | 
|---|
|  | 139 | return false; | 
|---|
|  | 140 | } | 
|---|
| [2997] | 141 |  | 
|---|
| [3730] | 142 | /** | 
|---|
|  | 143 | Loads modules. <var>$path</var> could be a separated list of paths | 
|---|
|  | 144 | (path separator depends on your OS). | 
|---|
| [2566] | 145 |  | 
|---|
| [3730] | 146 | <var>$ns</var> indicates if an additionnal file needs to be loaded on plugin | 
|---|
|  | 147 | load, value could be: | 
|---|
|  | 148 | - admin (loads module's _admin.php) | 
|---|
|  | 149 | - public (loads module's _public.php) | 
|---|
|  | 150 | - xmlrpc (loads module's _xmlrpc.php) | 
|---|
| [2566] | 151 |  | 
|---|
| [3730] | 152 | <var>$lang</var> indicates if we need to load a lang file on plugin | 
|---|
|  | 153 | loading. | 
|---|
|  | 154 | */ | 
|---|
|  | 155 | public function loadModules($path, $ns = null, $lang = null) | 
|---|
|  | 156 | { | 
|---|
|  | 157 | $this->path = explode(PATH_SEPARATOR, $path); | 
|---|
|  | 158 | $this->ns   = $ns; | 
|---|
| [2566] | 159 |  | 
|---|
| [3730] | 160 | $disabled = isset($_SESSION['sess_safe_mode']) && $_SESSION['sess_safe_mode']; | 
|---|
|  | 161 | $disabled = $disabled && !get_parent_class($this) ? true : false; | 
|---|
| [2566] | 162 |  | 
|---|
| [3874] | 163 | $ignored = []; | 
|---|
| [2945] | 164 |  | 
|---|
| [3730] | 165 | foreach ($this->path as $root) { | 
|---|
|  | 166 | if (!is_dir($root) || !is_readable($root)) { | 
|---|
|  | 167 | continue; | 
|---|
|  | 168 | } | 
|---|
| [2566] | 169 |  | 
|---|
| [3730] | 170 | if (substr($root, -1) != '/') { | 
|---|
|  | 171 | $root .= '/'; | 
|---|
|  | 172 | } | 
|---|
| [2566] | 173 |  | 
|---|
| [3730] | 174 | if (($d = @dir($root)) === false) { | 
|---|
|  | 175 | continue; | 
|---|
|  | 176 | } | 
|---|
| [2566] | 177 |  | 
|---|
| [3730] | 178 | while (($entry = $d->read()) !== false) { | 
|---|
|  | 179 | $full_entry = $root . $entry; | 
|---|
| [2566] | 180 |  | 
|---|
| [3730] | 181 | if ($entry != '.' && $entry != '..' && is_dir($full_entry) | 
|---|
|  | 182 | && file_exists($full_entry . '/_define.php')) { | 
|---|
|  | 183 | if (!file_exists($full_entry . '/_disabled') && !$disabled) { | 
|---|
|  | 184 | $this->id    = $entry; | 
|---|
|  | 185 | $this->mroot = $full_entry; | 
|---|
|  | 186 | ob_start(); | 
|---|
|  | 187 | require $full_entry . '/_define.php'; | 
|---|
|  | 188 | ob_end_clean(); | 
|---|
|  | 189 | $this->all_modules[$entry] = &$this->modules[$entry]; | 
|---|
|  | 190 | $this->id                  = null; | 
|---|
|  | 191 | $this->mroot               = null; | 
|---|
|  | 192 | } else { | 
|---|
|  | 193 | if (file_exists($full_entry . '/_define.php')) { | 
|---|
|  | 194 | $this->id            = $entry; | 
|---|
|  | 195 | $this->mroot         = $full_entry; | 
|---|
|  | 196 | $this->disabled_mode = true; | 
|---|
|  | 197 | ob_start(); | 
|---|
|  | 198 | require $full_entry . '/_define.php'; | 
|---|
|  | 199 | ob_end_clean(); | 
|---|
|  | 200 | $this->disabled_mode       = false; | 
|---|
|  | 201 | $this->disabled[$entry]    = $this->disabled_meta; | 
|---|
|  | 202 | $this->all_modules[$entry] = &$this->disabled[$entry]; | 
|---|
|  | 203 | $this->id                  = null; | 
|---|
|  | 204 | $this->mroot               = null; | 
|---|
|  | 205 | } | 
|---|
|  | 206 | } | 
|---|
|  | 207 | } | 
|---|
|  | 208 | } | 
|---|
|  | 209 | $d->close(); | 
|---|
|  | 210 | } | 
|---|
|  | 211 | $this->checkDependencies(); | 
|---|
|  | 212 | # Sort plugins | 
|---|
| [3874] | 213 | uasort($this->modules, [$this, 'sortModules']); | 
|---|
| [2566] | 214 |  | 
|---|
| [3730] | 215 | foreach ($this->modules as $id => $m) { | 
|---|
|  | 216 | # Load translation and _prepend | 
|---|
|  | 217 | if (file_exists($m['root'] . '/_prepend.php')) { | 
|---|
|  | 218 | $r = $this->loadModuleFile($m['root'] . '/_prepend.php'); | 
|---|
| [2566] | 219 |  | 
|---|
| [3730] | 220 | # If _prepend.php file returns null (ie. it has a void return statement) | 
|---|
|  | 221 | if (is_null($r)) { | 
|---|
|  | 222 | $ignored[] = $id; | 
|---|
|  | 223 | continue; | 
|---|
|  | 224 | } | 
|---|
|  | 225 | unset($r); | 
|---|
|  | 226 | } | 
|---|
| [2566] | 227 |  | 
|---|
| [3730] | 228 | $this->loadModuleL10N($id, $lang, 'main'); | 
|---|
|  | 229 | if ($ns == 'admin') { | 
|---|
|  | 230 | $this->loadModuleL10Nresources($id, $lang); | 
|---|
| [3874] | 231 | $this->core->adminurl->register('admin.plugin.' . $id, 'plugin.php', ['p' => $id]); | 
|---|
| [3730] | 232 | } | 
|---|
|  | 233 | } | 
|---|
| [3108] | 234 |  | 
|---|
| [3730] | 235 | // Give opportunity to do something before loading context (admin,public,xmlrpc) files | 
|---|
|  | 236 | $this->core->callBehavior('coreBeforeLoadingNsFiles', $this->core, $this, $lang); | 
|---|
| [3108] | 237 |  | 
|---|
| [3730] | 238 | foreach ($this->modules as $id => $m) { | 
|---|
|  | 239 | # If _prepend.php file returns null (ie. it has a void return statement) | 
|---|
|  | 240 | if (in_array($id, $ignored)) { | 
|---|
|  | 241 | continue; | 
|---|
|  | 242 | } | 
|---|
|  | 243 | # Load ns_file | 
|---|
|  | 244 | $this->loadNsFile($id, $ns); | 
|---|
|  | 245 | } | 
|---|
|  | 246 | } | 
|---|
| [2566] | 247 |  | 
|---|
| [3730] | 248 | public function requireDefine($dir, $id) | 
|---|
|  | 249 | { | 
|---|
|  | 250 | if (file_exists($dir . '/_define.php')) { | 
|---|
|  | 251 | $this->id = $id; | 
|---|
|  | 252 | ob_start(); | 
|---|
|  | 253 | require $dir . '/_define.php'; | 
|---|
|  | 254 | ob_end_clean(); | 
|---|
|  | 255 | $this->id = null; | 
|---|
|  | 256 | } | 
|---|
|  | 257 | } | 
|---|
| [2566] | 258 |  | 
|---|
| [3730] | 259 | /** | 
|---|
|  | 260 | This method registers a module in modules list. You should use this to | 
|---|
|  | 261 | register a new module. | 
|---|
| [2566] | 262 |  | 
|---|
| [3730] | 263 | <var>$permissions</var> is a comma separated list of permissions for your | 
|---|
|  | 264 | module. If <var>$permissions</var> is null, only super admin has access to | 
|---|
|  | 265 | this module. | 
|---|
| [2566] | 266 |  | 
|---|
| [3730] | 267 | <var>$priority</var> is an integer. Modules are sorted by priority and name. | 
|---|
|  | 268 | Lowest priority comes first. | 
|---|
| [2566] | 269 |  | 
|---|
| [3730] | 270 | @param    name            <b>string</b>        Module name | 
|---|
|  | 271 | @param    desc            <b>string</b>        Module description | 
|---|
|  | 272 | @param    author        <b>string</b>        Module author name | 
|---|
|  | 273 | @param    version        <b>string</b>        Module version | 
|---|
|  | 274 | @param    properties    <b>array</b>        extra properties | 
|---|
|  | 275 | (currently available keys : permissions, priority, type) | 
|---|
|  | 276 | */ | 
|---|
| [3874] | 277 | public function registerModule($name, $desc, $author, $version, $properties = []) | 
|---|
| [3730] | 278 | { | 
|---|
|  | 279 | if ($this->disabled_mode) { | 
|---|
|  | 280 | $this->disabled_meta = array_merge( | 
|---|
|  | 281 | $properties, | 
|---|
| [3874] | 282 | [ | 
|---|
| [3730] | 283 | 'root'          => $this->mroot, | 
|---|
|  | 284 | 'name'          => $name, | 
|---|
|  | 285 | 'desc'          => $desc, | 
|---|
|  | 286 | 'author'        => $author, | 
|---|
|  | 287 | 'version'       => $version, | 
|---|
|  | 288 | 'enabled'       => false, | 
|---|
|  | 289 | 'root_writable' => is_writable($this->mroot) | 
|---|
| [3874] | 290 | ] | 
|---|
| [3730] | 291 | ); | 
|---|
|  | 292 | return; | 
|---|
|  | 293 | } | 
|---|
|  | 294 | # Fallback to legacy registerModule parameters | 
|---|
|  | 295 | if (!is_array($properties)) { | 
|---|
|  | 296 | $args       = func_get_args(); | 
|---|
| [3874] | 297 | $properties = []; | 
|---|
| [3730] | 298 | if (isset($args[4])) { | 
|---|
|  | 299 | $properties['permissions'] = $args[4]; | 
|---|
|  | 300 | } | 
|---|
|  | 301 | if (isset($args[5])) { | 
|---|
|  | 302 | $properties['priority'] = (integer) $args[5]; | 
|---|
|  | 303 | } | 
|---|
|  | 304 | } | 
|---|
| [2492] | 305 |  | 
|---|
| [3730] | 306 | # Default module properties | 
|---|
|  | 307 | $properties = array_merge( | 
|---|
| [3874] | 308 | [ | 
|---|
| [3730] | 309 | 'permissions'       => null, | 
|---|
|  | 310 | 'priority'          => 1000, | 
|---|
|  | 311 | 'standalone_config' => false, | 
|---|
|  | 312 | 'type'              => null, | 
|---|
|  | 313 | 'enabled'           => true, | 
|---|
| [3874] | 314 | 'requires'          => [], | 
|---|
|  | 315 | 'settings'          => [] | 
|---|
|  | 316 | ], $properties | 
|---|
| [3730] | 317 | ); | 
|---|
| [2239] | 318 |  | 
|---|
| [3730] | 319 | # Check module type | 
|---|
|  | 320 | if (self::$type !== null && $properties['type'] !== null && $properties['type'] != self::$type) { | 
|---|
|  | 321 | $this->errors[] = sprintf( | 
|---|
|  | 322 | __('Module "%s" has type "%s" that mismatch required module type "%s".'), | 
|---|
|  | 323 | '<strong>' . html::escapeHTML($name) . '</strong>', | 
|---|
|  | 324 | '<em>' . html::escapeHTML($properties['type']) . '</em>', | 
|---|
|  | 325 | '<em>' . html::escapeHTML(self::$type) . '</em>' | 
|---|
|  | 326 | ); | 
|---|
|  | 327 | return; | 
|---|
|  | 328 | } | 
|---|
| [2239] | 329 |  | 
|---|
| [3730] | 330 | # Check module perms on admin side | 
|---|
|  | 331 | $permissions = $properties['permissions']; | 
|---|
|  | 332 | if ($this->ns == 'admin') { | 
|---|
|  | 333 | if ($permissions == '' && !$this->core->auth->isSuperAdmin()) { | 
|---|
|  | 334 | return; | 
|---|
|  | 335 | } elseif (!$this->core->auth->check($permissions, $this->core->blog->id)) { | 
|---|
|  | 336 | return; | 
|---|
|  | 337 | } | 
|---|
|  | 338 | } | 
|---|
| [2566] | 339 |  | 
|---|
| [3730] | 340 | # Check module install on multiple path | 
|---|
|  | 341 | if ($this->id) { | 
|---|
|  | 342 | $module_exists    = array_key_exists($name, $this->modules_names); | 
|---|
|  | 343 | $module_overwrite = $module_exists ? version_compare($this->modules_names[$name], $version, '<') : false; | 
|---|
|  | 344 | if (!$module_exists || ($module_exists && $module_overwrite)) { | 
|---|
|  | 345 | $this->modules_names[$name] = $version; | 
|---|
|  | 346 | $this->modules[$this->id]   = array_merge( | 
|---|
|  | 347 | $properties, | 
|---|
| [3874] | 348 | [ | 
|---|
| [3730] | 349 | 'root'          => $this->mroot, | 
|---|
|  | 350 | 'name'          => $name, | 
|---|
|  | 351 | 'desc'          => $desc, | 
|---|
|  | 352 | 'author'        => $author, | 
|---|
|  | 353 | 'version'       => $version, | 
|---|
|  | 354 | 'root_writable' => is_writable($this->mroot) | 
|---|
| [3874] | 355 | ] | 
|---|
| [3730] | 356 | ); | 
|---|
|  | 357 | } else { | 
|---|
|  | 358 | $path1          = path::real($this->moduleInfo($name, 'root')); | 
|---|
|  | 359 | $path2          = path::real($this->mroot); | 
|---|
|  | 360 | $this->errors[] = sprintf( | 
|---|
|  | 361 | __('Module "%s" is installed twice in "%s" and "%s".'), | 
|---|
|  | 362 | '<strong>' . $name . '</strong>', | 
|---|
|  | 363 | '<em>' . $path1 . '</em>', | 
|---|
|  | 364 | '<em>' . $path2 . '</em>' | 
|---|
|  | 365 | ); | 
|---|
|  | 366 | } | 
|---|
|  | 367 | } | 
|---|
|  | 368 | } | 
|---|
| [2566] | 369 |  | 
|---|
| [3730] | 370 | public function resetModulesList() | 
|---|
|  | 371 | { | 
|---|
| [3874] | 372 | $this->modules       = []; | 
|---|
|  | 373 | $this->modules_names = []; | 
|---|
|  | 374 | $this->errors        = []; | 
|---|
| [3730] | 375 | } | 
|---|
| [2566] | 376 |  | 
|---|
| [3730] | 377 | public static function installPackage($zip_file, dcModules &$modules) | 
|---|
|  | 378 | { | 
|---|
|  | 379 | $zip = new fileUnzip($zip_file); | 
|---|
|  | 380 | $zip->getList(false, '#(^|/)(__MACOSX|\.svn|\.hg|\.git|\.DS_Store|\.directory|Thumbs\.db)(/|$)#'); | 
|---|
| [2566] | 381 |  | 
|---|
| [3730] | 382 | $zip_root_dir = $zip->getRootDir(); | 
|---|
|  | 383 | $define       = ''; | 
|---|
|  | 384 | if ($zip_root_dir != false) { | 
|---|
|  | 385 | $target      = dirname($zip_file); | 
|---|
|  | 386 | $destination = $target . '/' . $zip_root_dir; | 
|---|
|  | 387 | $define      = $zip_root_dir . '/_define.php'; | 
|---|
|  | 388 | $has_define  = $zip->hasFile($define); | 
|---|
|  | 389 | } else { | 
|---|
|  | 390 | $target      = dirname($zip_file) . '/' . preg_replace('/\.([^.]+)$/', '', basename($zip_file)); | 
|---|
|  | 391 | $destination = $target; | 
|---|
|  | 392 | $define      = '_define.php'; | 
|---|
|  | 393 | $has_define  = $zip->hasFile($define); | 
|---|
|  | 394 | } | 
|---|
| [2566] | 395 |  | 
|---|
| [3730] | 396 | if ($zip->isEmpty()) { | 
|---|
|  | 397 | $zip->close(); | 
|---|
|  | 398 | unlink($zip_file); | 
|---|
|  | 399 | throw new Exception(__('Empty module zip file.')); | 
|---|
|  | 400 | } | 
|---|
| [2566] | 401 |  | 
|---|
| [3730] | 402 | if (!$has_define) { | 
|---|
|  | 403 | $zip->close(); | 
|---|
|  | 404 | unlink($zip_file); | 
|---|
|  | 405 | throw new Exception(__('The zip file does not appear to be a valid Dotclear module.')); | 
|---|
|  | 406 | } | 
|---|
| [2566] | 407 |  | 
|---|
| [3730] | 408 | $ret_code = 1; | 
|---|
| [2566] | 409 |  | 
|---|
| [3730] | 410 | if (!is_dir($destination)) { | 
|---|
|  | 411 | try { | 
|---|
|  | 412 | files::makeDir($destination, true); | 
|---|
| [2566] | 413 |  | 
|---|
| [3730] | 414 | $sandbox = clone $modules; | 
|---|
|  | 415 | $zip->unzip($define, $target . '/_define.php'); | 
|---|
| [2566] | 416 |  | 
|---|
| [3730] | 417 | $sandbox->resetModulesList(); | 
|---|
|  | 418 | $sandbox->requireDefine($target, basename($destination)); | 
|---|
|  | 419 | unlink($target . '/_define.php'); | 
|---|
| [2566] | 420 |  | 
|---|
| [3730] | 421 | $new_errors = $sandbox->getErrors(); | 
|---|
|  | 422 | if (!empty($new_errors)) { | 
|---|
|  | 423 | $new_errors = is_array($new_errors) ? implode(" \n", $new_errors) : $new_errors; | 
|---|
|  | 424 | throw new Exception($new_errors); | 
|---|
|  | 425 | } | 
|---|
| [2566] | 426 |  | 
|---|
| [3730] | 427 | files::deltree($destination); | 
|---|
|  | 428 | } catch (Exception $e) { | 
|---|
|  | 429 | $zip->close(); | 
|---|
|  | 430 | unlink($zip_file); | 
|---|
|  | 431 | files::deltree($destination); | 
|---|
|  | 432 | throw new Exception($e->getMessage()); | 
|---|
|  | 433 | } | 
|---|
|  | 434 | } else { | 
|---|
|  | 435 | # test for update | 
|---|
|  | 436 | $sandbox = clone $modules; | 
|---|
|  | 437 | $zip->unzip($define, $target . '/_define.php'); | 
|---|
| [2566] | 438 |  | 
|---|
| [3730] | 439 | $sandbox->resetModulesList(); | 
|---|
|  | 440 | $sandbox->requireDefine($target, basename($destination)); | 
|---|
|  | 441 | unlink($target . '/_define.php'); | 
|---|
|  | 442 | $new_modules = $sandbox->getModules(); | 
|---|
| [2566] | 443 |  | 
|---|
| [3730] | 444 | if (!empty($new_modules)) { | 
|---|
|  | 445 | $tmp        = array_keys($new_modules); | 
|---|
|  | 446 | $id         = $tmp[0]; | 
|---|
|  | 447 | $cur_module = $modules->getModules($id); | 
|---|
|  | 448 | if (!empty($cur_module) && (defined('DC_DEV') && DC_DEV === true || dcUtils::versionsCompare($new_modules[$id]['version'], $cur_module['version'], '>', true))) { | 
|---|
|  | 449 | # delete old module | 
|---|
|  | 450 | if (!files::deltree($destination)) { | 
|---|
|  | 451 | throw new Exception(__('An error occurred during module deletion.')); | 
|---|
|  | 452 | } | 
|---|
|  | 453 | $ret_code = 2; | 
|---|
|  | 454 | } else { | 
|---|
|  | 455 | $zip->close(); | 
|---|
|  | 456 | unlink($zip_file); | 
|---|
|  | 457 | throw new Exception(sprintf(__('Unable to upgrade "%s". (older or same version)'), basename($destination))); | 
|---|
|  | 458 | } | 
|---|
|  | 459 | } else { | 
|---|
|  | 460 | $zip->close(); | 
|---|
|  | 461 | unlink($zip_file); | 
|---|
|  | 462 | throw new Exception(sprintf(__('Unable to read new _define.php file'))); | 
|---|
|  | 463 | } | 
|---|
|  | 464 | } | 
|---|
|  | 465 | $zip->unzipAll($target); | 
|---|
|  | 466 | $zip->close(); | 
|---|
|  | 467 | unlink($zip_file); | 
|---|
|  | 468 | return $ret_code; | 
|---|
|  | 469 | } | 
|---|
| [2566] | 470 |  | 
|---|
| [3730] | 471 | /** | 
|---|
|  | 472 | This method installs all modules having a _install file. | 
|---|
| [2566] | 473 |  | 
|---|
| [3730] | 474 | @see dcModules::installModule | 
|---|
|  | 475 | */ | 
|---|
|  | 476 | public function installModules() | 
|---|
|  | 477 | { | 
|---|
| [3874] | 478 | $res = ['success' => [], 'failure' => []]; | 
|---|
| [3730] | 479 | foreach ($this->modules as $id => &$m) { | 
|---|
|  | 480 | $i = $this->installModule($id, $msg); | 
|---|
|  | 481 | if ($i === true) { | 
|---|
|  | 482 | $res['success'][$id] = true; | 
|---|
|  | 483 | } elseif ($i === false) { | 
|---|
|  | 484 | $res['failure'][$id] = $msg; | 
|---|
|  | 485 | } | 
|---|
|  | 486 | } | 
|---|
| [2566] | 487 |  | 
|---|
| [3730] | 488 | return $res; | 
|---|
|  | 489 | } | 
|---|
| [2566] | 490 |  | 
|---|
| [3730] | 491 | /** | 
|---|
|  | 492 | This method installs module with ID <var>$id</var> and having a _install | 
|---|
|  | 493 | file. This file should throw exception on failure or true if it installs | 
|---|
|  | 494 | successfully. | 
|---|
| [2566] | 495 |  | 
|---|
| [3730] | 496 | <var>$msg</var> is an out parameter that handle installer message. | 
|---|
| [2566] | 497 |  | 
|---|
| [3730] | 498 | @param    id        <b>string</b>        Module ID | 
|---|
|  | 499 | @param    msg        <b>string</b>        Module installer message | 
|---|
|  | 500 | @return    <b>boolean</b> | 
|---|
|  | 501 | */ | 
|---|
|  | 502 | public function installModule($id, &$msg) | 
|---|
|  | 503 | { | 
|---|
|  | 504 | try { | 
|---|
|  | 505 | $i = $this->loadModuleFile($this->modules[$id]['root'] . '/_install.php'); | 
|---|
|  | 506 | if ($i === true) { | 
|---|
|  | 507 | return true; | 
|---|
|  | 508 | } | 
|---|
|  | 509 | } catch (Exception $e) { | 
|---|
|  | 510 | $msg = $e->getMessage(); | 
|---|
|  | 511 | return false; | 
|---|
|  | 512 | } | 
|---|
| [2566] | 513 |  | 
|---|
| [3730] | 514 | return; | 
|---|
|  | 515 | } | 
|---|
| [2566] | 516 |  | 
|---|
| [3730] | 517 | public function deleteModule($id, $disabled = false) | 
|---|
|  | 518 | { | 
|---|
|  | 519 | if ($disabled) { | 
|---|
|  | 520 | $p = &$this->disabled; | 
|---|
|  | 521 | } else { | 
|---|
|  | 522 | $p = &$this->modules; | 
|---|
|  | 523 | } | 
|---|
| [2566] | 524 |  | 
|---|
| [3730] | 525 | if (!isset($p[$id])) { | 
|---|
|  | 526 | throw new Exception(__('No such module.')); | 
|---|
|  | 527 | } | 
|---|
| [2566] | 528 |  | 
|---|
| [3730] | 529 | if (!files::deltree($p[$id]['root'])) { | 
|---|
|  | 530 | throw new Exception(__('Cannot remove module files')); | 
|---|
|  | 531 | } | 
|---|
|  | 532 | } | 
|---|
| [2566] | 533 |  | 
|---|
| [3730] | 534 | public function deactivateModule($id) | 
|---|
|  | 535 | { | 
|---|
|  | 536 | if (!isset($this->modules[$id])) { | 
|---|
|  | 537 | throw new Exception(__('No such module.')); | 
|---|
|  | 538 | } | 
|---|
| [2566] | 539 |  | 
|---|
| [3730] | 540 | if (!$this->modules[$id]['root_writable']) { | 
|---|
|  | 541 | throw new Exception(__('Cannot deactivate plugin.')); | 
|---|
|  | 542 | } | 
|---|
| [2566] | 543 |  | 
|---|
| [3730] | 544 | if (@file_put_contents($this->modules[$id]['root'] . '/_disabled', '')) { | 
|---|
|  | 545 | throw new Exception(__('Cannot deactivate plugin.')); | 
|---|
|  | 546 | } | 
|---|
|  | 547 | } | 
|---|
| [2566] | 548 |  | 
|---|
| [3730] | 549 | public function activateModule($id) | 
|---|
|  | 550 | { | 
|---|
|  | 551 | if (!isset($this->disabled[$id])) { | 
|---|
|  | 552 | throw new Exception(__('No such module.')); | 
|---|
|  | 553 | } | 
|---|
| [2566] | 554 |  | 
|---|
| [3730] | 555 | if (!$this->disabled[$id]['root_writable']) { | 
|---|
|  | 556 | throw new Exception(__('Cannot activate plugin.')); | 
|---|
|  | 557 | } | 
|---|
| [2566] | 558 |  | 
|---|
| [3730] | 559 | if (@unlink($this->disabled[$id]['root'] . '/_disabled') === false) { | 
|---|
|  | 560 | throw new Exception(__('Cannot activate plugin.')); | 
|---|
|  | 561 | } | 
|---|
|  | 562 | } | 
|---|
| [2566] | 563 |  | 
|---|
| [3730] | 564 | /** | 
|---|
|  | 565 | This method will search for file <var>$file</var> in language | 
|---|
|  | 566 | <var>$lang</var> for module <var>$id</var>. | 
|---|
| [2566] | 567 |  | 
|---|
| [3730] | 568 | <var>$file</var> should not have any extension. | 
|---|
| [2566] | 569 |  | 
|---|
| [3730] | 570 | @param    id        <b>string</b>        Module ID | 
|---|
|  | 571 | @param    lang        <b>string</b>        Language code | 
|---|
|  | 572 | @param    file        <b>string</b>        File name (without extension) | 
|---|
|  | 573 | */ | 
|---|
|  | 574 | public function loadModuleL10N($id, $lang, $file) | 
|---|
|  | 575 | { | 
|---|
|  | 576 | if (!$lang || !isset($this->modules[$id])) { | 
|---|
|  | 577 | return; | 
|---|
|  | 578 | } | 
|---|
| [2566] | 579 |  | 
|---|
| [3730] | 580 | $lfile = $this->modules[$id]['root'] . '/locales/%s/%s'; | 
|---|
|  | 581 | if (l10n::set(sprintf($lfile, $lang, $file)) === false && $lang != 'en') { | 
|---|
|  | 582 | l10n::set(sprintf($lfile, 'en', $file)); | 
|---|
|  | 583 | } | 
|---|
|  | 584 | } | 
|---|
| [2566] | 585 |  | 
|---|
| [3730] | 586 | public function loadModuleL10Nresources($id, $lang) | 
|---|
|  | 587 | { | 
|---|
|  | 588 | if (!$lang || !isset($this->modules[$id])) { | 
|---|
|  | 589 | return; | 
|---|
|  | 590 | } | 
|---|
| [2566] | 591 |  | 
|---|
| [3730] | 592 | $f = l10n::getFilePath($this->modules[$id]['root'] . '/locales', 'resources.php', $lang); | 
|---|
|  | 593 | if ($f) { | 
|---|
|  | 594 | $this->loadModuleFile($f); | 
|---|
|  | 595 | } | 
|---|
|  | 596 | } | 
|---|
| [2566] | 597 |  | 
|---|
| [3730] | 598 | /** | 
|---|
|  | 599 | Returns all modules associative array or only one module if <var>$id</var> | 
|---|
|  | 600 | is present. | 
|---|
| [2566] | 601 |  | 
|---|
| [3730] | 602 | @param    id        <b>string</b>        Optionnal module ID | 
|---|
|  | 603 | @return    <b>array</b> | 
|---|
|  | 604 | */ | 
|---|
|  | 605 | public function getModules($id = null) | 
|---|
|  | 606 | { | 
|---|
|  | 607 | if ($id && isset($this->modules[$id])) { | 
|---|
|  | 608 | return $this->modules[$id]; | 
|---|
|  | 609 | } | 
|---|
|  | 610 | return $this->modules; | 
|---|
|  | 611 | } | 
|---|
| [2566] | 612 |  | 
|---|
| [3730] | 613 | /** | 
|---|
|  | 614 | Returns true if the module with ID <var>$id</var> exists. | 
|---|
| [2566] | 615 |  | 
|---|
| [3730] | 616 | @param    id        <b>string</b>        Module ID | 
|---|
|  | 617 | @return    <b>boolean</b> | 
|---|
|  | 618 | */ | 
|---|
|  | 619 | public function moduleExists($id) | 
|---|
|  | 620 | { | 
|---|
|  | 621 | return isset($this->modules[$id]); | 
|---|
|  | 622 | } | 
|---|
| [2566] | 623 |  | 
|---|
| [3730] | 624 | /** | 
|---|
|  | 625 | Returns all disabled modules in an array | 
|---|
| [2566] | 626 |  | 
|---|
| [3730] | 627 | @return    <b>array</b> | 
|---|
|  | 628 | */ | 
|---|
|  | 629 | public function getDisabledModules() | 
|---|
|  | 630 | { | 
|---|
|  | 631 | return $this->disabled; | 
|---|
|  | 632 | } | 
|---|
| [2566] | 633 |  | 
|---|
| [3730] | 634 | /** | 
|---|
|  | 635 | Returns root path for module with ID <var>$id</var>. | 
|---|
| [2566] | 636 |  | 
|---|
| [3730] | 637 | @param    id        <b>string</b>        Module ID | 
|---|
|  | 638 | @return    <b>string</b> | 
|---|
|  | 639 | */ | 
|---|
|  | 640 | public function moduleRoot($id) | 
|---|
|  | 641 | { | 
|---|
|  | 642 | return $this->moduleInfo($id, 'root'); | 
|---|
|  | 643 | } | 
|---|
| [2566] | 644 |  | 
|---|
| [3730] | 645 | /** | 
|---|
|  | 646 | Returns a module information that could be: | 
|---|
|  | 647 | - root | 
|---|
|  | 648 | - name | 
|---|
|  | 649 | - desc | 
|---|
|  | 650 | - author | 
|---|
|  | 651 | - version | 
|---|
|  | 652 | - permissions | 
|---|
|  | 653 | - priority | 
|---|
| [2566] | 654 |  | 
|---|
| [3730] | 655 | @param    id        <b>string</b>        Module ID | 
|---|
|  | 656 | @param    info        <b>string</b>        Information to retrieve | 
|---|
|  | 657 | @return    <b>string</b> | 
|---|
|  | 658 | */ | 
|---|
|  | 659 | public function moduleInfo($id, $info) | 
|---|
|  | 660 | { | 
|---|
|  | 661 | return isset($this->modules[$id][$info]) ? $this->modules[$id][$info] : null; | 
|---|
|  | 662 | } | 
|---|
| [2566] | 663 |  | 
|---|
| [3730] | 664 | /** | 
|---|
|  | 665 | Loads namespace <var>$ns</var> specific files for all modules. | 
|---|
| [2566] | 666 |  | 
|---|
| [3730] | 667 | @param    ns        <b>string</b>        Namespace name | 
|---|
|  | 668 | */ | 
|---|
|  | 669 | public function loadNsFiles($ns = null) | 
|---|
|  | 670 | { | 
|---|
|  | 671 | foreach ($this->modules as $k => $v) { | 
|---|
|  | 672 | $this->loadNsFile($k, $ns); | 
|---|
|  | 673 | } | 
|---|
|  | 674 | } | 
|---|
| [2566] | 675 |  | 
|---|
| [3730] | 676 | /** | 
|---|
|  | 677 | Loads namespace <var>$ns</var> specific file for module with ID | 
|---|
|  | 678 | <var>$id</var> | 
|---|
| [2566] | 679 |  | 
|---|
| [3730] | 680 | @param    id        <b>string</b>        Module ID | 
|---|
|  | 681 | @param    ns        <b>string</b>        Namespace name | 
|---|
|  | 682 | */ | 
|---|
|  | 683 | public function loadNsFile($id, $ns = null) | 
|---|
|  | 684 | { | 
|---|
|  | 685 | switch ($ns) { | 
|---|
|  | 686 | case 'admin': | 
|---|
|  | 687 | $this->loadModuleFile($this->modules[$id]['root'] . '/_admin.php'); | 
|---|
|  | 688 | break; | 
|---|
|  | 689 | case 'public': | 
|---|
|  | 690 | $this->loadModuleFile($this->modules[$id]['root'] . '/_public.php'); | 
|---|
|  | 691 | break; | 
|---|
|  | 692 | case 'xmlrpc': | 
|---|
|  | 693 | $this->loadModuleFile($this->modules[$id]['root'] . '/_xmlrpc.php'); | 
|---|
|  | 694 | break; | 
|---|
|  | 695 | } | 
|---|
|  | 696 | } | 
|---|
| [2566] | 697 |  | 
|---|
| [3730] | 698 | public function getErrors() | 
|---|
|  | 699 | { | 
|---|
|  | 700 | return $this->errors; | 
|---|
|  | 701 | } | 
|---|
| [2566] | 702 |  | 
|---|
| [3730] | 703 | protected function loadModuleFile($________, $catch = true) | 
|---|
|  | 704 | { | 
|---|
|  | 705 | if (!file_exists($________)) { | 
|---|
|  | 706 | return; | 
|---|
|  | 707 | } | 
|---|
| [2566] | 708 |  | 
|---|
| [3730] | 709 | self::$_k = array_keys($GLOBALS); | 
|---|
| [2566] | 710 |  | 
|---|
| [3730] | 711 | foreach (self::$_k as self::$_n) { | 
|---|
|  | 712 | if (!in_array(self::$_n, self::$superglobals)) { | 
|---|
|  | 713 | global ${self::$_n}; | 
|---|
|  | 714 | } | 
|---|
|  | 715 | } | 
|---|
| [2566] | 716 |  | 
|---|
| [3730] | 717 | if ($catch) { | 
|---|
|  | 718 | // Catch ouput to prevents hacked or corrupted modules | 
|---|
|  | 719 | ob_start(); | 
|---|
|  | 720 | $ret = require $________; | 
|---|
|  | 721 | ob_end_clean(); | 
|---|
|  | 722 | return $ret; | 
|---|
|  | 723 | } | 
|---|
| [3395] | 724 |  | 
|---|
| [3730] | 725 | return require $________; | 
|---|
|  | 726 | } | 
|---|
| [2566] | 727 |  | 
|---|
| [3730] | 728 | private function sortModules($a, $b) | 
|---|
|  | 729 | { | 
|---|
|  | 730 | if ($a['priority'] == $b['priority']) { | 
|---|
|  | 731 | return strcasecmp($a['name'], $b['name']); | 
|---|
|  | 732 | } | 
|---|
| [2566] | 733 |  | 
|---|
| [3730] | 734 | return ($a['priority'] < $b['priority']) ? -1 : 1; | 
|---|
|  | 735 | } | 
|---|
| [0] | 736 | } | 
|---|