PocketMine-MP  1.4 - API 1.10.0
 All Classes Namespaces Functions Variables Pages
PluginManager.php
1 <?php
2 
3 /*
4  *
5  * ____ _ _ __ __ _ __ __ ____
6  * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7  * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8  * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9  * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * @author PocketMine Team
17  * @link http://www.pocketmine.net/
18  *
19  *
20 */
21 
22 namespace pocketmine\plugin;
23 
38 
43 
45  private $server;
46 
48  private $commandMap;
49 
53  protected $plugins = [];
54 
58  protected $permissions = [];
59 
63  protected $defaultPerms = [];
64 
68  protected $defaultPermsOp = [];
69 
73  protected $permSubs = [];
74 
78  protected $defSubs = [];
79 
83  protected $defSubsOp = [];
84 
88  protected $fileAssociations = [];
89 
91  public static $pluginParentTimer;
92 
93  public static $useTimings = false;
94 
99  public function __construct(Server $server, SimpleCommandMap $commandMap){
100  $this->server = $server;
101  $this->commandMap = $commandMap;
102  }
103 
109  public function getPlugin($name){
110  if(isset($this->plugins[$name])){
111  return $this->plugins[$name];
112  }
113 
114  return null;
115  }
116 
122  public function registerInterface($loaderName){
123  if(is_subclass_of($loaderName, PluginLoader::class)){
124  $loader = new $loaderName($this->server);
125  }else{
126  return false;
127  }
128 
129  $this->fileAssociations[$loaderName] = $loader;
130 
131  return true;
132  }
133 
137  public function getPlugins(){
138  return $this->plugins;
139  }
140 
147  public function loadPlugin($path, $loaders = null){
148  foreach(($loaders === null ? $this->fileAssociations : $loaders) as $loader){
149  if(preg_match($loader->getPluginFilters(), basename($path)) > 0){
150  $description = $loader->getPluginDescription($path);
151  if($description instanceof PluginDescription){
152  if(($plugin = $loader->loadPlugin($path)) instanceof Plugin){
153  $this->plugins[$plugin->getDescription()->getName()] = $plugin;
154 
155  $pluginCommands = $this->parseYamlCommands($plugin);
156 
157  if(count($pluginCommands) > 0){
158  $this->commandMap->registerAll($plugin->getDescription()->getName(), $pluginCommands);
159  }
160 
161  return $plugin;
162  }
163  }
164  }
165  }
166 
167  return null;
168  }
169 
176  public function loadPlugins($directory, $newLoaders = null){
177 
178  if(is_dir($directory)){
179  $plugins = [];
180  $loadedPlugins = [];
181  $dependencies = [];
182  $softDependencies = [];
183  if(is_array($newLoaders)){
184  $loaders = [];
185  foreach($newLoaders as $key){
186  if(isset($this->fileAssociations[$key])){
187  $loaders[$key] = $this->fileAssociations[$key];
188  }
189  }
190  }else{
191  $loaders = $this->fileAssociations;
192  }
193  foreach($loaders as $loader){
194  foreach(new \RegexIterator(new \DirectoryIterator($directory), $loader->getPluginFilters()) as $file){
195  if($file === "." or $file === ".."){
196  continue;
197  }
198  $file = $directory . $file;
199  try{
200  $description = $loader->getPluginDescription($file);
201  if($description instanceof PluginDescription){
202  $name = $description->getName();
203  if(stripos($name, "pocketmine") !== false or stripos($name, "minecraft") !== false or stripos($name, "mojang") !== false){
204  $this->server->getLogger()->error("Could not load plugin '" . $name . "': restricted name");
205  continue;
206  }elseif(strpos($name, " ") !== false){
207  $this->server->getLogger()->warning("Plugin '" . $name . "' uses spaces in its name, this is discouraged");
208  }
209 
210  if(isset($plugins[$name]) or $this->getPlugin($name) instanceof Plugin){
211  $this->server->getLogger()->error("Could not load duplicate plugin '" . $name . "': plugin exists");
212  continue;
213  }
214 
215  $compatible = false;
216  //Check multiple dependencies
217  foreach($description->getCompatibleApis() as $version){
218  //Format: majorVersion.minorVersion.patch
219  $version = array_map("intval", explode(".", $version));
220  $apiVersion = array_map("intval", explode(".", $this->server->getApiVersion()));
221  //Completely different API version
222  if($version[0] !== $apiVersion[0]){
223  continue;
224  }
225  //If the plugin requires new API features, being backwards compatible
226  if($version[1] > $apiVersion[1]){
227  continue;
228  }
229 
230  $compatible = true;
231  break;
232  }
233 
234  if($compatible === false){
235  $this->server->getLogger()->error("Could not load plugin '" . $name . "': API version not compatible");
236  continue;
237  }
238 
239  $plugins[$name] = $file;
240 
241  $softDependencies[$name] = (array) $description->getSoftDepend();
242  $dependencies[$name] = (array) $description->getDepend();
243 
244  foreach($description->getLoadBefore() as $before){
245  if(isset($softDependencies[$before])){
246  $softDependencies[$before][] = $name;
247  }else{
248  $softDependencies[$before] = [$name];
249  }
250  }
251  }
252  }catch(\Exception $e){
253  $this->server->getLogger()->error("Could not load '" . $file . "' in folder '" . $directory . "': " . $e->getMessage());
254  if(($logger = $this->server->getLogger()) instanceof MainLogger){
255  $logger->logException($e);
256  }
257  }
258  }
259  }
260 
261 
262  while(count($plugins) > 0){
263  $missingDependency = true;
264  foreach($plugins as $name => $file){
265  if(isset($dependencies[$name])){
266  foreach($dependencies[$name] as $key => $dependency){
267  if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
268  unset($dependencies[$name][$key]);
269  }elseif(!isset($plugins[$dependency])){
270  $this->server->getLogger()->critical("Could not load plugin '" . $name . "': Unknown dependency");
271  break;
272  }
273  }
274 
275  if(count($dependencies[$name]) === 0){
276  unset($dependencies[$name]);
277  }
278  }
279 
280  if(isset($softDependencies[$name])){
281  foreach($softDependencies[$name] as $key => $dependency){
282  if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
283  unset($softDependencies[$name][$key]);
284  }
285  }
286 
287  if(count($softDependencies[$name]) === 0){
288  unset($softDependencies[$name]);
289  }
290  }
291 
292  if(!isset($dependencies[$name]) and !isset($softDependencies[$name])){
293  unset($plugins[$name]);
294  $missingDependency = false;
295  if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){
296  $loadedPlugins[$name] = $plugin;
297  }else{
298  $this->server->getLogger()->critical("Could not load plugin '" . $name . "'");
299  }
300  }
301  }
302 
303  if($missingDependency === true){
304  foreach($plugins as $name => $file){
305  if(!isset($dependencies[$name])){
306  unset($softDependencies[$name]);
307  unset($plugins[$name]);
308  $missingDependency = false;
309  if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){
310  $loadedPlugins[$name] = $plugin;
311  }else{
312  $this->server->getLogger()->critical("Could not load plugin '" . $name . "'");
313  }
314  }
315  }
316 
317  //No plugins loaded :(
318  if($missingDependency === true){
319  foreach($plugins as $name => $file){
320  $this->server->getLogger()->critical("Could not load plugin '" . $name . "': circular dependency detected");
321  }
322  $plugins = [];
323  }
324  }
325  }
326 
327  TimingsCommand::$timingStart = microtime(true);
328 
329  return $loadedPlugins;
330  }else{
331  TimingsCommand::$timingStart = microtime(true);
332 
333  return [];
334  }
335  }
336 
342  public function getPermission($name){
343  if(isset($this->permissions[$name])){
344  return $this->permissions[$name];
345  }
346 
347  return null;
348  }
349 
355  public function addPermission(Permission $permission){
356  if(!isset($this->permissions[$permission->getName()])){
357  $this->permissions[$permission->getName()] = $permission;
358  $this->calculatePermissionDefault($permission);
359 
360  return true;
361  }
362 
363  return false;
364  }
365 
369  public function removePermission($permission){
370  if($permission instanceof Permission){
371  unset($this->permissions[$permission->getName()]);
372  }else{
373  unset($this->permissions[$permission]);
374  }
375  }
376 
382  public function getDefaultPermissions($op){
383  if($op === true){
384  return $this->defaultPermsOp;
385  }else{
386  return $this->defaultPerms;
387  }
388  }
389 
393  public function recalculatePermissionDefaults(Permission $permission){
394  if(isset($this->permissions[$permission->getName()])){
395  unset($this->defaultPermsOp[$permission->getName()]);
396  unset($this->defaultPerms[$permission->getName()]);
397  $this->calculatePermissionDefault($permission);
398  }
399  }
400 
404  private function calculatePermissionDefault(Permission $permission){
405  Timings::$permissionDefaultTimer->startTiming();
406  if($permission->getDefault() === Permission::DEFAULT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){
407  $this->defaultPermsOp[$permission->getName()] = $permission;
408  $this->dirtyPermissibles(true);
409  }
410 
411  if($permission->getDefault() === Permission::DEFAULT_NOT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){
412  $this->defaultPerms[$permission->getName()] = $permission;
413  $this->dirtyPermissibles(false);
414  }
415  Timings::$permissionDefaultTimer->startTiming();
416  }
417 
421  private function dirtyPermissibles($op){
422  foreach($this->getDefaultPermSubscriptions($op) as $p){
423  $p->recalculatePermissions();
424  }
425  }
426 
431  public function subscribeToPermission($permission, Permissible $permissible){
432  if(!isset($this->permSubs[$permission])){
433  $this->permSubs[$permission] = [];
434  }
435  $this->permSubs[$permission][spl_object_hash($permissible)] = new \WeakRef($permissible);
436  }
437 
442  public function unsubscribeFromPermission($permission, Permissible $permissible){
443  if(isset($this->permSubs[$permission])){
444  unset($this->permSubs[$permission][spl_object_hash($permissible)]);
445  if(count($this->permSubs[$permission]) === 0){
446  unset($this->permSubs[$permission]);
447  }
448  }
449  }
450 
456  public function getPermissionSubscriptions($permission){
457  if(isset($this->permSubs[$permission])){
458  $subs = [];
459  foreach($this->permSubs[$permission] as $k => $perm){
461  if($perm->valid()){
462  $subs[] = $perm->get();
463  }else{
464  unset($this->permSubs[$permission][$k]);
465  }
466  }
467 
468  return $subs;
469  }
470 
471  return [];
472  }
473 
478  public function subscribeToDefaultPerms($op, Permissible $permissible){
479  if($op === true){
480  $this->defSubsOp[spl_object_hash($permissible)] = new \WeakRef($permissible);
481  }else{
482  $this->defSubs[spl_object_hash($permissible)] = new \WeakRef($permissible);
483  }
484  }
485 
490  public function unsubscribeFromDefaultPerms($op, Permissible $permissible){
491  if($op === true){
492  unset($this->defSubsOp[spl_object_hash($permissible)]);
493  }else{
494  unset($this->defSubs[spl_object_hash($permissible)]);
495  }
496  }
497 
503  public function getDefaultPermSubscriptions($op){
504  $subs = [];
505 
506  if($op === true){
507  foreach($this->defSubsOp as $k => $perm){
509  if($perm->valid()){
510  $subs[] = $perm->get();
511  }else{
512  unset($this->defSubsOp[$k]);
513  }
514  }
515  }else{
516  foreach($this->defSubs as $k => $perm){
518  if($perm->valid()){
519  $subs[] = $perm->get();
520  }else{
521  unset($this->defSubs[$k]);
522  }
523  }
524  }
525 
526  return $subs;
527  }
528 
532  public function getPermissions(){
533  return $this->permissions;
534  }
535 
541  public function isPluginEnabled(Plugin $plugin){
542  if($plugin instanceof Plugin and isset($this->plugins[$plugin->getDescription()->getName()])){
543  return $plugin->isEnabled();
544  }else{
545  return false;
546  }
547  }
548 
552  public function enablePlugin(Plugin $plugin){
553  if(!$plugin->isEnabled()){
554  try{
555  foreach($plugin->getDescription()->getPermissions() as $perm){
556  $this->addPermission($perm);
557  }
558  $plugin->getPluginLoader()->enablePlugin($plugin);
559  }catch(\Exception $e){
560  $logger = Server::getInstance()->getLogger();
561  if($logger instanceof MainLogger){
562  $logger->logException($e);
563  }
564  $this->disablePlugin($plugin);
565  }
566  }
567  }
568 
574  protected function parseYamlCommands(Plugin $plugin){
575  $pluginCmds = [];
576 
577  foreach($plugin->getDescription()->getCommands() as $key => $data){
578  if(strpos($key, ":") !== false){
579  $this->server->getLogger()->critical("Could not load command " . $key . " for plugin " . $plugin->getDescription()->getName());
580  continue;
581  }
582  if(is_array($data)){
583  $newCmd = new PluginCommand($key, $plugin);
584  if(isset($data["description"])){
585  $newCmd->setDescription($data["description"]);
586  }
587 
588  if(isset($data["usage"])){
589  $newCmd->setUsage($data["usage"]);
590  }
591 
592  if(isset($data["aliases"]) and is_array($data["aliases"])){
593  $aliasList = [];
594  foreach($data["aliases"] as $alias){
595  if(strpos($alias, ":") !== false){
596  $this->server->getLogger()->critical("Could not load alias " . $alias . " for plugin " . $plugin->getDescription()->getName());
597  continue;
598  }
599  $aliasList[] = $alias;
600  }
601 
602  $newCmd->setAliases($aliasList);
603  }
604 
605  if(isset($data["permission"])){
606  $newCmd->setPermission($data["permission"]);
607  }
608 
609  if(isset($data["permission-message"])){
610  $newCmd->setPermissionMessage($data["permission-message"]);
611  }
612 
613  $pluginCmds[] = $newCmd;
614  }
615  }
616 
617  return $pluginCmds;
618  }
619 
620  public function disablePlugins(){
621  foreach($this->getPlugins() as $plugin){
622  $this->disablePlugin($plugin);
623  }
624  }
625 
629  public function disablePlugin(Plugin $plugin){
630  if($plugin->isEnabled()){
631  try{
632  $plugin->getPluginLoader()->disablePlugin($plugin);
633  }catch(\Exception $e){
634  $logger = Server::getInstance()->getLogger();
635  if($logger instanceof MainLogger){
636  $logger->logException($e);
637  }
638  }
639 
640  $this->server->getScheduler()->cancelTasks($plugin);
642  foreach($plugin->getDescription()->getPermissions() as $perm){
643  $this->removePermission($perm);
644  }
645  }
646  }
647 
648  public function clearPlugins(){
649  $this->disablePlugins();
650  $this->plugins = [];
651  $this->fileAssociations = [];
652  $this->permissions = [];
653  $this->defaultPerms = [];
654  $this->defaultPermsOp = [];
655  }
656 
662  public function callEvent(Event $event){
663  foreach($event->getHandlers()->getRegisteredListeners() as $registration){
664  if(!$registration->getPlugin()->isEnabled()){
665  continue;
666  }
667 
668  try{
669  $registration->callEvent($event);
670  }catch(\Exception $e){
671  $this->server->getLogger()->critical("Could not pass event " . $event->getEventName() . " to " . $registration->getPlugin()->getDescription()->getFullName() . ": " . $e->getMessage() . " on " . get_class($registration->getListener()));
672  if(($logger = $this->server->getLogger()) instanceof MainLogger){
673  $logger->logException($e);
674  }
675  }
676  }
677  }
678 
687  public function registerEvents(Listener $listener, Plugin $plugin){
688  if(!$plugin->isEnabled()){
689  throw new PluginException("Plugin attempted to register " . get_class($listener) . " while not enabled");
690  }
691 
692  $reflection = new \ReflectionClass(get_class($listener));
693  foreach($reflection->getMethods() as $method){
694  if(!$method->isStatic()){
695  $priority = EventPriority::NORMAL;
696  $ignoreCancelled = false;
697  if(preg_match("/^[\t ]*\\* @priority[\t ]{1,}([a-zA-Z]{1,})/m", (string) $method->getDocComment(), $matches) > 0){
698  $matches[1] = strtoupper($matches[1]);
699  if(defined(EventPriority::class . "::" . $matches[1])){
700  $priority = constant(EventPriority::class . "::" . $matches[1]);
701  }
702  }
703  if(preg_match("/^[\t ]*\\* @ignoreCancelled[\t ]{1,}([a-zA-Z]{1,})/m", (string) $method->getDocComment(), $matches) > 0){
704  $matches[1] = strtolower($matches[1]);
705  if($matches[1] === "false"){
706  $ignoreCancelled = false;
707  }elseif($matches[1] === "true"){
708  $ignoreCancelled = true;
709  }
710  }
711 
712  $parameters = $method->getParameters();
713  if(count($parameters) === 1 and $parameters[0]->getClass() instanceof \ReflectionClass and is_subclass_of($parameters[0]->getClass()->getName(), Event::class)){
714  $class = $parameters[0]->getClass()->getName();
715  $reflection = new \ReflectionClass($class);
716  if(strpos((string) $reflection->getDocComment(), "@deprecated") !== false and $this->server->getProperty("settings.deprecated-verbose", true)){
717  $this->server->getLogger()->warning('Plugin ' . $plugin->getName() . ' has registered a listener for ' . $class . ' on method ' . get_class($listener) . '->' . $method->getName() . '(), but the event is Deprecated.');
718  }
719  $this->registerEvent($class, $listener, $priority, new MethodEventExecutor($method->getName()), $plugin, $ignoreCancelled);
720  }
721  }
722  }
723  }
724 
735  public function registerEvent($event, Listener $listener, $priority, EventExecutor $executor, Plugin $plugin, $ignoreCancelled = false){
736  if(!is_subclass_of($event, Event::class) or (new \ReflectionClass($event))->isAbstract()){
737  throw new PluginException($event . " is not a valid Event");
738  }
739 
740  if(!$plugin->isEnabled()){
741  throw new PluginException("Plugin attempted to register " . $event . " while not enabled");
742  }
743 
744  $timings = new TimingsHandler("Plugin: " . $plugin->getDescription()->getFullName() . " Event: " . get_class($listener) . "::" . ($executor instanceof MethodEventExecutor ? $executor->getMethod() : "???") . "(" . (new \ReflectionClass($event))->getShortName() . ")", self::$pluginParentTimer);
745 
746  $this->getEventListeners($event)->register(new RegisteredListener($listener, $executor, $priority, $plugin, $ignoreCancelled, $timings));
747  }
748 
754  private function getEventListeners($event){
755  if($event::$handlerList === null){
756  $event::$handlerList = new HandlerList();
757  }
758 
759  return $event::$handlerList;
760  }
761 
765  public function useTimings(){
766  return self::$useTimings;
767  }
768 
772  public function setUseTimings($use){
773  self::$useTimings = (bool) $use;
774  }
775 
776 }
recalculatePermissionDefaults(Permission $permission)
loadPlugins($directory, $newLoaders=null)
unsubscribeFromDefaultPerms($op, Permissible $permissible)
subscribeToDefaultPerms($op, Permissible $permissible)
registerEvents(Listener $listener, Plugin $plugin)
registerEvent($event, Listener $listener, $priority, EventExecutor $executor, Plugin $plugin, $ignoreCancelled=false)
static getInstance()
Definition: Server.php:1444
static unregisterAll($object=null)
Definition: HandlerList.php:56
addPermission(Permission $permission)
__construct(Server $server, SimpleCommandMap $commandMap)
unsubscribeFromPermission($permission, Permissible $permissible)
subscribeToPermission($permission, Permissible $permissible)
loadPlugin($path, $loaders=null)