dcCore dcCore instance /** Object constructor. @param core dcCore dcCore instance */ public function __construct($core) { $this->core =& $core; } /** * Checks all modules dependencies * Fills in the following information in module : * * cannot_enable : list reasons why module cannot be enabled. Not set if module can be enabled * * cannot_disable : list reasons why module cannot be disabled. Not set if module can be disabled * * implies : reverse dependencies * @return array list of enabled modules with unmet dependencies, and that must be disabled. */ public function checkDependencies() { $dc_version = preg_replace('/\-dev$/','',DC_VERSION); $this->to_disable = array(); foreach ($this->all_modules as $k => &$m) { if (isset($m['requires'])) { $missing = array(); foreach ($m['requires'] as &$dep) { if (!is_array($dep)) { $dep = array($dep); } // grab missing dependencies if (!isset($this->all_modules[$dep[0]]) && ($dep[0] != 'core')) { // module not present $missing[$dep[0]] = sprintf(__("Requires %s module which is not installed"), $dep[0]); } elseif ((count($dep) > 1) && version_compare(($dep[0] == 'core' ? $dc_version : $this->all_modules[$dep[0]]['version']),$dep[1]) == -1) { // module present, but version missing if ($dep[0] == 'core') { $missing[$dep[0]] = sprintf(__("Requires Dotclear version %s, but version %s is installed"), $dep[1],$dc_version); } else { $missing[$dep[0]] = sprintf(__("Requires %s module version %s, but version %s is installed"), $dep[0],$dep[1],$this->all_modules[$dep[0]]['version']); } } elseif (($dep[0] != 'core') && !$this->all_modules[$dep[0]]['enabled']) { // module disabled $missing[$dep[0]] = sprintf(__("Requires %s module which is disabled"), $dep[0]); } $this->all_modules[$dep[0]]['implies'][]=$k; } if (count($missing)) { $m['cannot_enable']=$missing; if ($m['enabled']) { $this->to_disable[]=array('name' => $k,'reason'=> $missing); } } } } // Check modules that cannot be disabled foreach ($this->modules as $k => &$m) { if (isset($m['implies']) && $m['enabled']) { foreach ($m['implies'] as $im) { if (isset($this->all_modules[$im]) && $this->all_modules[$im]['enabled']) { $m['cannot_disable'][]=$im; } } } } } /** * Checks all modules dependencies, and disable unmet dependencies * @param string $redir_url URL to redirect if modules are to disable * @return boolean, true if a redirection has been performed */ public function disableDepModules($redir_url) { if (isset($_GET['dep'])) { // Avoid infinite redirects return false; } $reason = array(); foreach ($this->to_disable as $module) { try{ $this->deactivateModule($module['name']); $reason[] = sprintf("
  • %s : %s
  • ",$module['name'],join(',',$module['reason'])); } catch (Exception $e) { } } if (count($reason)) { $message = sprintf ("

    %s

    ", __('The following extensions have been disabled :'), join('',$reason) ); dcPage::addWarningNotice($message,array('divtag' => true,'with_ts' => false)); $url = $redir_url.(strpos($redir_url,"?") ? '&' : '?').'dep=1'; http::redirect($url); return true; } return false; } /** Loads modules. $path could be a separated list of paths (path separator depends on your OS). $ns indicates if an additionnal file needs to be loaded on plugin load, value could be: - admin (loads module's _admin.php) - public (loads module's _public.php) - xmlrpc (loads module's _xmlrpc.php) $lang indicates if we need to load a lang file on plugin loading. */ public function loadModules($path,$ns=null,$lang=null) { $this->path = explode(PATH_SEPARATOR,$path); $this->ns = $ns; $disabled = isset($_SESSION['sess_safe_mode']) && $_SESSION['sess_safe_mode']; $disabled = $disabled && !get_parent_class($this) ? true : false; $ignored = array(); foreach ($this->path as $root) { if (!is_dir($root) || !is_readable($root)) { continue; } if (substr($root,-1) != '/') { $root .= '/'; } if (($d = @dir($root)) === false) { continue; } while (($entry = $d->read()) !== false) { $full_entry = $root.$entry; if ($entry != '.' && $entry != '..' && is_dir($full_entry) && file_exists($full_entry.'/_define.php')) { if (!file_exists($full_entry.'/_disabled') && !$disabled) { $this->id = $entry; $this->mroot = $full_entry; require $full_entry.'/_define.php'; $this->all_modules[$entry] =& $this->modules[$entry]; $this->id = null; $this->mroot = null; } else { if (file_exists($full_entry.'/_define.php')) { $this->id = $entry; $this->mroot = $full_entry; $this->disabled_mode=true; require $full_entry.'/_define.php'; $this->disabled_mode=false; $this->disabled[$entry] = $this->disabled_meta; $this->all_modules[$entry] =& $this->disabled[$entry]; $this->id = null; $this->mroot = null; } } } } $d->close(); } $this->checkDependencies(); # Sort plugins uasort($this->modules,array($this,'sortModules')); foreach ($this->modules as $id => $m) { # Load translation and _prepend if (file_exists($m['root'].'/_prepend.php')) { $r = $this->loadModuleFile($m['root'].'/_prepend.php'); # If _prepend.php file returns null (ie. it has a void return statement) if (is_null($r)) { $ignored[] = $id; continue; } unset($r); } $this->loadModuleL10N($id,$lang,'main'); if ($ns == 'admin') { $this->loadModuleL10Nresources($id,$lang); $this->core->adminurl->register('admin.plugin.'.$id,'plugin.php',array('p'=>$id)); } } // Give opportunity to do something before loading context (admin,public,xmlrpc) files $this->core->callBehavior('coreBeforeLoadingNsFiles',$this->core,$this,$lang); foreach ($this->modules as $id => $m) { # If _prepend.php file returns null (ie. it has a void return statement) if (in_array($id,$ignored)) { continue; } # Load ns_file $this->loadNsFile($id,$ns); } } public function requireDefine($dir,$id) { if (file_exists($dir.'/_define.php')) { $this->id = $id; require $dir.'/_define.php'; $this->id = null; } } /** This method registers a module in modules list. You should use this to register a new module. $permissions is a comma separated list of permissions for your module. If $permissions is null, only super admin has access to this module. $priority is an integer. Modules are sorted by priority and name. Lowest priority comes first. @param name string Module name @param desc string Module description @param author string Module author name @param version string Module version @param properties array extra properties (currently available keys : permissions, priority, type) */ public function registerModule($name,$desc,$author,$version, $properties = array()) { if ($this->disabled_mode) { $this->disabled_meta = array_merge( $properties, array( 'root' => $this->mroot, 'name' => $name, 'desc' => $desc, 'author' => $author, 'version' => $version, 'enabled' => false, 'root_writable' => is_writable($this->mroot) ) ); return; } # Fallback to legacy registerModule parameters if (!is_array($properties)) { $args = func_get_args(); $properties = array(); if (isset($args[4])) { $properties['permissions']=$args[4]; } if (isset($args[5])) { $properties['priority']= (integer)$args[5]; } } # Default module properties $properties = array_merge( array( 'permissions' => null, 'priority' => 1000, 'standalone_config' => false, 'type' => null, 'enabled' => true, 'requires' => array() ), $properties ); # Check module type if (self::$type !== null && $properties['type'] !== null && $properties['type'] != self::$type) { $this->errors[] = sprintf( __('Module "%s" has type "%s" that mismatch required module type "%s".'), ''.html::escapeHTML($name).'', ''.html::escapeHTML($properties['type']).'', ''.html::escapeHTML(self::$type).'' ); return; } # Check module perms on admin side $permissions = $properties['permissions']; if ($this->ns == 'admin') { if ($permissions == '' && !$this->core->auth->isSuperAdmin()) { return; } elseif (!$this->core->auth->check($permissions,$this->core->blog->id)) { return; } } # Check module install on multiple path if ($this->id) { $module_exists = array_key_exists($name,$this->modules_names); $module_overwrite = $module_exists ? version_compare($this->modules_names[$name],$version,'<') : false; if (!$module_exists || ($module_exists && $module_overwrite)) { $this->modules_names[$name] = $version; $this->modules[$this->id] = array_merge( $properties, array( 'root' => $this->mroot, 'name' => $name, 'desc' => $desc, 'author' => $author, 'version' => $version, 'root_writable' => is_writable($this->mroot) ) ); } else { $path1 = path::real($this->moduleInfo($name,'root')); $path2 = path::real($this->mroot); $this->errors[] = sprintf( __('Module "%s" is installed twice in "%s" and "%s".'), ''.$name.'', ''.$path1.'', ''.$path2.'' ); } } } public function resetModulesList() { $this->modules = array(); $this->modules_names = array(); $this->errors = array(); } public static function installPackage($zip_file,dcModules &$modules) { $zip = new fileUnzip($zip_file); $zip->getList(false,'#(^|/)(__MACOSX|\.svn|\.hg|\.git|\.DS_Store|\.directory|Thumbs\.db)(/|$)#'); $zip_root_dir = $zip->getRootDir(); $define = ''; if ($zip_root_dir != false) { $target = dirname($zip_file); $destination = $target.'/'.$zip_root_dir; $define = $zip_root_dir.'/_define.php'; $has_define = $zip->hasFile($define); } else { $target = dirname($zip_file).'/'.preg_replace('/\.([^.]+)$/','',basename($zip_file)); $destination = $target; $define = '_define.php'; $has_define = $zip->hasFile($define); } if ($zip->isEmpty()) { $zip->close(); unlink($zip_file); throw new Exception(__('Empty module zip file.')); } if (!$has_define) { $zip->close(); unlink($zip_file); throw new Exception(__('The zip file does not appear to be a valid Dotclear module.')); } $ret_code = 1; if (!is_dir($destination)) { try { files::makeDir($destination,true); $sandbox = clone $modules; $zip->unzip($define, $target.'/_define.php'); $sandbox->resetModulesList(); $sandbox->requireDefine($target,basename($destination)); unlink($target.'/_define.php'); $new_errors = $sandbox->getErrors(); if (!empty($new_errors)) { $new_errors = is_array($new_errors) ? implode(" \n",$new_errors) : $new_errors; throw new Exception($new_errors); } files::deltree($destination); } catch(Exception $e) { $zip->close(); unlink($zip_file); files::deltree($destination); throw new Exception($e->getMessage()); } } else { # test for update $sandbox = clone $modules; $zip->unzip($define, $target.'/_define.php'); $sandbox->resetModulesList(); $sandbox->requireDefine($target,basename($destination)); unlink($target.'/_define.php'); $new_modules = $sandbox->getModules(); if (!empty($new_modules)) { $tmp = array_keys($new_modules); $id = $tmp[0]; $cur_module = $modules->getModules($id); if (!empty($cur_module) && (defined('DC_DEV') && DC_DEV === true || dcUtils::versionsCompare($new_modules[$id]['version'], $cur_module['version'], '>', true))) { # delete old module if (!files::deltree($destination)) { throw new Exception(__('An error occurred during module deletion.')); } $ret_code = 2; } else { $zip->close(); unlink($zip_file); throw new Exception(sprintf(__('Unable to upgrade "%s". (older or same version)'),basename($destination))); } } else { $zip->close(); unlink($zip_file); throw new Exception(sprintf(__('Unable to read new _define.php file'))); } } $zip->unzipAll($target); $zip->close(); unlink($zip_file); return $ret_code; } /** This method installs all modules having a _install file. @see dcModules::installModule */ public function installModules() { $res = array('success'=>array(),'failure'=>array()); foreach ($this->modules as $id => &$m) { $i = $this->installModule($id,$msg); if ($i === true) { $res['success'][$id] = true; } elseif ($i === false) { $res['failure'][$id] = $msg; } } return $res; } /** This method installs module with ID $id and having a _install file. This file should throw exception on failure or true if it installs successfully. $msg is an out parameter that handle installer message. @param id string Module ID @param msg string Module installer message @return boolean */ public function installModule($id,&$msg) { try { $i = $this->loadModuleFile($this->modules[$id]['root'].'/_install.php'); if ($i === true) { return true; } } catch (Exception $e) { $msg = $e->getMessage(); return false; } return null; } public function deleteModule($id,$disabled=false) { if ($disabled) { $p =& $this->disabled; } else { $p =& $this->modules; } if (!isset($p[$id])) { throw new Exception(__('No such module.')); } if (!files::deltree($p[$id]['root'])) { throw new Exception(__('Cannot remove module files')); } } public function deactivateModule($id) { if (!isset($this->modules[$id])) { throw new Exception(__('No such module.')); } if (!$this->modules[$id]['root_writable']) { throw new Exception(__('Cannot deactivate plugin.')); } if (@file_put_contents($this->modules[$id]['root'].'/_disabled','')) { throw new Exception(__('Cannot deactivate plugin.')); } } public function activateModule($id) { if (!isset($this->disabled[$id])) { throw new Exception(__('No such module.')); } if (!$this->disabled[$id]['root_writable']) { throw new Exception(__('Cannot activate plugin.')); } if (@unlink($this->disabled[$id]['root'].'/_disabled') === false) { throw new Exception(__('Cannot activate plugin.')); } } /** This method will search for file $file in language $lang for module $id. $file should not have any extension. @param id string Module ID @param lang string Language code @param file string File name (without extension) */ public function loadModuleL10N($id,$lang,$file) { if (!$lang || !isset($this->modules[$id])) { return; } $lfile = $this->modules[$id]['root'].'/locales/%s/%s'; if (l10n::set(sprintf($lfile,$lang,$file)) === false && $lang != 'en') { l10n::set(sprintf($lfile,'en',$file)); } } public function loadModuleL10Nresources($id,$lang) { if (!$lang || !isset($this->modules[$id])) { return; } $f = l10n::getFilePath($this->modules[$id]['root'].'/locales','resources.php',$lang); if ($f) { $this->loadModuleFile($f); } } /** Returns all modules associative array or only one module if $id is present. @param id string Optionnal module ID @return array */ public function getModules($id=null) { if ($id && isset($this->modules[$id])) { return $this->modules[$id]; } return $this->modules; } /** Returns true if the module with ID $id exists. @param id string Module ID @return boolean */ public function moduleExists($id) { return isset($this->modules[$id]); } /** Returns all disabled modules in an array @return array */ public function getDisabledModules() { return $this->disabled; } /** Returns root path for module with ID $id. @param id string Module ID @return string */ public function moduleRoot($id) { return $this->moduleInfo($id,'root'); } /** Returns a module information that could be: - root - name - desc - author - version - permissions - priority @param id string Module ID @param info string Information to retrieve @return string */ public function moduleInfo($id,$info) { return isset($this->modules[$id][$info]) ? $this->modules[$id][$info] : null; } /** Loads namespace $ns specific files for all modules. @param ns string Namespace name */ public function loadNsFiles($ns=null) { foreach ($this->modules as $k => $v) { $this->loadNsFile($k,$ns); } } /** Loads namespace $ns specific file for module with ID $id @param id string Module ID @param ns string Namespace name */ public function loadNsFile($id,$ns=null) { switch ($ns) { case 'admin': $this->loadModuleFile($this->modules[$id]['root'].'/_admin.php'); break; case 'public': $this->loadModuleFile($this->modules[$id]['root'].'/_public.php'); break; case 'xmlrpc': $this->loadModuleFile($this->modules[$id]['root'].'/_xmlrpc.php'); break; } } public function getErrors() { return $this->errors; } protected function loadModuleFile($________) { if (!file_exists($________)) { return; } self::$_k = array_keys($GLOBALS); foreach (self::$_k as self::$_n) { if (!in_array(self::$_n,self::$superglobals)) { global ${self::$_n}; } } return require $________; } private function sortModules($a,$b) { if ($a['priority'] == $b['priority']) { return strcasecmp($a['name'],$b['name']); } return ($a['priority'] < $b['priority']) ? -1 : 1; } }