From af14c8757206b450ef4accaf8d3de1c623cb4133 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Sun, 23 Mar 2014 00:22:14 +0100 Subject: [PATCH] Added new Plugin and Permissions stuff :D --- src/PocketMine/PluginAPI.php | 220 +-------- src/PocketMine/Server.php | 27 +- src/PocketMine/ServerAPI.php | 9 +- src/PocketMine/network/query/QueryHandler.php | 5 +- src/PocketMine/permission/Permissible.php | 60 +++ src/PocketMine/permission/Permission.php | 130 ++++++ .../ServerOperator.php} | 39 +- src/PocketMine/plugin/Plugin.php | 76 +++ src/PocketMine/plugin/PluginBase.php | 202 ++++++++ src/PocketMine/plugin/PluginDescription.php | 207 +++++++++ src/PocketMine/plugin/PluginLoadOrder.php | 41 ++ src/PocketMine/plugin/PluginLoader.php | 57 +++ src/PocketMine/plugin/PluginManager.php | 439 ++++++++++++++++++ 13 files changed, 1255 insertions(+), 257 deletions(-) create mode 100644 src/PocketMine/permission/Permissible.php create mode 100644 src/PocketMine/permission/Permission.php rename src/PocketMine/{Deprecation.php => permission/ServerOperator.php} (61%) create mode 100644 src/PocketMine/plugin/Plugin.php create mode 100644 src/PocketMine/plugin/PluginBase.php create mode 100644 src/PocketMine/plugin/PluginDescription.php create mode 100644 src/PocketMine/plugin/PluginLoadOrder.php create mode 100644 src/PocketMine/plugin/PluginLoader.php create mode 100644 src/PocketMine/plugin/PluginManager.php diff --git a/src/PocketMine/PluginAPI.php b/src/PocketMine/PluginAPI.php index 49c79d45a..33a4dd9bd 100644 --- a/src/PocketMine/PluginAPI.php +++ b/src/PocketMine/PluginAPI.php @@ -22,19 +22,13 @@ namespace PocketMine; use PocketMine\Network\Protocol\Info; -use PocketMine\PMF\Plugin as PMFPlugin; -use PocketMine\Utils\Config; -use PocketMine\Utils\TextFormat; -use PocketMine\Utils\Utils; +use PocketMine\Plugin\PluginManager; class PluginAPI extends \stdClass{ private $server; - private $plugins = array(); - private $randomNonce; public function __construct(){ $this->server = ServerAPI::request(); - $this->randomNonce = Utils::getRandomBytes(16, false); $this->server->api->console->register("plugins", "", array($this, "commandHandler")); $this->server->api->console->register("version", "", array($this, "commandHandler")); $this->server->api->ban->cmdWhitelist("version"); @@ -45,13 +39,14 @@ class PluginAPI extends \stdClass{ switch($cmd){ case "plugins": $output = "Plugins: "; - foreach($this->getList() as $plugin){ - $output .= $plugin["name"] . ": " . $plugin["version"] . ", "; + foreach(PluginManager::getPlugins() as $plugin){ + $d = $plugin->getDescription(); + $output .= $d->getName() . ": " . $d->getVersion() . ", "; } $output = $output === "Plugins: " ? "No plugins installed.\n" : substr($output, 0, -2) . "\n"; break; case "version": - $output = "PocketMine-MP " . VERSION . " 「" . CODENAME . "」 API #" . API_VERSION . " for Minecraft: PE " . MINECRAFT_VERSION . " protocol #" . Info::CURRENT_PROTOCOL; + $output = "PocketMine-MP " . \PocketMine\VERSION . " 「" . \PocketMine\CODENAME . "」 API #" . \PocketMine\API_VERSION . " for Minecraft: PE " . \PocketMine\MINECRAFT_VERSION . " protocol #" . Info::CURRENT_PROTOCOL; if(\PocketMine\GIT_COMMIT !== str_repeat("00", 20)){ $output .= " (git " . \PocketMine\GIT_COMMIT . ")"; } @@ -63,210 +58,9 @@ class PluginAPI extends \stdClass{ } public function __destruct(){ - foreach($this->plugins as $p){ - $p[0]->__destruct(); - } - unset($plugins); - } - - public function getList(){ - $list = array(); - foreach($this->plugins as $p){ - $list[] = $p[1]; - } - - return $list; - } - - public function getAll(){ - return $this->plugins; - } - - public function load($file){ - if(is_link($file) or is_dir($file) or !file_exists($file)){ - console("[ERROR] " . basename($file) . " is not a file"); - - return false; - } - if(strtolower(substr($file, -3)) === "pmf"){ - $pmf = new PMFPlugin($file); - $info = $pmf->getPluginInfo(); - } else{ - $content = file_get_contents($file); - $info = strstr($content, "*/", true); - $content = str_repeat(PHP_EOL, substr_count($info, "\n")) . substr(strstr($content, "*/"), 2); - if(preg_match_all('#([a-zA-Z0-9\-_]*)=([^\r\n]*)#u', $info, $matches) == 0){ //false or 0 matches - console("[ERROR] Failed parsing of " . basename($file)); - - return false; - } - $info = array(); - foreach($matches[1] as $k => $i){ - $v = $matches[2][$k]; - switch(strtolower($v)){ - case "on": - case "true": - case "yes": - $v = true; - break; - case "off": - case "false": - case "no": - $v = false; - break; - } - $info[$i] = $v; - } - $info["code"] = $content; - $info["class"] = trim(strtolower($info["class"])); - } - if(!isset($info["name"]) or !isset($info["version"]) or !isset($info["class"]) or !isset($info["author"])){ - console("[ERROR] Failed parsing of " . basename($file)); - - return false; - } - console("[INFO] Loading plugin \"" . TextFormat::GREEN . $info["name"] . TextFormat::RESET . "\" " . TextFormat::AQUA . $info["version"] . TextFormat::RESET . " by " . TextFormat::AQUA . $info["author"] . TextFormat::RESET); - if($info["class"] !== "none" and class_exists($info["class"])){ - console("[ERROR] Failed loading plugin: class already exists"); - - return false; - } - if(((!isset($pmf) and (include $file) === false) or (isset($pmf) and eval($info["code"]) === false)) and $info["class"] !== "none" and !class_exists($info["class"])){ - console("[ERROR] Failed loading {$info['name']}: evaluation error"); - - return false; - } - - $className = $info["class"]; - $apiversion = array_map("intval", explode(",", (string) $info["apiversion"])); - if(!in_array(API_VERSION, $apiversion)){ - console("[WARNING] Plugin \"" . $info["name"] . "\" may not be compatible with the API (" . $info["apiversion"] . " != " . API_VERSION . ")! It can crash or corrupt the server!"); - } - - $identifier = $this->getIdentifier($info["name"], $info["author"]); - - if($info["class"] !== "none"){ - $object = new $className($this->server->api, false); - if(!($object instanceof Plugin)){ - console("[ERROR] Plugin \"" . $info["name"] . "\" doesn't use the Plugin Interface"); - if(method_exists($object, "__destruct")){ - $object->__destruct(); - } - $object = null; - unset($object); - } else{ - $this->plugins[$identifier] = array($object, $info); - } - } else{ - $this->plugins[$identifier] = array(new DummyPlugin($this->server->api, false), $info); - } - - return true; - } - - public function getIdentifier($name, $author){ - return sha1(trim(strtolower($name)), true) ^ sha1(trim(strtolower($author)), true) ^ sha1($this->randomNonce, true); - } - - public function get($identifier){ - if($identifier instanceof Plugin){ - foreach($this->plugins as $p){ - if($p[0] === $identifier){ - return $p; - } - } - - return false; - } - if(isset($this->plugins[$identifier])){ - return $this->plugins[$identifier]; - } - - return false; - } - - public function pluginsPath(){ - $path = join(DIRECTORY_SEPARATOR, array(\PocketMine\DATA . "plugins", "")); - @mkdir($path); - - return $path; - } - - - public function configPath(Plugin $plugin){ - $p = $this->get($plugin); - $identifier = $this->getIdentifier($p[1]["name"], $p[1]["author"]); - if($p === false){ - return false; - } - $path = $this->pluginsPath() . $p[1]["name"] . DIRECTORY_SEPARATOR; - $this->plugins[$identifier][1]["path"] = $path; - @mkdir($path); - - return $path; - } - - public function createConfig(Plugin $plugin, $default = array()){ - $p = $this->get($plugin); - if($p === false){ - return false; - } - $path = $this->configPath($plugin); - $cnf = new Config($path . "config.yml", Config::YAML, $default); - $cnf->save(); - - return $path; - } - - public function readYAML($file){ - return yaml_parse(preg_replace("#^([ ]*)([a-zA-Z_]{1}[^\:]*)\:#m", "$1\"$2\":", file_get_contents($file))); - } - - public function writeYAML($file, $data){ - return file_put_contents($file, yaml_emit($data, YAML_UTF8_ENCODING)); } public function init(){ - $this->server->event("server.start", array($this, "initAll")); - $this->loadAll(); + } - - private function loadAll(){ - $dir = dir($this->pluginsPath()); - while(false !== ($file = $dir->read())){ - if($file{0} !== "."){ - $ext = strtolower(substr($file, -3)); - if($ext === "php" or $ext === "pmf"){ - $this->load($dir->path . $file); - } - } - } - } - - public function initAll(){ - console("[INFO] Starting plugins..."); - foreach($this->plugins as $p){ - $p[0]->init(); //ARGHHH!!! Plugin loading randomly fails!! - } - } -} - - -interface Plugin{ - public function __construct(ServerAPI $api, $server = false); - - public function init(); - - public function __destruct(); -} - -class DummyPlugin implements Plugin{ - public function __construct(ServerAPI $api, $server = false){ - } - - public function init(){ - } - - public function __destruct(){ - } -} +} \ No newline at end of file diff --git a/src/PocketMine/Server.php b/src/PocketMine/Server.php index fe6ff46ae..baa15e9ec 100644 --- a/src/PocketMine/Server.php +++ b/src/PocketMine/Server.php @@ -31,6 +31,7 @@ use PocketMine\Network\Packet; use PocketMine\Network\Protocol\Info; use PocketMine\Network\RakNet\Info as RakNetInfo; use PocketMine\Network\RakNet\Packet as RakNetPacket; +use PocketMine\Plugin\PluginManager; use PocketMine\Utils\Utils; use PocketMine\Utils\VersionString; @@ -301,12 +302,6 @@ class Server{ public function addHandler($event, callable $callable, $priority = 5){ if(!is_callable($callable)){ return false; - } elseif(isset(Deprecation::$events[$event])){ - $sub = ""; - if(Deprecation::$events[$event] !== false){ - $sub = " Substitute \"" . Deprecation::$events[$event] . "\" found."; - } - console("[ERROR] Event \"$event\" has been deprecated.$sub [Adding handle to " . (is_array($callable) ? get_class($callable[0]) . "::" . $callable[1] : $callable) . "]"); } $priority = (int) $priority; $hnid = $this->handCnt++; @@ -346,12 +341,6 @@ class Server{ break; } } - } elseif(isset(Deprecation::$events[$event])){ - $sub = ""; - if(Deprecation::$events[$event] !== false){ - $sub = " Substitute \"" . Deprecation::$events[$event] . "\" found."; - } - console("[ERROR] Event \"$event\" has been deprecated.$sub [Handler]"); } if($result !== false){ @@ -460,11 +449,11 @@ class Server{ $p["rcon.password"] = "******"; } $dump .= "server.properties: " . var_export($p, true) . "\r\n\r\n\r\n"; - if($this->api->plugin instanceof PluginAPI){ - $plist = $this->api->plugin->getList(); + if(class_exists("PocketMine\\Plugin\\PluginManager", false)){ $dump .= "Loaded plugins:\r\n"; - foreach($plist as $p){ - $dump .= $p["name"] . " " . $p["version"] . " by " . $p["author"] . "\r\n"; + foreach(PluginManager::getPlugins() as $p){ + $d = $p->getDescription(); + $dump .= $d->getName() . " " . $d->getVersion() . " by " . implode(", ", $d->getAuthors()) . "\r\n"; } $dump .= "\r\n\r\n"; } @@ -618,12 +607,6 @@ class Server{ $ev($data, $event); } } - } elseif(isset(Deprecation::$events[$event])){ - $sub = ""; - if(Deprecation::$events[$event] !== false){ - $sub = " Substitute \"" . Deprecation::$events[$event] . "\" found."; - } - console("[ERROR] Event \"$event\" has been deprecated.$sub [Trigger]"); } } diff --git a/src/PocketMine/ServerAPI.php b/src/PocketMine/ServerAPI.php index 091350263..fafd652a5 100644 --- a/src/PocketMine/ServerAPI.php +++ b/src/PocketMine/ServerAPI.php @@ -29,6 +29,8 @@ use PocketMine\Network\Protocol\Info; use PocketMine\Network\Query\QueryHandler; use PocketMine\Network\RCON\RCON; use PocketMine\Network\UPnP\UPnP; +use PocketMine\Plugin\PluginManager; +use PocketMine\PMF\Plugin; use PocketMine\Recipes\Crafting; use PocketMine\Tile\Tile; use PocketMine\Utils\Config; @@ -220,7 +222,7 @@ class ServerAPI{ } } - $this->apiList[] = $this->plugin = new PluginAPI(); + console("[INFO] Loaded ".count(PluginManager::loadPlugins(\PocketMine\DATA . "plugins/")). " plugin(s)."); } @@ -270,8 +272,9 @@ class ServerAPI{ public function sendUsage(){ console("[DEBUG] Sending usage data...", true, true, 2); $plist = ""; - foreach($this->plugin->getList() as $p){ - $plist .= str_replace(array(";", ":"), "", $p["name"]) . ":" . str_replace(array(";", ":"), "", $p["version"]) . ";"; + foreach(PluginManager::getPlugins() as $p){ + $d = $p->getDescription(); + $plist .= str_replace(array(";", ":"), "", $d->getName()) . ":" . str_replace(array(";", ":"), "", $d->getVersion()) . ";"; } $this->asyncOperation(ASYNC_CURL_POST, array( diff --git a/src/PocketMine/network/query/QueryHandler.php b/src/PocketMine/network/query/QueryHandler.php index 330b739b5..faa3b4edd 100644 --- a/src/PocketMine/network/query/QueryHandler.php +++ b/src/PocketMine/network/query/QueryHandler.php @@ -59,11 +59,12 @@ class QueryHandler{ public function regenerateInfo(){ $str = ""; $plist = "PocketMine-MP " . PocketMine\VERSION; - $pl = $this->server->api->plugin->getList(); + $pl = PocketMine\Plugin\PluginManager::getPlugins(); if(count($pl) > 0){ $plist .= ":"; foreach($pl as $p){ - $plist .= " " . str_replace(array(";", ":", " "), array("", "", "_"), $p["name"]) . " " . str_replace(array(";", ":", " "), array("", "", "_"), $p["version"]) . ";"; + $d = $p->getDescription(); + $plist .= " " . str_replace(array(";", ":", " "), array("", "", "_"), $d->getName()) . " " . str_replace(array(";", ":", " "), array("", "", "_"), $d->getVersion()) . ";"; } $plist = substr($plist, 0, -1); } diff --git a/src/PocketMine/permission/Permissible.php b/src/PocketMine/permission/Permissible.php new file mode 100644 index 000000000..ce57d51e6 --- /dev/null +++ b/src/PocketMine/permission/Permissible.php @@ -0,0 +1,60 @@ +name = $name; + $this->description = $description !== null ? $description : ""; + $this->defaultValue = $defaultValue !== null ? $defaultValue : self::$DEFAULT_PERMISSION; + $this->children = $children; + + $this->recalculatePermissibles(); + } + + /** + * @return string + */ + public function getName(){ + return $this->name; + } + + /** + * @return array + */ + public function getChildren(){ + return $this->children; + } + + /** + * @return string + */ + public function getDefault(){ + return $this->defaultValue; + } + + /** + * @param string $value + */ + public function setDefault($value){ + if($value !== $this->defaultValue){ + $this->defaultValue = $value; + $this->recalculatePermissibles(); + } + } + + /** + * @return string + */ + public function getDescription(){ + return $this->description; + } + + /** + * @param string $value + */ + public function setDescription($value){ + $this->description = $value; + } + + public function getPermissibles(){ + //TODO: get from plugin manager + //plugin handler -> getPermissionSubscriptions($this->name); + return array(); + } + + public function recalculatePermissibles(){ + //TODO: recalculate + $perms = $this->getPermissibles(); + + //plugin handler -> recalculatePermissionDefaults($this); + + foreach($perms as $p){ + $p->recalculatePermissions(); + } + } + + + public function addParent($name){ + if($name instanceof Permission){ + + }else{ + + } + } + +} \ No newline at end of file diff --git a/src/PocketMine/Deprecation.php b/src/PocketMine/permission/ServerOperator.php similarity index 61% rename from src/PocketMine/Deprecation.php rename to src/PocketMine/permission/ServerOperator.php index f9fcdb31e..794a24d82 100644 --- a/src/PocketMine/Deprecation.php +++ b/src/PocketMine/permission/ServerOperator.php @@ -2,11 +2,11 @@ /* * - * ____ _ _ __ __ _ __ __ ____ - * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \ + * ____ _ _ __ __ _ __ __ ____ + * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) | - * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/ - * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_| + * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/ + * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_| * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -15,23 +15,28 @@ * * @author PocketMine Team * @link http://www.pocketmine.net/ - * + * * */ -//TODO: REMOVE +namespace PocketMine\Permission; -namespace PocketMine; +use PocketMine; -class Deprecation{ - public static $events = array( - "server.tick" => "ServerAPI::schedule()", - "server.time" => "time.change", - "world.block.change" => "block.change", - "block.drop" => "item.drop", - "api.op.check" => "op.check", - "api.player.offline.get" => "player.offline.get", - "api.player.offline.save" => "player.offline.save", - ); +interface ServerOperator{ + /** + * Checks if the current object has operator permissions + * + * @return bool + */ + public function isOp(); + /** + * Sets the operator permission for the current object + * + * @param bool $value + * + * @return void + */ + public function setOp($value); } \ No newline at end of file diff --git a/src/PocketMine/plugin/Plugin.php b/src/PocketMine/plugin/Plugin.php new file mode 100644 index 000000000..f777774cc --- /dev/null +++ b/src/PocketMine/plugin/Plugin.php @@ -0,0 +1,76 @@ +isEnabled === true; + } + + public final function setEnabled($boolean = true){ + if($this->isEnabled !== $boolean){ + $this->isEnabled = $boolean; + if($this->isEnabled === true){ + $this->onEnable(); + }else{ + $this->onDisable(); + } + } + } + + public function onDisable(){ + + } + + public final function isDisabled(){ + return $this->isEnabled === false; + } + + public final function getDataFolder(){ + return $this->dataFolder; + } + + public final function getDescription(){ + return $this->description; + } + + protected final function initialize(PocketMine\Server $server, PluginDescription $description, $dataFolder, $file){ + if($this->initialized === false){ + $this->initialized = true; + $this->server = $server; + $this->description = $description; + $this->dataFolder = $dataFolder; + $this->file = $file; + $this->configFile = $this->dataFolder . "config.yml"; + } + } + + public final function isInitialized(){ + return $this->initialized; + } + + public function onCommand(CommandSender $sender, Command $command, $label, $args){ + return false; + } + + /* TODO + public function getCommand($name){ + $this->get + }*/ + + /** + * Gets an embedded resource on the plugin file. + * + * @param string $filename + * + * @return bool|string Resource data, or false + */ + public function getResource($filename){ + $filename = str_replace("\\", "/", $filename); + if(isset($this->phar) and $this->phar instanceof \Phar){ + //TODO + }else{ + if(file_exists($this->dataFolder . "resources/" . $filename)){ + return file_get_contents($this->dataFolder . "resources/" . $filename); + } + return false; + } + } + + public function saveResource($filename, $replace = false){ + if(trim($filename) === ""){ + return false; + } + + if(($resource = $this->getResource($filename)) === false){ + return false; + } + + $out = $this->dataFolder . $filename; + if(!file_exists($this->dataFolder)){ + @mkdir($this->dataFolder, 0755, true); + } + + if(file_exists($out) and $replace !== true){ + return false; + } + + return @file_put_contents($out, $resource) !== false; + } + + /** + * Returns all the resources incrusted on the plugin + * + * @return string[] + */ + public function getResources(){ + if(!$this->isPhar()){ + $resources = array(); + foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->dataFolder . "resources/")) as $resource){ + $resources[] = $resource; + } + return $resources; + } + } + + public function getConfig(){ + if(!isset($this->config)){ + $this->reloadConfig(); + } + return $this->config; + } + + public function saveConfig(){ + if($this->getConfig()->save() === false){ + console("[SEVERE] Could not save config to ". $this->configFile); + return false; + } + return true; + } + + public function saveDefaultConfig(){ + if(!file_exists($this->configFile)){ + $this->saveResource("config.yml", false); + } + } + + public function reloadConfig(){ + $this->config = new Config($this->configFile); + if(($configStream = $this->getResource("config.yml")) !== false){ + $this->config->setDefaults(yaml_parse(config::fixYAMLIndexes($configStream))); + } + } + + public final function getServer(){ + return $this->dataFolder; + } + + public final function getName() + { + return $this->description->getName(); + } + + protected function getFile(){ + return $this->file; + } + +} \ No newline at end of file diff --git a/src/PocketMine/plugin/PluginDescription.php b/src/PocketMine/plugin/PluginDescription.php new file mode 100644 index 000000000..74db89440 --- /dev/null +++ b/src/PocketMine/plugin/PluginDescription.php @@ -0,0 +1,207 @@ +loadMap(\yaml_parse($yamlString)); + } + + private function loadMap(array $plugin){ + $this->name = preg_replace("[^A-Za-z0-9 _.-]", "", $plugin["name"]); + if($this->name === ""){ + trigger_error("Invalid PluginDescription name", E_USER_WARNING); + return; + } + $this->name = str_replace(" ", "_", $this->name); + $this->version = $plugin["version"]; + $this->main = $plugin["main"]; + $this->api = !is_array($plugin["api"]) ? array($plugin["api"]) : $plugin["api"]; + if(stripos($this->main, "PocketMine\\") === 0){ + trigger_error("Invalid PluginDescription main, cannot start within the PocketMine namespace", E_USER_ERROR); + return; + } + + //TODO: commands + + if(isset($plugin["depend"])){ + $this->depend = (array) $plugin["depend"]; + } + if(isset($plugin["softdepend"])){ + $this->softDepend = (array) $plugin["softdepend"]; + } + if(isset($plugin["loadbefore"])){ + $this->loadBefore = (array) $plugin["loadbefore"]; + } + + if(isset($plugin["website"])){ + $this->website = $plugin["website"]; + } + if(isset($plugin["description"])){ + $this->description = $plugin["description"]; + } + if(isset($plugin["load"])){ + $order = strtoupper($plugin["load"]); + if(!defined("PocketMine\\Plugin\\PluginLoadOrder::" . $order)){ + trigger_error("Invalid PluginDescription load", E_USER_ERROR); + return; + }else{ + $this->order = constant("PocketMine\\Plugin\\PluginLoadOrder::" . $order); + } + } + + if(isset($plugin["authors"])){ + $this->authors = $plugin["authors"]; + }elseif(isset($plugin["author"])){ + $this->authors = array($plugin["author"]); + }else{ + $this->authors = array(); + } + + //TODO: Do permissions + } + + /** + * @return array + */ + public function getCompatibleApis(){ + return $this->api; + } + + /** + * @return array + */ + public function getAuthors(){ + return $this->authors; + } + + /** + * @return array + */ + public function getCommands(){ + return $this->commands; + } + + /** + * @return array + */ + public function getDepend() + { + return $this->depend; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @return array + */ + public function getLoadBefore() + { + return $this->loadBefore; + } + + /** + * @return string + */ + public function getMain() + { + return $this->main; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return int + */ + public function getOrder() + { + return $this->order; + } + + /** + * @return Permission[] + */ + public function getPermisions() + { + return $this->permissions; + } + + /** + * @return array + */ + public function getSoftDepend() + { + return $this->softDepend; + } + + /** + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * @return string + */ + public function getWebsite() + { + return $this->website; + } +} \ No newline at end of file diff --git a/src/PocketMine/plugin/PluginLoadOrder.php b/src/PocketMine/plugin/PluginLoadOrder.php new file mode 100644 index 000000000..ba68139a1 --- /dev/null +++ b/src/PocketMine/plugin/PluginLoadOrder.php @@ -0,0 +1,41 @@ +getPluginFilters()); + return true; + } + + /** + * @return Plugin[] + */ + public static function getPlugins(){ + return static::$plugins; + } + + /** + * @param string $path + * + * @return Plugin + */ + public static function loadPlugin($path){ + foreach(static::$fileAssociations as $loader){ + if(preg_match($loader[1], basename($file)) > 0){ + $description = $loader[0]->getPluginDescription($path); + if($description instanceof PluginDescription){ + return $loader[0]->loadPlugin($path); + } + } + } + + return null; + } + + /** + * @param $directory + * + * @return Plugin[] + */ + public static function loadPlugins($directory){ + if(is_dir($directory)){ + $plugins = array(); + $loadedPlugins = array(); + $dependencies = array(); + $softDependencies = array(); + foreach(new \IteratorIterator(new \DirectoryIterator($directory)) as $file){ + foreach(static::$fileAssociations as $loader){ + if(preg_match($loader[1], basename($file)) > 0){ + $description = $loader[0]->getPluginDescription($file); + if($description instanceof PluginDescription){ + $name = $description->getName(); + if(stripos($name, "pocketmine") !== false or stripos($name, "minecraft") !== false or stripos($name, "mojang") !== false){ + console("[ERROR] Could not load plugin '".$name."': restricted name"); + continue; + }elseif(strpos($name, " ") !== false){ + console("[WARNING] Plugin '".$name."' uses spaces in its name, this is discouraged"); + } + if(isset($plugins[$name])){ + console("[ERROR] Could not load duplicate plugin '".$name."': plugin exists"); + continue; + } + + $compatible = false; + + //Check multiple dependencies + foreach($description->getCompatibleApis() as $version){ + //Format: majorVersion.minorVersion.patch + $version = array_map("intval", explode(".", $version)); + $apiVersion = array_map("intval", explode(".", PocketMine\API_VERSION)); + + //Completely different API version + if($version[0] !== $apiVersion[0]){ + continue; + } + + //If the plugin requires new API features, being backwards compatible + if($version[1] > $apiVersion[1]){ + continue; + } + + $compatible = true; + break; + } + + if($compatible === false){ + console("[ERROR] Could not load plugin '".$name."': API version not compatible"); + continue; + } + + $plugins[$name] = $file; + + $softDependencies[$name] = (array) $description->getSoftDepend(); + $dependencies[$name] = (array) $description->getDepend(); + + foreach($description->getLoadBefore() as $before){ + if(isset($softDependencies[$before])){ + $softDependencies[$before][] = $name; + }else{ + $softDependencies[$before] = array($name); + } + } + + break; + } + } + } + } + + while(count($plugins) > 0){ + $missingDependency = true; + foreach($plugins as $name => $file){ + if(isset($dependencies[$name])){ + foreach($dependencies[$name] as $key => $dependency){ + if(isset($loadedPlugins[$dependency])){ + unset($dependencies[$name][$key]); + }elseif(!isset($plugins[$dependency])){ + console("[SEVERE] Could not load plugin '".$name."': Unknown dependency"); + break; + } + } + + if(count($dependencies[$name]) === 0){ + unset($dependencies[$name]); + } + } + + if(isset($softDependencies[$name])){ + foreach($softDependencies[$name] as $key => $dependency){ + if(isset($loadedPlugins[$dependency])){ + unset($softDependencies[$name][$key]); + } + } + + if(count($softDependencies[$name]) === 0){ + unset($softDependencies[$name]); + } + } + + if(!isset($dependencies[$name]) and !isset($softDependencies[$name])){ + unset($plugins[$name]); + $missingDependency = false; + if($plugin = static::loadPlugin($file) and $plugin instanceof Plugin){ + $loadedPlugins[$name] = $plugin; + }else{ + console("[SEVERE] Could not load plugin '".$name."'"); + } + } + } + + if($missingDependency === true){ + foreach($plugins as $name => $file){ + if(!isset($dependencies[$name])){ + unset($softDependencies[$name]); + unset($plugins[$name]); + $missingDependency = false; + if($plugin = static::loadPlugin($file) and $plugin instanceof Plugin){ + $loadedPlugins[$name] = $plugin; + }else{ + console("[SEVERE] Could not load plugin '".$name."'"); + } + } + } + + //No plugins loaded :( + if($missingDependency === true){ + foreach($plugins as $name => $file){ + console("[SEVERE] Could not load plugin '".$name."': circular dependency detected"); + } + $plugins = array(); + } + } + } + + return $loadedPlugins; + }else{ + return array(); + } + } + + /** + * @param string $name + * + * @return null|Permission + */ + public static function getPermission($name){ + if(isset(static::$permissions[$name])){ + return static::$permissions[$name]; + } + return null; + } + + /** + * @param Permission $permission + * + * @return bool + */ + public static function addPermission(Permission $permission){ + if(!isset(static::$permissions[$permission->getName()])){ + static::$permissions[$permission->getName()] = $permission; + static::calculatePermissionDefault($permission); + return true; + } + return false; + } + + /** + * @param string|Permission $permission + */ + public function removePermission($permission){ + if($permission instanceof Permission){ + unset(static::$permissions[$permission->getName()]); + }else{ + unset(static::$permissions[$permission]); + } + } + + /** + * @param boolean $op + * + * @return Permission[] + */ + public static function getDefaultPermissions($op){ + if($op === true){ + return static::$defaultPermsOp; + }else{ + return static::$defaultPerms; + } + } + + /** + * @param Permission $permission + */ + public static function recalculatePermissionDefaults(Permission $permission){ + if(isset(static::$permissions[$permission->getName()])){ + unset(static::$defaultPermsOp[$permission->getName()]); + unset(static::$defaultPerms[$permission->getName()]); + static::calculatePermissionDefault($permission); + } + } + + /** + * @param Permission $permission + */ + private static function calculatePermissionDefault(Permission $permission){ + if($permission->getDefault() === Permission::DEFAULT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){ + static::$defaultPermsOp[$permission->getName()] = $permission; + static::dirtyPermissibles(true); + } + + if($permission->getDefault() === Permission::DEFAULT_NOT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){ + static::$defaultPerms[$permission->getName()] = $permission; + static::dirtyPermissibles(false); + } + } + + /** + * @param boolean $op + */ + private static function dirtyPermissibles($op){ + foreach(static::getDefaultPermSubscriptions($op) as $p){ + $p->recalculatePermissions(); + } + } + + /** + * @param string $permission + * @param Permissible $permissible + */ + public static function subscribeToPermission($permission, Permissible $permissible){ + if(!isset(static::$permSubs[$permission])){ + //TODO: Use WeakRef + static::$permSubs[$permission] = array(); + } + static::$permSubs[$permission][spl_object_hash($permissible)] = $permissible; + } + + /** + * @param string $permission + * @param Permissible $permissible + */ + public static function unsubscribeFromPermission($permission, Permissible $permissible){ + if(isset(static::$permSubs[$permission])){ + unset(static::$permSubs[$permission][spl_object_hash($permissible)]); + } + } + + /** + * @param string $permission + * + * @return Permissible[] + */ + public static function getPermissionSubscriptions($permission){ + if(isset(static::$permSubs[$permission])){ + return static::$permSubs[$permission]; + } + return array(); + } + + /** + * @param boolean $op + * @param Permissible $permissible + */ + public static function subscribeToDefaultPerms($op, Permissible $permissible){ + if($op === true){ + static::$defSubsOp[spl_object_hash($permissible)] = $permissible; + }else{ + static::$defSubs[spl_object_hash($permissible)] = $permissible; + } + } + + /** + * @param boolean $op + * @param Permissible $permissible + */ + public static function unsubscribeFromDefaultPerms($op, Permissible $permissible){ + if($op === true){ + unset(static::$defSubsOp[spl_object_hash($permissible)]); + }else{ + unset(static::$defSubs[spl_object_hash($permissible)]); + } + } + + /** + * @param boolean $op + * + * @return Permissible[] + */ + public static function getDefaultPermSubscriptions($op){ + if($op === true){ + return static::$defSubsOp; + }else{ + return static::$defSubs; + } + } + + /** + * @return Permission[] + */ + public static function getPermissions(){ + return static::$permissions; + } + + +} \ No newline at end of file