From fa24e6b9655857d6767a066ec373a20551de04ed Mon Sep 17 00:00:00 2001 From: alejandroliu Date: Fri, 26 Aug 2016 13:30:17 +0200 Subject: [PATCH] Importing 1.1.0 --- README.md | 71 +++++ media/ZipPlugin-icon.png | Bin 0 -> 3620 bytes plugin.yml | 7 + src/ZipPluginLoader/Dummy.php | 8 + src/ZipPluginLoader/Main.php | 24 ++ src/ZipPluginLoader/MyZipStream.php | 49 ++++ src/ZipPluginLoader/ZipPluginLoader.php | 367 ++++++++++++++++++++++++ 7 files changed, 526 insertions(+) create mode 100644 README.md create mode 100644 media/ZipPlugin-icon.png create mode 100644 plugin.yml create mode 100644 src/ZipPluginLoader/Dummy.php create mode 100644 src/ZipPluginLoader/Main.php create mode 100644 src/ZipPluginLoader/MyZipStream.php create mode 100644 src/ZipPluginLoader/ZipPluginLoader.php diff --git a/README.md b/README.md new file mode 100644 index 0000000..3037acf --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ + + +ZipPluginLoader +=============== + +* Summary: Load Zip packed plugins +* Dependency Plugins: n/a +* PocketMine-MP version: 1.4 - API 1.10.0 +* DependencyPlugins: - +* OptionalPlugins: - +* Categories: Developer Tools +* Plugin Access: Manages plugins +* WebSite: [github](https://github.com/alejandroliu/bad-plugins/tree/master/ZipPluginLoader) + +Overview +-------- + +**NOTE: This is unlike _DevTools_, as it is focus on only loading Zip files +instead of folders like _DevTools_.** You still need _DevTools_ if +you actually want to do Plugin Development and to create _phar_ file +plugins. + +This plugin Let's you load plugins from zip files. This is +particularly handy when trying out source plugins from +[GitHub](http://github.com) as you can click the **Download ZIP** +button, as you can then place the zip file in the plugins folder. + +Essentially you put your plugin code in a zip file, and this plugin +will look for a **plugin.yml** file in there and load the plugin. + +If there are multiple plugins in a zip file, all the plugins will be +loaded by default. You can control what plugins will be loaded by +creating a control file. For example if you have a: + + example.zip + +You need to create a text file called: + + example.ctl + +In this file you list (one plugin per line) the plugins that you want +to load. Any plugins **not** in listed the control file will **not** be +loaded. + +Changes +------- + +* 1.1.0: Multiple plugins + * Change the way error reporting is done... + * Supports for multiple plugins in a zip file. +* 1.0.0: First release + +Copyright +--------- + + ZipPluginLoader + Copyright (C) 2015 Alejandro Liu + All Rights Reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . diff --git a/media/ZipPlugin-icon.png b/media/ZipPlugin-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..201e58cc68212f599f37a566e767d1ff03f8e8ef GIT binary patch literal 3620 zcmZu!3piBk8vYG!lT=eFccy8P`!$ynX)@!oL^LIZDN(z~D!Hf2rqGO(EOeQ~u1q#I zPRi1_R69ztbIqPuF4-!%#kecYoK^cg=h^MfdY=DZ-@l&k```C_zi-{R9$TCi%c;l# z02af}Ocrsbe;H&FF>5V)#|1zdc(`tNAeNG^L-O?`Ay$$v2ae+yhM_2mAP5Y@5Cq`> zU;v;1AOOGsKt$Acmv9_l7(h{gAOMB|^#7c2fMWnd0g3+J8z30|j|dpWBnXPb2vGwRK*+c0e(4cZf+08xV?^a(fDn0p+YK=Iwmt+& zfTI9I0O2kH7!FVjASltcm;|6WK>-jzUynvTk_-THJuq|A_JiFcSJ)xxUdpxE{gwTn zySsu*>WpAZ57*U!mrilxTzouqUftQ2erfP!(Dlf9-W~IoF@@%=>sWw#)zPOH6eHF% zj^CJ0ynw{n>r&IHIX7j<*JYNMK*QSY!@^Gy4Si3p(Mfp=^7_I%)2^Mg>c2iQpH+NP zEURB!nCL#?Kc>6KB36aI_~#!yq<@im34*|Xmt%SCOgaD*0I0rmb1i+pI@}fit7`1| z+KSmd^b1Zo0raoz{T>W|dwlPTcMiM2h}N+gp3S^HLx=%iW! zhZ-Q?eou2@ECen>H2WI;^U@wa?ph5%u2rL5!foB6caK%Iso7qTSJOm9&+7mU66ski z*=SCUkxP?Cnd5RZvcVnJG!N+Co39xsoCP6b1N%ym%ww8G(E39^_FWqF7d}>^Ekc|C z^POPHHfsixS)M-n43NnbdMX)Wm}@cWfV0^?paaula|CTB0qfw8GQ+arwVy3!jAjS= z%5p@u-l_NV^+4<+@^5+Rc*l|FDQp8*g_RDbuIj0)Y0IRfq)Zc8n`ksTN+RhdDA-eD z)6oLGfMipMr<cOr7U4*Q=)pg|&V8`6}f_`o1n)&J;JE^5^xksN@|d zLijfd#r<`)0s%kBk8b6u)BEdPI2y$ z0m^M{#QYDm{ByS_72Y2_QXY}?)Pj9L&WTPTkuR?9_cdRPV}|uS9&g8U(>xYRrAib2Na;s+l)u8PHmK3L~x!OG*^C#^J6tSl{e z=&h_aQL8nTubw!mn{YcQYt-lVE`wDg9CqQ32)8J;v!=HLc|(H4z3DD|j(Xm9y-WK& z)oYs~NaMDRCBt``w;BfFiT`4$uXugq;yk`od*JpF2i|DY?C28p6J;~6?$9|V7WHq( zpUtH*&MskBMvL-tv&`hrglU|6l9SHMKlWfPUxDTkQ6&kN5bR!DvvPNH(@KNu*Adx# zs2F{~u81t$^HAJ$gK^bW`*J<3n^yNm)K}|O=v^At->_KKQtj14$pxEHHD%@Ih%S@l z-ripPa0SwswP8qD+*5UYX2VQpt5A5^g|8IQ9&DLb7u@rp%4oNu>5Jyd%@jc0({d$x zpz)!NWfmv*_ri@oS$1`3^W|I1eT`%?sDSz!^oAh2U>EM%QZsllPp&`<(w^CbhWBABdRJYq}Yl6#$sPVNm*H0 zUS6P|pWoDAo9t4J;3G#g)zs2iLA*bNLSb85#Op_KrH6zcx+<*F>L#bAbdzX&H8r*1 zLx*gl$6oU+4o#2X!mh3^B3**G!73*wr)_yJn{PP@3wZ9dn7#&?(K4HcC6RLEcu1-m6tN-0MLDs#rRC;OjM>#> zCarE?3x+ftBglD{=HRKne=52kardm@JqUvRi}yyy7nPIvc4S8D6j)wFSWKF! zo!u#-``Cjfw@_D;8Vx9o@P_q=eOs5K_|MiOS)1 zj^Sx{Z@`D@g{Y6_XL;ceO;1R}58VvU(Ez{h+>mfflSFwbMcH~NXSYjxa4q*)ma>jN2g(V5)=0E#jCgIsbc2ZR71ylEngcb*5Y2=)ro163QwlB1& zQWJEO9_LUxlcSU8YK)c!ncqDPDKAL?x$Iyj zC&Q&AMew+9N{1YI0)`()DArgQvMKc!Z^)Xl^8K2EMg5n0!n0n#ePhqN8~0A2_3=s! z(@*Hry}XHReUYpba*?CiY*utw^D+q8(N688-~Daqr$tf}1#a)uLZ?dY>|Uqc)h9a- y&!+el1)BShgqD5x)wt1zx&0h{@oR}1kSu5yB|TW;FaGlP2VjRS%p!Zg)Bgc8tky^X literal 0 HcmV?d00001 diff --git a/plugin.yml b/plugin.yml new file mode 100644 index 0000000..8f24c98 --- /dev/null +++ b/plugin.yml @@ -0,0 +1,7 @@ +name: ZipPluginLoader +main: ZipPluginLoader\Main +version: 1.1.0 +api: 1.10.0 +load: STARTUP +author: aliuly +description: Load Zip packed plugins diff --git a/src/ZipPluginLoader/Dummy.php b/src/ZipPluginLoader/Dummy.php new file mode 100644 index 0000000..cca70ca --- /dev/null +++ b/src/ZipPluginLoader/Dummy.php @@ -0,0 +1,8 @@ +getLogger()->error("Unable to register Zip wrapper"); + throw new \RuntimeException("Runtime checks failed"); + return; + } + } + $this->getServer()->getPluginManager()->registerInterface("ZipPluginLoader\\ZipPluginLoader"); + $this->getServer()->getPluginManager()->loadPlugins($this->getServer()->getPluginPath(), ["ZipPluginLoader\\ZipPluginLoader"]); + $this->getServer()->enablePlugins(PluginLoadOrder::STARTUP); + } + public function onDisable() { + if (in_array("myzip",stream_get_wrappers())) { + stream_wrapper_unregister("myzip"); + } + } +} diff --git a/src/ZipPluginLoader/MyZipStream.php b/src/ZipPluginLoader/MyZipStream.php new file mode 100644 index 0000000..7580187 --- /dev/null +++ b/src/ZipPluginLoader/MyZipStream.php @@ -0,0 +1,49 @@ +path = $path; + $zippath = preg_replace('/^myzip:/','zip:',$path); + $this->fp = @fopen($zippath,$mode); + if ($this->fp == false) return false; + return true; + } + public function stream_close() { + fclose($this->fp); + } + public function stream_read($count) { + return fread($this->fp,$count); + } + public function stream_eof() { + return feof($this->fp); + } + public function url_stat($path,$flags) { + $ret = []; + $zippath = preg_replace('/^myzip:\/\//',"",$path); + $parts = explode('#',$zippath,2); + if (count($parts)!=2) return false; + list($zippath,$subfile) = $parts; + $za = new \ZipArchive(); + if ($za->open($zippath) !== true) return false; + $i = $za->locateName($subfile); + if ($i === false) return false; + $zst = $za->statIndex($i); + $za->close(); + unset($za); + foreach([7=>'size', 8=>'mtime',9=>'mtime',10=>'mtime'] as $a=>$b) { + if (!isset($zst[$b])) continue; + $ret[$a] = $zst[$b]; + } + return $ret; + } + public function stream_stat() { + return $this->url_stat($this->path,0); + } +} diff --git a/src/ZipPluginLoader/ZipPluginLoader.php b/src/ZipPluginLoader/ZipPluginLoader.php new file mode 100644 index 0000000..2478552 --- /dev/null +++ b/src/ZipPluginLoader/ZipPluginLoader.php @@ -0,0 +1,367 @@ +server = $server; + } + + /** + * Gets the PluginDescription from the file + * + * @param string $file + * + * @return PluginDescription + */ + public function getPluginDescription($file){//@API + if (substr($file,0,strlen(self::PREFIX)) == self::PREFIX) { + if (substr($file,-strlen(self::CANARY)) == self::CANARY) { + // This is an internal path + $file = substr($file,0,strlen($file)-strlen(self::CANARY)); + } + return $this->myGetPluginDesc($file); + } + $ymls = $this->findFiles($file,"plugin.yml"); + if ($ymls === null) return null; + if (count($ymls) > 1) { + $plugins = $this->check_plugins($file,$ymls); + return $this->getDummyDesc($plugins,$file); + } + return $this->myGetPluginDesc(self::PREFIX.$file."#".$ymls[0]); + } + + /** + * Loads the plugin contained in $file + * + * @param string $file + * + * @return Plugin + */ + public function loadPlugin($file){//@API + if (substr($file,0,strlen(self::PREFIX)) == self::PREFIX) { + if (substr($file,-strlen(self::CANARY)) == self::CANARY) { + // This is an internal path + $file = substr($file,0,strlen($file)-strlen(self::CANARY)); + } + $desc = $this->myGetPluginDesc($file); + $dataFolder=$this->zipdir($file).DIRECTORY_SEPARATOR.$desc->getName(); + $this->server->getLogger()->info(TextFormat::AQUA."Loading zip NESTED plugin " . $desc->getFullName()); + return $this->initPlugin($desc,$dataFolder,$file); + } + $ymls = $this->findFiles($file,"plugin.yml"); + if ($ymls === null) { + $this->server->getLogger()->error(TextFormat::RED."Unable to load zip $file"); + $this->server->getLogger()->error(TextFormat::RED."plugin.yml not found"); + throw new PluginException("Couldn't load plugin"); + return null; + } + if (count($ymls) > 1) { + // Load all the internal plugins + $plugins = $this->check_plugins($file,$ymls); + $this->server->getLogger()->info(TextFormat::AQUA."Loading ". + count($plugins)." plugin(s) from ". + basename($file)); + // Check if we need to do a loadbefore... + foreach (array_keys($plugins) as $p) { + if (isset($plugins[$p]["loadbefore"])) { + foreach ($plugins[$p]["loadbefore"] as $b) { + if (isset($plugins[$b])) { + if (isset($plugins[$b]["softdepend"])) { + $plugins[$b]["softdepend"][] = $p; + } else { + $plugins[$b]["softdepend"] = [$p]; + } + } + } + } + } + + $loaded = []; + while (count($plugins)) { + $cnt = 0; + foreach (array_keys($plugins) as $pname) { + $load = true; + // Check dependancies... + if(isset($plugins[$pname]["depend"])) { + foreach($plugins[$p]["depend"] as $d) { + if (isset($plugins[$d])) { + $load = false; + break; + } + if (isset($loaded[$d])) continue; + + $found = $this->server->getPluginManager()->getPlugin($d); + if ($found === null) { + throw new PluginException("Missing dependancy: $d"); + return null; + } + } + if (!$load) continue; + } + if(isset($plugins[$pname]["softdepend"])) { + foreach($plugins[$p]["softdepend"] as $d) { + if (isset($plugins[$d])) { + $load = false; + break; + } + } + } + if (!$load) continue; + + // We can load this plugin... + $dat = $plugins[$pname]; + unset($plugins[$pname]); + $this->server->getPluginManager()->loadPlugin($dat["path"].self::CANARY,[$this]); + $loaded[$pname] = $dat; + ++$cnt; + } + if ($cnt == 0) { + throw new PluginException("Error loading plugins"); + break; + } + } + if (count($plugins)) { + $this->server->getLogger()->error(TextFormat::RED."Failed to load plugins ".implode(", ",array_keys($plugins))); + return null; + } + + // Load dummy + $plugins = $this->check_plugins($file,$ymls); + $desc = $this->getDummyDesc($plugins,$file); + $dataFolder = dirname($file) . DIRECTORY_SEPARATOR . $desc->getName(); + return $this->initPlugin($desc,$dataFolder,$file); + } + $desc = $this->myGetPluginDesc(self::PREFIX.$file."#".$ymls[0]); + $dataFolder = dirname($file) . DIRECTORY_SEPARATOR . $desc->getName(); + $basepath = $ymls[0] == self::PLUGIN_YML ? + self::PREFIX.$file."#" : + self::PREFIX.$file."#".dirname($ymls[0])."/"; + + $this->server->getLogger()->info(TextFormat::AQUA."Loading zip plugin " . $desc->getFullName()); + return $this->initPlugin($desc,$dataFolder,$basepath); + } + /** + * Returns the filename patterns that this loader accepts + * + * @return array + */ + public function getPluginFilters(){//@API + return "/\\.zip$/i"; + } + /** + * @param Plugin $plugin + */ + public function enablePlugin(Plugin $plugin){//@API + if($plugin instanceof PluginBase and !$plugin->isEnabled()){ + $this->server->getLogger()->info("Enabling " . $plugin->getDescription()->getFullName()); + + $plugin->setEnabled(true); + + Server::getInstance()->getPluginManager()->callEvent(new PluginEnableEvent($plugin)); + } + } + + /** + * @param Plugin $plugin + */ + public function disablePlugin(Plugin $plugin){//@API + if($plugin instanceof PluginBase and $plugin->isEnabled()){ + $this->server->getLogger()->info("Disabling " . $plugin->getDescription()->getFullName()); + + Server::getInstance()->getPluginManager()->callEvent(new PluginDisableEvent($plugin)); + + $plugin->setEnabled(false); + } + } + + + /********************************************************************/ + protected function getDummyDesc($plugins,$file) { + $name = preg_replace('/\.zip$/i',"",basename($file)); + $ch = [ + "name" => "_". $name, + "version" => "zipFile", + "main" => "ZipPluginLoader\\Dummy", + "description" => "Plugin Wrapper for loading ".$name, + ]; + foreach (["api","authors"] as $key) { + $ch[$key] = []; + foreach ($plugins as $pp) { + if (!isset($pp[$key])) continue; + foreach ($pp[$key] as $a) { + if (isset($ch[$key][$a])) continue; + $ch[$key][$a] = $a; + } + } + $ch[$key] = array_values($ch[$key]); + } + foreach (["depend","softdepend","loadbefore"] as $key) { + $ch[$key] = []; + foreach ($plugins as $pp) { + if (!isset($pp[$key])) continue; + foreach ($pp[$key] as $a) { + if (isset($plugins[$a])) continue; // Internal depedency + if (isset($ch[$key][$a])) continue; + $ch[$key][$a] = $a; + } + } + $ch[$key] = array_values($ch[$key]); + } + return new PluginDescription(yaml_emit($ch,YAML_UTF8_ENCODING)); + } + protected function myGetPluginDesc($file) { + if (substr($file,0,strlen(self::PREFIX)) != self::PREFIX) { + $file = self::PREFIX . $file; + } + if (substr($file,-strlen(self::PLUGIN_YML)) != self::PLUGIN_YML) { + if (substr($file,-strlen(self::ZIP_EXT)) == self::ZIP_EXT) { + $file .= "#".self::PLUGIN_YML; + } else { + switch(substr($file,-1)) { + case "/": + case "#": + break; + default: + $file .= "/"; + } + $file .= self::PLUGIN_YML; + } + } + $yaml = @file_get_contents($file); + if ($yaml == "") return null; + return new PluginDescription($yaml); + } + protected function check_plugins($file,$ymls) { + $plugins = []; + + // Check if there is a control file + $ok = false; + $ctl = preg_replace('/\.zip$/i','.ctl',$file); + if (file_exists($ctl)) { + $ctl = file($ctl,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES); + $ok = []; + foreach($ctl as $i) { + $i = trim($i); + if (substr($i,0,1) == ";" || substr($i,0,1) == "#") continue; + $ok[$i] = $i; + } + } + + foreach ($ymls as $plugin_yml) { + $dat = @file_get_contents(self::PREFIX.$file."#".$plugin_yml); + if ($dat == "") continue; + $dat = yaml_parse($dat); + $plugin = []; + foreach (["name","version","main"] as $str) { + if (!isset($dat[$str])) { + throw new PluginException("Invalid $plugin_yml"); + return null; + } + $plugin[$str] = $dat[$str]; + } + if ($ok) { + // Filter out plugins not listed in control file + if (!isset($ok[$dat["name"]])) continue; + } + if (!isset($dat["api"])) { + throw new PluginException("No API defined in $plugin_yml"); + return null; + } + $plugin["api"] = is_array($dat["api"]) ? $dat["api"] : [$dat["api"]]; + $plugin["path"] = self::PREFIX.$file."#". + ($plugin_yml == self::PLUGIN_YML ? "" : + dirname($plugin_yml)."/"); + foreach(["website","description","prefix","load"] as $str) { + if (isset($dat[$str])) $plugin[$str] = $dat[$str]; + } + $plugin["authors"] = []; + if (isset($dat["author"])) $plugin["authors"][] = $dat["author"]; + if (isset($dat["authors"])) { + foreach($dat["authors"] as $a) { + $plugin["authors"][] = $a; + } + } + foreach(["depend","loadBefore","softdepend"] as $arr) { + $plugin[$arr] = isset($dat[$arr]) ? (array)$dat[$arr] : []; + } + foreach(["commands","permissions"] as $arr) { + if (isset($dat[$arr]) && is_array($dat[$arr])) { + $plugin[$arr] = $dat[$arr]; + } + } + $plugins[$plugin["name"]] = $plugin; + } + return $plugins; + } + protected function findFiles($zip,$file) { + $files = []; + $za = new \ZipArchive(); + if($za->open($zip) !== true) return null; + // Look for plugin data... + $basepath = null; + + for ($i=0;$i < $za->numFiles;$i++) { + $st = $za->statIndex($i); + if (!isset($st["name"])) continue; + if (basename($st["name"]) == $file) { + $files[] = $st["name"]; + } + } + $za->close(); + unset($za); + if (count($files)) return $files; + return null; + } + + protected function initPlugin($desc,$dataFolder,$path) { + if (!($desc instanceof PluginDescription)) { + throw new PluginException("Couldn't load plugin"); + return null; + } + if(file_exists($dataFolder) and !is_dir($dataFolder)){ + throw new PluginException("Projected dataFolder '" . $dataFolder . "' for " . $descr->getName() . " exists and is not a directory"); + return null; + } + $className = $desc->getMain(); + + $this->server->getLoader()->addPath($path . "src"); + if(!class_exists($className, true)){ + throw new PluginException("Couldn't load zip plugin " . $descr->getName() . ": main class not found"); + return null; + } + $plugin = new $className(); + $plugin->init($this, $this->server, $desc, $dataFolder, $path); + $plugin->onLoad(); + return $plugin; + } + protected function zipdir($ff) { + if (substr($ff,0,strlen(self::PREFIX)) == self::PREFIX) { + $ff = substr($ff,strlen(self::PREFIX)); + } + $p = strpos($ff,"#"); + if ($p !== false) { + $ff = substr($ff,0,$p); + } + return dirname($ff); + } +}