diff --git a/CHANGELOG.md b/CHANGELOG.md index 32124672..bcee2745 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ * auto detected browser language in select language ### Plugins +* sandbox for plugins, don't install when requirements are not satisfied * allow comments inside plugin json file (php style) * new require options for plugins: (look into example.json) * require database version of the MyAAC schema diff --git a/TODO b/TODO index 50d70d35..899f9117 100644 --- a/TODO +++ b/TODO @@ -2,7 +2,6 @@ 0.* * support duplicated vocation names with different ids - * sandbox for plugins, don't install when requirements are not passed * add changelog management interface * kathrine tickets - show/hide * csrf token protection diff --git a/system/bin/install_plugin.php b/system/bin/install_plugin.php index 27842e22..c727b782 100644 --- a/system/bin/install_plugin.php +++ b/system/bin/install_plugin.php @@ -33,7 +33,7 @@ if(Plugins::install($path_to_file)) { echo 'WARNING: ' . $warning; } - $info = Plugins::getPlugin(); + $info = Plugins::getPluginJson(); echo (isset($info['name']) ? $info['name'] . ' p' : 'P') . 'lugin has been successfully installed.' . PHP_EOL; } else { diff --git a/system/libs/plugins.php b/system/libs/plugins.php index c757cd98..37180385 100644 --- a/system/libs/plugins.php +++ b/system/libs/plugins.php @@ -43,7 +43,7 @@ use Composer\Semver\Semver; class Plugins { private static $warnings = array(); private static $error = null; - private static $plugin = array(); + private static $plugin_json = array(); public static function getHooks() { @@ -60,7 +60,7 @@ class Plugins { $string = file_get_contents(PLUGINS . $filename . '.json'); $string = self::removeComments($string); $plugin = json_decode($string, true); - self::$plugin = $plugin; + self::$plugin_json = $plugin; if ($plugin == null) { self::$warnings[] = 'Cannot load ' . $filename . '.json. File might be not a valid json code.'; continue; @@ -98,257 +98,258 @@ class Plugins { } $zip = new ZipArchive(); - if($zip->open($file)) { - for ($i = 0; $i < $zip->numFiles; $i++) { - $tmp = $zip->getNameIndex($i); - if(pathinfo($tmp, PATHINFO_DIRNAME) == 'plugins' && pathinfo($tmp, PATHINFO_EXTENSION) == 'json') - $json_file = $tmp; - } + if($zip->open($file) !== true) { + self::$error = 'There was a problem with opening zip archive.'; + return false; + } - if(!isset($json_file)) { - self::$error = 'Cannot find plugin info .json file. Installation is discontinued.'; - return false; - } + for ($i = 0; $i < $zip->numFiles; $i++) { + $tmp = $zip->getNameIndex($i); + if(pathinfo($tmp, PATHINFO_DIRNAME) == 'plugins' && pathinfo($tmp, PATHINFO_EXTENSION) == 'json') + $json_file = $tmp; + } - if($zip->extractTo(BASE)) { // place in the directory with same name - $file_name = BASE . $json_file; - if(!file_exists($file_name)) { - self::$error = "Cannot load " . $file_name . ". File doesn't exist."; - return false; - } - else { - $string = file_get_contents($file_name); - $string = self::removeComments($string); - $plugin = json_decode($string, true); - self::$plugin = $plugin; - if ($plugin == null) { - self::$warnings[] = 'Cannot load ' . $file_name . '. File might be not a valid json code.'; - } - else { - $continue = true; - - if(!isset($plugin['name'])) { - self::$warnings[] = 'Plugin "name" tag is not set.'; - } - if(!isset($plugin['description'])) { - self::$warnings[] = 'Plugin "description" tag is not set.'; - } - if(!isset($plugin['version'])) { - self::$warnings[] = 'Plugin "version" tag is not set.'; - } - if(!isset($plugin['author'])) { - self::$warnings[] = 'Plugin "author" tag is not set.'; - } - if(!isset($plugin['contact'])) { - self::$warnings[] = 'Plugin "contact" tag is not set.'; - } - - if(isset($plugin['require'])) { - $require = $plugin['require']; - - $myaac_satified = true; - if(isset($require['myaac_'])) { - $require_myaac = $require['myaac_']; - if(!Semver::satisfies(MYAAC_VERSION, $require_myaac)) { - $myaac_satified = false; - } - } - else if(isset($require['myaac'])) { - $require_myaac = $require['myaac']; - if(version_compare(MYAAC_VERSION, $require_myaac, '<')) { - $myaac_satified = false; - } - } - - if(!$myaac_satified) { - self::$error = "Your AAC version doesn't meet the requirement of this plugin. Required version is: " . $require_myaac . ", and you're using version " . MYAAC_VERSION . "."; - $continue = false; - } - - $php_satified = true; - if(isset($require['php_'])) { - $require_php = $require['php_']; - if(!Semver::satisfies(phpversion(), $require_php)) { - $php_satified = false; - } - } - else if(isset($require['php'])) { - $require_php = $require['php']; - if(version_compare(phpversion(), $require_php, '<')) { - $php_satified = false; - } - } - - if(!$php_satified) { - self::$error = "Your PHP version doesn't meet the requirement of this plugin. Required version is: " . $require_php . ", and you're using version " . phpversion() . "."; - $continue = false; - } - - $database_satified = true; - if(isset($require['database_'])) { - $require_database = $require['database_']; - if(!Semver::satisfies(DATABASE_VERSION, $require_database)) { - $database_satified = false; - } - } - else if(isset($require['database'])) { - $require_database = $require['database']; - if(version_compare(DATABASE_VERSION, $require_database, '<')) { - $database_satified = false; - } - } - - if(!$database_satified) { - self::$error = "Your database version doesn't meet the requirement of this plugin. Required version is: " . $require_database . ", and you're using version " . DATABASE_VERSION . "."; - $continue = false; - } - - if($continue) { - foreach($require as $req => $version) { - if(in_array($req, array('myaac', 'myaac_', 'php', 'php_', 'database', 'database_'))) { - continue; - } - - $req = strtolower($req); - if(in_array($req, array('php-ext', 'php-extension'))) { // require php extension - if(!extension_loaded($version)) { - self::$error = "This plugin requires php extension: " . $version . " to be installed."; - $continue = false; - break; - } - } - else if($req == 'table') { - if(!$db->hasTable($version)) { - self::$error = "This plugin requires table: " . $version . " to exist in the database."; - $continue = false; - break; - } - } - else if($req == 'column') { - $tmp = explode('.', $version); - if(count($tmp) == 2) { - if(!$db->hasColumn($tmp[0], $tmp[1])) { - self::$error = "This plugin requires database column: " . $tmp[0] . "." . $tmp[1] . " to exist in database."; - $continue = false; - break; - } - } - } - else if(strpos($req, 'ext-') !== false) { - $tmp = explode('-', $req); - if(count($tmp) == 2) { - if(!extension_loaded($tmp[1]) || !Semver::satisfies(phpversion($tmp[1]), $version)) { - self::$error = "This plugin requires php extension: " . $tmp[1] . ", version " . $version . " to be installed."; - $continue = false; - break; - } - } - } - else if(!self::is_installed($req, $version)) { - self::$error = "This plugin requires another plugin to run correctly. The another plugin is: " . $req . ", with version " . $version . "."; - $continue = false; - break; - } - } - } - } - - if($continue) { - if (isset($plugin['install'])) { - if (file_exists(BASE . $plugin['install'])) { - $db->revalidateCache(); - require BASE . $plugin['install']; - $db->revalidateCache(); - } - else - self::$warnings[] = 'Cannot load install script. Your plugin might be not working correctly.'; - } - - $cache = Cache::getInstance(); - if($cache->enabled()) { - $cache->delete('templates'); - $cache->delete('hooks'); - $cache->delete('template_menus'); - } - - $zip->close(); - return true; - } - } - } - } - else { - self::$error = 'There was a problem with extracting zip archive.'; - } + if(!isset($json_file)) { + self::$error = 'Cannot find plugin info .json file. Installation is discontinued.'; + return false; + } + $plugin_temp_dir = CACHE . 'plugins/' . str_replace('.zip', '', basename($file)) . '/'; + if(!$zip->extractTo($plugin_temp_dir)) { // place in cache dir + self::$error = 'There was a problem with extracting zip archive to cache directory.'; $zip->close(); + return false; + } + + self::$error = 'There was a problem with extracting zip archive.'; + $file_name = $plugin_temp_dir . $json_file; + if(!file_exists($file_name)) { + self::$error = "Cannot load " . $file_name . ". File doesn't exist."; + $zip->close(); + return false; + } + + $string = file_get_contents($file_name); + $string = self::removeComments($string); + $plugin_json = json_decode($string, true); + self::$plugin_json = $plugin_json; + if ($plugin_json == null) { + self::$warnings[] = 'Cannot load ' . $file_name . '. File might be not a valid json code.'; } else { - self::$error = 'There was a problem with opening zip archive.'; + $continue = true; + + if(!isset($plugin_json['name']) || empty(trim($plugin_json['name']))) { + self::$warnings[] = 'Plugin "name" tag is not set.'; + } + if(!isset($plugin_json['description']) || empty(trim($plugin_json['description']))) { + self::$warnings[] = 'Plugin "description" tag is not set.'; + } + if(!isset($plugin_json['version']) || empty(trim($plugin_json['version']))) { + self::$warnings[] = 'Plugin "version" tag is not set.'; + } + if(!isset($plugin_json['author']) || empty(trim($plugin_json['author']))) { + self::$warnings[] = 'Plugin "author" tag is not set.'; + } + if(!isset($plugin_json['contact']) || empty(trim($plugin_json['contact']))) { + self::$warnings[] = 'Plugin "contact" tag is not set.'; + } + + if(isset($plugin_json['require'])) { + $require = $plugin_json['require']; + + $myaac_satified = true; + if(isset($require['myaac_'])) { + $require_myaac = $require['myaac_']; + if(!Semver::satisfies(MYAAC_VERSION, $require_myaac)) { + $myaac_satified = false; + } + } + else if(isset($require['myaac'])) { + $require_myaac = $require['myaac']; + if(version_compare(MYAAC_VERSION, $require_myaac, '<')) { + $myaac_satified = false; + } + } + + if(!$myaac_satified) { + self::$error = "Your AAC version doesn't meet the requirement of this plugin. Required version is: " . $require_myaac . ", and you're using version " . MYAAC_VERSION . "."; + return false; + } + + $php_satisfied = true; + if(isset($require['php_'])) { + $require_php = $require['php_']; + if(!Semver::satisfies(phpversion(), $require_php)) { + $php_satisfied = false; + } + } + else if(isset($require['php'])) { + $require_php = $require['php']; + if(version_compare(phpversion(), $require_php, '<')) { + $php_satisfied = false; + } + } + + if(!$php_satisfied) { + self::$error = "Your PHP version doesn't meet the requirement of this plugin. Required version is: " . $require_php . ", and you're using version " . phpversion() . "."; + $continue = false; + } + + $database_satisfied = true; + if(isset($require['database_'])) { + $require_database = $require['database_']; + if(!Semver::satisfies(DATABASE_VERSION, $require_database)) { + $database_satisfied = false; + } + } + else if(isset($require['database'])) { + $require_database = $require['database']; + if(version_compare(DATABASE_VERSION, $require_database, '<')) { + $database_satisfied = false; + } + } + + if(!$database_satisfied) { + self::$error = "Your database version doesn't meet the requirement of this plugin. Required version is: " . $require_database . ", and you're using version " . DATABASE_VERSION . "."; + $continue = false; + } + + if($continue) { + foreach($require as $req => $version) { + $req = strtolower(trim($req)); + $version = trim($version); + + if(in_array($req, array('myaac', 'myaac_', 'php', 'php_', 'database', 'database_'))) { + continue; + } + + if(in_array($req, array('php-ext', 'php-extension'))) { // require php extension + if(!extension_loaded($version)) { + self::$error = "This plugin requires php extension: " . $version . " to be installed."; + $continue = false; + break; + } + } + else if($req == 'table') { + if(!$db->hasTable($version)) { + self::$error = "This plugin requires table: " . $version . " to exist in the database."; + $continue = false; + break; + } + } + else if($req == 'column') { + $tmp = explode('.', $version); + if(count($tmp) == 2) { + if(!$db->hasColumn($tmp[0], $tmp[1])) { + self::$error = "This plugin requires database column: " . $tmp[0] . "." . $tmp[1] . " to exist in database."; + $continue = false; + break; + } + } + } + else if(strpos($req, 'ext-') !== false) { + $tmp = explode('-', $req); + if(count($tmp) == 2) { + if(!extension_loaded($tmp[1]) || !Semver::satisfies(phpversion($tmp[1]), $version)) { + self::$error = "This plugin requires php extension: " . $tmp[1] . ", version " . $version . " to be installed."; + $continue = false; + break; + } + } + } + else if(!self::is_installed($req, $version)) { + self::$error = "This plugin requires another plugin to run correctly. The another plugin is: " . $req . ", with version " . $version . "."; + $continue = false; + break; + } + } + } + } + + if($continue) { + if (isset($plugin_json['install'])) { + if (file_exists(BASE . $plugin_json['install'])) { + $db->revalidateCache(); + require BASE . $plugin_json['install']; + $db->revalidateCache(); + } + else + self::$warnings[] = 'Cannot load install script. Your plugin might be not working correctly.'; + } + + if(!$zip->extractTo(BASE)) { // "Real" Install + self::$error = 'There was a problem with extracting zip archive to base directory.'; + $zip->close(); + return false; + } + + $cache = Cache::getInstance(); + if($cache->enabled()) { + $cache->delete('templates'); + $cache->delete('hooks'); + $cache->delete('template_menus'); + } + + return true; + } } return false; } - public static function uninstall($plugin_name) { - global $db; - + public static function uninstall($plugin_name) + { $filename = BASE . 'plugins/' . $plugin_name . '.json'; if(!file_exists($filename)) { self::$error = 'Plugin ' . $plugin_name . ' does not exist.'; return false; } - else { - $string = file_get_contents($filename); - $string = self::removeComments($string); - $plugin_info = json_decode($string, true); - if($plugin_info == false) { - self::$error = 'Cannot load plugin info ' . $plugin_name . '.json'; - return false; + $string = file_get_contents($filename); + $string = self::removeComments($string); + $plugin_info = json_decode($string, true); + if($plugin_info == false) { + self::$error = 'Cannot load plugin info ' . $plugin_name . '.json'; + return false; + } + + if(!isset($plugin_info['uninstall'])) { + self::$error = "Plugin doesn't have uninstall options defined. Skipping..."; + return false; + } + + $success = true; + foreach($plugin_info['uninstall'] as $file) { + if(strpos($file, '/') === 0) { + $success = false; + self::$error = "You cannot use absolute paths (starting with slash - '/'): " . $file; + break; } - else { - if(!isset($plugin_info['uninstall'])) { - self::$error = "Plugin doesn't have uninstall options defined. Skipping..."; - return false; - } - else { - $success = true; - foreach($plugin_info['uninstall'] as $file) { - if(strpos($file, '/') === 0) { - $success = false; - self::$error = "You cannot use absolute paths (starting with slash - '/'): " . $file; - break; - } - $file = str_replace('\\', '/', BASE . $file); - $realpath = str_replace('\\', '/', realpath(dirname($file))); - if(!is_sub_dir($file, BASE) || $realpath != dirname($file)) { - $success = false; - self::$error = "You don't have rights to delete: " . $file; - break; - } - } + $file = str_replace('\\', '/', BASE . $file); + $realpath = str_replace('\\', '/', realpath(dirname($file))); + if(!is_sub_dir($file, BASE) || $realpath != dirname($file)) { + $success = false; + self::$error = "You don't have rights to delete: " . $file; + break; + } + } - if($success) { - foreach($plugin_info['uninstall'] as $file) { - if(!deleteDirectory(BASE . $file)) { - self::$warnings[] = 'Cannot delete: ' . $$file; - } - } - } - - if($success) { - $cache = Cache::getInstance(); - if($cache->enabled()) { - $cache->delete('templates'); - $cache->delete('hooks'); - $cache->delete('template_menus'); - } - - return true; - } + if($success) { + foreach($plugin_info['uninstall'] as $file) { + if(!deleteDirectory(BASE . $file)) { + self::$warnings[] = 'Cannot delete: ' . $file; } } + + $cache = Cache::getInstance(); + if($cache->enabled()) { + $cache->delete('templates'); + $cache->delete('hooks'); + $cache->delete('template_menus'); + } + + return true; } return false; @@ -381,8 +382,8 @@ class Plugins { return self::$error; } - public static function getPlugin() { - return self::$plugin; + public static function getPluginJson() { + return self::$plugin_json; } public static function removeComments($string) { diff --git a/system/pages/admin/plugins.php b/system/pages/admin/plugins.php index e3a19d81..111d0a2b 100644 --- a/system/pages/admin/plugins.php +++ b/system/pages/admin/plugins.php @@ -69,7 +69,7 @@ if (isset($_REQUEST['uninstall'])) { warning($warning); } - $info = Plugins::getPlugin(); + $info = Plugins::getPluginJson(); success((isset($info['name']) ? '' . $info['name'] . ' p' : 'P') . 'lugin has been successfully installed.'); } else { $error = Plugins::getError(); @@ -93,6 +93,7 @@ foreach (get_plugins() as $plugin) { $string = file_get_contents(BASE . 'plugins/' . $plugin . '.json'); $string = Plugins::removeComments($string); $plugin_info = json_decode($string, true); + if ($plugin_info == false) { warning('Cannot load plugin info ' . $plugin . '.json'); } else {