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(),
'settings' => 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;
}
}