CWIS Developer Documentation

- Home
- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

PluginManager.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: PluginManager.php
4 #
5 # Part of the ScoutLib application support library
6 # Copyright 2009-2013 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu
8 #
9
13 class PluginManager
14 {
15
16 # ---- PUBLIC INTERFACE --------------------------------------------------
17
25 public function __construct($AppFramework, $PluginDirectories)
26 {
27 # save framework and directory list for later use
28 $this->AF = $AppFramework;
29 Plugin::SetApplicationFramework($AppFramework);
30 $this->DirsToSearch = $PluginDirectories;
31
32 # get our own database handle
33 $this->DB = new Database();
34
35 # hook into events to load plugin PHP and HTML files
36 $this->AF->HookEvent("EVENT_PHP_FILE_LOAD", array($this, "FindPluginPhpFile"),
37 ApplicationFramework::ORDER_LAST);
38 $this->AF->HookEvent("EVENT_HTML_FILE_LOAD", array($this, "FindPluginHtmlFile"),
39 ApplicationFramework::ORDER_LAST);
40
41 # tell PluginCaller helper object how to get to us
42 PluginCaller::$Manager = $this;
43 }
44
49 public function LoadPlugins()
50 {
51 $ErrMsgs = array();
52
53 # look for plugin files
54 $this->PluginFiles = $this->FindPluginFiles($this->DirsToSearch);
55
56 # for each plugin file found
57 foreach ($this->PluginFiles as $PluginName => $PluginFileName)
58 {
59 try
60 {
61 # attempt to load plugin
62 $Result = $this->LoadPlugin($PluginName, $PluginFileName);
63
64 # add plugin to list of loaded plugins
65 $this->Plugins[$PluginName] = $Result;
66 }
67 catch (Exception $Exception)
68 {
69 # save errors
70 $ErrMsgs[$PluginName][] = $Exception->getMessage();
71 }
72 }
73
74 # check dependencies and drop any plugins with failed dependencies
75 $DepErrMsgs = $this->CheckDependencies($this->Plugins);
76 $DisabledPlugins = array();
77 foreach ($DepErrMsgs as $PluginName => $Msgs)
78 {
79 $DisabledPlugins[] = $PluginName;
80 foreach ($Msgs as $Msg)
81 {
82 $ErrMsgs[$PluginName][] = $Msg;
83 }
84 }
85
86 # sort plugins according to any loading order requests
87 $this->Plugins = $this->SortPluginsByInitializationPrecedence(
88 $this->Plugins);
89
90 # for each plugin
91 foreach ($this->Plugins as $PluginName => $Plugin)
92 {
93 # if plugin is loaded and enabled
94 if (!in_array($PluginName, $DisabledPlugins)
95 && $Plugin->IsEnabled())
96 {
97 # attempt to make plugin ready
98 try
99 {
100 $Result = $this->ReadyPlugin($Plugin);
101 }
102 catch (Exception $Except)
103 {
104 $Result = array("Uncaught Exception: ".$Except->getMessage());
105 }
106
107 # if making plugin ready failed
108 if ($Result !== NULL)
109 {
110 # save error messages
111 foreach ($Result as $Msg)
112 {
113 $ErrMsgs[$PluginName][] = $Msg;
114 }
115 }
116 else
117 {
118 # mark plugin as ready
119 $Plugin->IsReady(TRUE);
120 }
121 }
122 }
123
124 # check plugin dependencies again in case an install or upgrade failed
125 $DepErrMsgs = $this->CheckDependencies($this->Plugins, TRUE);
126
127 # for any plugins that were disabled because of dependencies
128 foreach ($DepErrMsgs as $PluginName => $Msgs)
129 {
130 # make sure all plugin hooks are undone
131 $this->UnhookPlugin($this->Plugins[$PluginName]);
132
133 # mark the plugin as unready
134 $this->Plugins[$PluginName]->IsReady(FALSE);
135
136 # record any errors that were reported
137 foreach ($Msgs as $Msg)
138 {
139 $ErrMsgs[$PluginName][] = $Msg;
140 }
141 }
142
143 # save any error messages for later use
144 $this->ErrMsgs = $ErrMsgs;
145
146 # report to caller whether any problems were encountered
147 return count($ErrMsgs) ? FALSE : TRUE;
148 }
149
155 public function GetErrorMessages()
156 {
157 return $this->ErrMsgs;
158 }
159
168 public function GetPlugin($PluginName, $EvenIfNotReady = FALSE)
169 {
170 if (!$EvenIfNotReady && array_key_exists($PluginName, $this->Plugins)
171 && !$this->Plugins[$PluginName]->IsReady())
172 {
173 $Trace = debug_backtrace();
174 $Caller = basename($Trace[0]["file"]).":".$Trace[0]["line"];
175 throw new Exception("Attempt to access uninitialized plugin "
176 .$PluginName." from ".$Caller);
177 }
178 return isset($this->Plugins[$PluginName])
179 ? $this->Plugins[$PluginName] : NULL;
180 }
181
186 public function GetPlugins()
187 {
188 return $this->Plugins;
189 }
190
198 public function GetPluginForCurrentPage()
199 {
200 return $this->GetPlugin($this->PageFilePlugin);
201 }
202
208 public function GetPluginAttributes()
209 {
210 # for each loaded plugin
211 $Info = array();
212 foreach ($this->Plugins as $PluginName => $Plugin)
213 {
214 # retrieve plugin attributes
215 $Info[$PluginName] = $Plugin->GetAttributes();
216
217 # add in other values to attributes
218 $Info[$PluginName]["Enabled"] = $Plugin->IsEnabled();
219 $Info[$PluginName]["Installed"] = $Plugin->IsInstalled();
220 $Info[$PluginName]["ClassFile"] = $this->PluginFiles[$PluginName];
221 }
222
223 # sort plugins by name
224 uasort($Info, function ($A, $B) {
225 $AName = strtoupper($A["Name"]);
226 $BName = strtoupper($B["Name"]);
227 return ($AName == $BName) ? 0
228 : ($AName < $BName) ? -1 : 1;
229 });
230
231 # return plugin info to caller
232 return $Info;
233 }
234
240 public function GetDependents($PluginName)
241 {
242 $Dependents = array();
243 $AllAttribs = $this->GetPluginAttributes();
244 foreach ($AllAttribs as $Name => $Attribs)
245 {
246 if (array_key_exists($PluginName, $Attribs["Requires"]))
247 {
248 $Dependents[] = $Name;
249 $SubDependents = $this->GetDependents($Name);
250 $Dependents = array_merge($Dependents, $SubDependents);
251 }
252 }
253 return $Dependents;
254 }
255
260 public function GetActivePluginList()
261 {
262 $ActivePluginNames = array();
263 foreach ($this->Plugins as $PluginName => $Plugin)
264 {
265 if ($Plugin->IsReady())
266 {
267 $ActivePluginNames[] = $PluginName;
268 }
269 }
270 return $ActivePluginNames;
271 }
272
279 public function PluginEnabled($PluginName, $NewValue = NULL)
280 {
281 return !isset($this->Plugins[$PluginName]) ? FALSE
282 : $this->Plugins[$PluginName]->IsEnabled($NewValue);
283 }
284
290 public function UninstallPlugin($PluginName)
291 {
292 # assume success
293 $Result = NULL;
294
295 # if plugin is installed
296 if ($this->Plugins[$PluginName]->IsInstalled())
297 {
298 # call uninstall method for plugin
299 $Result = $this->Plugins[$PluginName]->Uninstall();
300
301 # if plugin uninstall method succeeded
302 if ($Result === NULL)
303 {
304 # remove plugin info from database
305 $this->DB->Query("DELETE FROM PluginInfo"
306 ." WHERE BaseName = '".addslashes($PluginName)."'");
307
308 # drop our data for the plugin
309 unset($this->Plugins[$PluginName]);
310 unset($this->PluginFiles[$PluginName]);
311 }
312 }
313
314 # report results (if any) to caller
315 return $Result;
316 }
317
323 static public function SetConfigValueLoader($Func)
324 {
325 if (!is_callable($Func))
326 {
327 throw new InvalidArgumentException(
328 "Invalid configuration value loading function supplied.");
329 }
330 self::$CfgValueLoader = $Func;
331 }
332
333
334 # ---- PRIVATE INTERFACE -------------------------------------------------
335
336 private $AF;
337 private $DB;
338 private $DirsToSearch;
339 private $ErrMsgs = array();
340 private $PageFilePlugin = NULL;
341 private $Plugins = array();
342 private $PluginFiles = array();
343 private $PluginHasDir = array();
344
345 static private $CfgValueLoader;
346
356 private function FindPluginFiles($DirsToSearch)
357 {
358 # for each directory
359 $PluginFiles = array();
360 foreach ($DirsToSearch as $Dir)
361 {
362 # if directory exists
363 if (is_dir($Dir))
364 {
365 # for each file in directory
366 # (plugin directories will be in the list before
367 # similarly-named (single-file) plugin files,
368 # so directory versions will be preferred over
369 # single-file versions)
370 $FileNames = scandir($Dir);
371 foreach ($FileNames as $FileName)
372 {
373 # if file looks like base plugin file
374 if (preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*\.php$/", $FileName))
375 {
376 # if we do not already have a plugin with that name
377 $PluginName = substr($FileName, 0, -4);
378 if (!isset($PluginFiles[$PluginName]))
379 {
380 # add file to list
381 $PluginFiles[$PluginName] = $Dir."/".$FileName;
382 }
383 }
384 # else if file looks like plugin directory
385 elseif (is_dir($Dir."/".$FileName)
386 && preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*/", $FileName))
387 {
388 # if there is a base plugin file in the directory
389 $PluginName = $FileName;
390 $PluginFile = $Dir."/".$PluginName."/".$PluginName.".php";
391 if (file_exists($PluginFile))
392 {
393 # add file to list
394 $PluginFiles[$PluginName] = $PluginFile;
395 }
396 else
397 {
398 # record error
399 $this->ErrMsgs[$PluginName][] =
400 "Expected plugin file <i>".$PluginName.".php</i> not"
401 ." found in plugin subdirectory <i>"
402 .$Dir."/".$PluginName."</i>";
403 }
404 }
405 }
406 }
407 }
408
409 # return info about found plugins to caller
410 return $PluginFiles;
411 }
412
422 private function LoadPlugin($PluginName, $PluginFileName)
423 {
424 # bring in plugin class file
425 include_once($PluginFileName);
426
427 # check to make sure plugin class is defined by file
428 if (!class_exists($PluginName))
429 {
430 throw new Exception("Expected class <i>".$PluginName
431 ."</i> not found in plugin file <i>"
432 .$PluginFileName."</i>");
433 }
434
435 # check that plugin class is a valid descendant of base plugin class
436 if (!is_subclass_of($PluginName, "Plugin"))
437 {
438 throw new Exception("Plugin <b>".$PluginName."</b>"
439 ." could not be loaded because <i>".$PluginName."</i> class"
440 ." was not a subclass of base <i>Plugin</i> class");
441 }
442
443 # instantiate and register the plugin
444 $Plugin = new $PluginName($PluginName);
445
446 # check required plugin attributes
447 $RequiredAttribs = array("Name", "Version");
448 $Attribs = $Plugin->GetAttributes();
449 foreach ($RequiredAttribs as $AttribName)
450 {
451 if (!strlen($Attribs[$AttribName]))
452 {
453 throw new Exception("Plugin <b>".$PluginName."</b>"
454 ." could not be loaded because it"
455 ." did not have a <i>"
456 .$AttribName."</i> attribute set.");
457 }
458 }
459
460 # return loaded plugin
461 return $Plugin;
462 }
463
469 private function ReadyPlugin(&$Plugin)
470 {
471 # tell system to recognize any plugin subdirectories
472 $this->RecognizePluginDirectories($Plugin->GetBaseName());
473
474 # install or upgrade plugin if needed
475 $PluginInstalled = $this->InstallPlugin($Plugin);
476
477 # if install/upgrade failed
478 if (is_string($PluginInstalled))
479 {
480 # report errors to caller
481 return array($PluginInstalled);
482 }
483
484 # set up plugin configuration options
485 $ErrMsgs = $Plugin->SetUpConfigOptions();
486
487 # if plugin configuration setup failed
488 if ($ErrMsgs !== NULL)
489 {
490 # report errors to caller
491 return is_array($ErrMsgs) ? $ErrMsgs : array($ErrMsgs);
492 }
493
494 # set default configuration values if necessary
495 if ($PluginInstalled)
496 {
497 $this->SetPluginDefaultConfigValues($Plugin);
498 }
499
500 # initialize the plugin
501 $ErrMsgs = $Plugin->Initialize();
502
503 # if initialization failed
504 if ($ErrMsgs !== NULL)
505 {
506 # report errors to caller
507 return is_array($ErrMsgs) ? $ErrMsgs : array($ErrMsgs);
508 }
509
510 # register and hook any events for plugin
511 $ErrMsgs = $this->HookPlugin($Plugin);
512
513 # make sure all hooks are undone if hooking failed
514 if ($ErrMsgs !== NULL)
515 {
516 $this->UnhookPlugin($Plugin);
517 }
518
519 # report result to caller
520 return $ErrMsgs;
521 }
522
528 private function HookPlugin(&$Plugin)
529 {
530 # register any events declared by plugin
531 $Events = $Plugin->DeclareEvents();
532 if (count($Events)) { $this->AF->RegisterEvent($Events); }
533
534 # if plugin has events that need to be hooked
535 $EventsToHook = $Plugin->HookEvents();
536 if (count($EventsToHook))
537 {
538 # for each event
539 $ErrMsgs = array();
540 foreach ($EventsToHook as $EventName => $PluginMethods)
541 {
542 # for each method to hook for the event
543 if (!is_array($PluginMethods))
544 { $PluginMethods = array($PluginMethods); }
545 foreach ($PluginMethods as $PluginMethod)
546 {
547 # if the event only allows static callbacks
548 if ($this->AF->IsStaticOnlyEvent($EventName))
549 {
550 # hook event with shell for static callback
551 $Caller = new PluginCaller(
552 $Plugin->GetBaseName(), $PluginMethod);
553 $Result = $this->AF->HookEvent(
554 $EventName,
555 array($Caller, "CallPluginMethod"));
556 }
557 else
558 {
559 # hook event
560 $Result = $this->AF->HookEvent(
561 $EventName, array($Plugin, $PluginMethod));
562 }
563
564 # record any errors
565 if ($Result === FALSE)
566 {
567 $ErrMsgs[] = "Unable to hook requested event <i>"
568 .$EventName."</i> for plugin <b>"
569 .$Plugin->GetBaseName()."</b>";
570 }
571 }
572 }
573
574 # if event hook setup failed
575 if (count($ErrMsgs))
576 {
577 # report errors to caller
578 return $ErrMsgs;
579 }
580 }
581
582 # report success to caller
583 return NULL;
584 }
585
590 private function UnhookPlugin(&$Plugin)
591 {
592 # if plugin had events to hook
593 $EventsToHook = $Plugin->HookEvents();
594 if (count($EventsToHook))
595 {
596 # for each event
597 $ErrMsgs = array();
598 foreach ($EventsToHook as $EventName => $PluginMethods)
599 {
600 # for each method to hook for the event
601 if (!is_array($PluginMethods))
602 { $PluginMethods = array($PluginMethods); }
603 foreach ($PluginMethods as $PluginMethod)
604 {
605 # if the event only allows static callbacks
606 if ($this->AF->IsStaticOnlyEvent($EventName))
607 {
608 # unhook event with shell for static callback
609 $Caller = new PluginCaller(
610 $Plugin->GetBaseName(), $PluginMethod);
611 $this->AF->UnhookEvent($EventName,
612 array($Caller, "CallPluginMethod"));
613 }
614 else
615 {
616 # unhook event
617 $this->AF->UnhookEvent(
618 $EventName, array($Plugin, $PluginMethod));
619 }
620 }
621 }
622 }
623 }
624
632 private function InstallPlugin(&$Plugin)
633 {
634 # if plugin has not been installed
635 $InstallOrUpgradePerformed = FALSE;
636 $PluginName = $Plugin->GetBaseName();
637 $Attribs = $Plugin->GetAttributes();
638 $LockName = __CLASS__.":Install:".$PluginName;
639 if (!$Plugin->IsInstalled())
640 {
641 # set default values if present
642 $this->SetPluginDefaultConfigValues($Plugin, TRUE);
643
644 # try to get lock to prevent anyone else from trying to run
645 # install or upgrade at the same time
646 $GotLock = $this->AF->GetLock($LockName, FALSE);
647
648 # if could not get lock
649 if (!$GotLock)
650 {
651 # return error
652 return "Installation of plugin <b>"
653 .$PluginName."</b> in progress.";
654 }
655
656 # install plugin
657 $ErrMsg = $Plugin->Install();
658 $InstallOrUpgradePerformed = TRUE;
659
660 # if install succeeded
661 if ($ErrMsg == NULL)
662 {
663 # mark plugin as installed
664 $Plugin->IsInstalled(TRUE);
665
666 # release lock
667 $this->AF->ReleaseLock($LockName);
668 }
669 else
670 {
671 # release lock
672 $this->AF->ReleaseLock($LockName);
673
674 # return error message about installation failure
675 return "Installation of plugin <b>"
676 .$PluginName."</b> failed: <i>".$ErrMsg."</i>";
677 }
678 }
679 else
680 {
681 # if plugin version is newer than version in database
682 if (version_compare($Attribs["Version"],
683 $Plugin->InstalledVersion()) == 1)
684 {
685 # set default values for any new configuration settings
686 $this->SetPluginDefaultConfigValues($Plugin);
687
688 # try to get lock to prevent anyone else from trying to run
689 # upgrade or install at the same time
690 $GotLock = $this->AF->GetLock($LockName, FALSE);
691
692 # if could not get lock
693 if (!$GotLock)
694 {
695 # return error
696 return "Upgrade of plugin <b>"
697 .$PluginName."</b> in progress.";
698 }
699
700 # upgrade plugin
701 $ErrMsg = $Plugin->Upgrade($Plugin->InstalledVersion());
702 $InstallOrUpgradePerformed = TRUE;
703
704 # if upgrade succeeded
705 if ($ErrMsg === NULL)
706 {
707 # update plugin version in database
708 $Plugin->InstalledVersion($Attribs["Version"]);
709
710 # release lock
711 $this->AF->ReleaseLock($LockName);
712 }
713 else
714 {
715 # release lock
716 $this->AF->ReleaseLock($LockName);
717
718 # report error message about upgrade failure
719 return "Upgrade of plugin <b>"
720 .$PluginName."</b> from version <i>"
721 .addslashes($Plugin->InstalledVersion())
722 ."</i> to version <i>"
723 .addslashes($Attribs["Version"])."</i> failed: <i>"
724 .$ErrMsg."</i>";
725 }
726 }
727 # else if plugin version is older than version in database
728 elseif (version_compare($Attribs["Version"],
729 $Plugin->InstalledVersion()) == -1)
730 {
731 # return error message about version conflict
732 return "Plugin <b>"
733 .$PluginName."</b> is older (<i>"
734 .addslashes($Attribs["Version"])
735 ."</i>) than previously-installed version (<i>"
736 .addslashes($Plugin->InstalledVersion())
737 ."</i>).";
738 }
739 }
740
741 # report result to caller
742 return $InstallOrUpgradePerformed;
743 }
744
750 private function RecognizePluginDirectories($PluginName)
751 {
752 # if plugin has its own subdirectory
753 $PluginFileName = $this->PluginFiles[$PluginName];
754 $this->PluginHasDir[$PluginName] = preg_match(
755 "%/".$PluginName."/".$PluginName.".php\$%",
756 $PluginFileName) ? TRUE : FALSE;
757 if ($this->PluginHasDir[$PluginName])
758 {
759 # if plugin has its own object directory
760 $Dir = dirname($PluginFileName);
761 if (is_dir($Dir."/objects"))
762 {
763 # add object directory to class autoloading list
764 ApplicationFramework::AddObjectDirectory($Dir."/objects");
765 }
766 else
767 {
768 # add plugin directory to class autoloading list
769 ApplicationFramework::AddObjectDirectory($Dir);
770 }
771
772 # if plugin has its own interface directory
773 $InterfaceDir = $Dir."/interface";
774 if (is_dir($InterfaceDir))
775 {
776 # add interface directory to AF search lists
777 $this->AF->AddInterfaceDirectories(
778 [ $InterfaceDir."/%ACTIVEUI%/",
779 $InterfaceDir."/%DEFAULTUI%/" ]);
780
781 # add interface object directories if any found
782 if (count(glob($InterfaceDir."/*/objects")))
783 {
784 $this->AF->AddObjectDirectory(
785 $InterfaceDir."/%ACTIVEUI%/objects");
786 $this->AF->AddObjectDirectory(
787 $InterfaceDir."/%DEFAULTUI%/objects");
788 }
789
790 # add interface include directories if any found
791 if (count(glob($InterfaceDir."/*/include")))
792 {
793 $this->AF->AddIncludeDirectories(
794 [ $InterfaceDir."/%ACTIVEUI%/include",
795 $InterfaceDir."/%DEFAULTUI%/include" ]);
796 }
797
798 # add image directories if any found
799 if (count(glob($InterfaceDir."/*/images")))
800 {
801 $this->AF->AddImageDirectories(
802 [ $InterfaceDir."/%ACTIVEUI%/images",
803 $InterfaceDir."/%DEFAULTUI%/images" ]);
804 }
805 }
806 }
807 }
808
816 private function SetPluginDefaultConfigValues($Plugin, $Overwrite = FALSE)
817 {
818 # if plugin has configuration info
819 $Attribs = $Plugin->GetAttributes();
820 if (isset($Attribs["CfgSetup"]))
821 {
822 foreach ($Attribs["CfgSetup"] as $CfgValName => $CfgSetup)
823 {
824 if (isset($CfgSetup["Default"]) && ($Overwrite
825 || ($Plugin->ConfigSetting($CfgValName) === NULL)))
826 {
827 if (isset(self::$CfgValueLoader))
828 {
829 $Plugin->ConfigSetting($CfgValName,
830 call_user_func(self::$CfgValueLoader,
831 $CfgSetup["Type"], $CfgSetup["Default"]));
832 }
833 else
834 {
835 $Plugin->ConfigSetting($CfgValName, $CfgSetup["Default"]);
836 }
837 }
838 }
839 }
840 }
841
849 private function CheckDependencies($Plugins, $CheckReady = FALSE)
850 {
851 # look until all enabled plugins check out okay
852 $ErrMsgs = array();
853 do
854 {
855 # start out assuming all plugins are okay
856 $AllOkay = TRUE;
857
858 # for each plugin
859 foreach ($Plugins as $PluginName => $Plugin)
860 {
861 # if plugin is enabled and not checking for ready
862 # or plugin is ready
863 if ($Plugin->IsEnabled() && (!$CheckReady || $Plugin->IsReady()))
864 {
865 # load plugin attributes
866 if (!isset($Attribs[$PluginName]))
867 {
868 $Attribs[$PluginName] = $Plugin->GetAttributes();
869 }
870
871 # for each dependency for this plugin
872 foreach ($Attribs[$PluginName]["Requires"]
873 as $ReqName => $ReqVersion)
874 {
875 # handle PHP version requirements
876 if ($ReqName == "PHP")
877 {
878 if (version_compare($ReqVersion, phpversion(), ">"))
879 {
880 $ErrMsgs[$PluginName][] = "PHP version "
881 ."<i>".$ReqVersion."</i>"
882 ." required by <b>".$PluginName."</b>"
883 ." was not available. (Current PHP version"
884 ." is <i>".phpversion()."</i>.)";
885 }
886 }
887 # handle PHP extension requirements
888 elseif (preg_match("/^PHPX_/", $ReqName))
889 {
890 list($Dummy, $ExtensionName) = explode("_", $ReqName, 2);
891 if (!extension_loaded($ExtensionName))
892 {
893 $ErrMsgs[$PluginName][] = "PHP extension "
894 ."<i>".$ExtensionName."</i>"
895 ." required by <b>".$PluginName."</b>"
896 ." was not available.";
897 }
898 elseif (($ReqVersion !== TRUE)
899 && (phpversion($ExtensionName) !== FALSE)
900 && version_compare($ReqVersion,
901 phpversion($ExtensionName), ">"))
902 {
903 $ErrMsgs[$PluginName][] = "PHP extension "
904 ."<i>".$ExtensionName."</i>"
905 ." version <i>".$ReqVersion."</i>"
906 ." required by <b>".$PluginName."</b>"
907 ." was not available. (Current version"
908 ." of extension <i>".$ExtensionName."</i>"
909 ." is <i>".phpversion($ExtensionName)."</i>.)";
910 }
911 }
912 # handle dependencies on other plugins
913 else
914 {
915 # load plugin attributes if not already loaded
916 if (isset($Plugins[$ReqName])
917 && !isset($Attribs[$ReqName]))
918 {
919 $Attribs[$ReqName] =
920 $Plugins[$ReqName]->GetAttributes();
921 }
922
923 # if target plugin is not present or is too old
924 # or is not enabled
925 # or (if appropriate) is not ready
926 if (!isset($Plugins[$ReqName])
927 || version_compare($ReqVersion,
928 $Attribs[$ReqName]["Version"], ">")
929 || !$Plugins[$ReqName]->IsEnabled()
930 || ($CheckReady
931 && !$Plugins[$ReqName]->IsReady()))
932 {
933 # add error message
934 $ErrMsgs[$PluginName][] = "Plugin <i>"
935 .$ReqName." ".$ReqVersion."</i>"
936 ." required by <b>".$PluginName."</b>"
937 ." was not available.";
938 }
939 }
940
941 # if problem was found with plugin
942 if (isset($ErrMsgs[$PluginName]))
943 {
944 # remove plugin from our list
945 unset($Plugins[$PluginName]);
946
947 # set flag to indicate a plugin had to be dropped
948 $AllOkay = FALSE;
949 }
950 }
951 }
952 }
953 } while ($AllOkay == FALSE);
954
955 # return messages about any dropped plugins back to caller
956 return $ErrMsgs;
957 }
958
967 private function SortPluginsByInitializationPrecedence($Plugins)
968 {
969 # load plugin attributes
970 foreach ($Plugins as $PluginName => $Plugin)
971 {
972 $PluginAttribs[$PluginName] = $Plugin->GetAttributes();
973 }
974
975 # determine initialization order
976 $PluginsAfterUs = array();
977 foreach ($PluginAttribs as $PluginName => $Attribs)
978 {
979 foreach ($Attribs["InitializeBefore"] as $OtherPluginName)
980 {
981 $PluginsAfterUs[$PluginName][] = $OtherPluginName;
982 }
983 foreach ($Attribs["InitializeAfter"] as $OtherPluginName)
984 {
985 $PluginsAfterUs[$OtherPluginName][] = $PluginName;
986 }
987 }
988
989 # infer other initialization order cues from lists of required plugins
990 foreach ($PluginAttribs as $PluginName => $Attribs)
991 {
992 # for each required plugin
993 foreach ($Attribs["Requires"]
994 as $RequiredPluginName => $RequiredPluginVersion)
995 {
996 # skip the requirement if it it not for another known plugin
997 if (!isset($PluginAttribs[$RequiredPluginName]))
998 {
999 continue;
1000 }
1001
1002 # if there is not a requirement in the opposite direction
1003 if (!array_key_exists($PluginName,
1004 $PluginAttribs[$RequiredPluginName]["Requires"]))
1005 {
1006 # if the required plugin is not scheduled to be after us
1007 if (!array_key_exists($PluginName, $PluginsAfterUs)
1008 || !in_array($RequiredPluginName,
1009 $PluginsAfterUs[$PluginName]))
1010 {
1011 # if we are not already scheduled to be after the required plugin
1012 if (!array_key_exists($PluginName, $PluginsAfterUs)
1013 || !in_array($RequiredPluginName,
1014 $PluginsAfterUs[$PluginName]))
1015 {
1016 # schedule us to be after the required plugin
1017 $PluginsAfterUs[$RequiredPluginName][] =
1018 $PluginName;
1019 }
1020 }
1021 }
1022 }
1023 }
1024
1025 # keep track of those plugins we have yet to do and those that are done
1026 $UnsortedPlugins = array_keys($Plugins);
1027 $PluginsProcessed = array();
1028
1029 # limit the number of iterations of the plugin ordering loop
1030 # to 10 times the number of plugins we have
1031 $MaxIterations = 10 * count($UnsortedPlugins);
1032 $IterationCount = 0;
1033
1034 # iterate through all the plugins that need processing
1035 while (($NextPlugin = array_shift($UnsortedPlugins)) !== NULL)
1036 {
1037 # check to be sure that we're not looping forever
1038 $IterationCount++;
1039 if ($IterationCount > $MaxIterations)
1040 {
1041 throw new Exception(
1042 "Max iteration count exceeded trying to determine plugin"
1043 ." loading order. Is there a dependency loop?");
1044 }
1045
1046 # if no plugins require this one, it can go last
1047 if (!isset($PluginsAfterUs[$NextPlugin]))
1048 {
1049 $PluginsProcessed[$NextPlugin] = $MaxIterations;
1050 }
1051 else
1052 {
1053 # for plugins that are required by others
1054 $Index = $MaxIterations;
1055 foreach ($PluginsAfterUs[$NextPlugin] as $GoBefore)
1056 {
1057 if (!isset($PluginsProcessed[$GoBefore]))
1058 {
1059 # if there is something that requires us which hasn't
1060 # yet been assigned an order, then we can't determine
1061 # our own place on this iteration
1062 array_push($UnsortedPlugins, $NextPlugin);
1063 continue 2;
1064 }
1065 else
1066 {
1067 # otherwise, make sure that we're loaded
1068 # before the earliest of the things that require us
1069 $Index = min($Index, $PluginsProcessed[$GoBefore] - 1);
1070 }
1071 }
1072 $PluginsProcessed[$NextPlugin] = $Index;
1073 }
1074 }
1075
1076 # arrange plugins according to our ordering
1077 asort($PluginsProcessed, SORT_NUMERIC);
1078 $SortedPlugins = array();
1079 foreach ($PluginsProcessed as $PluginName => $SortOrder)
1080 {
1081 $SortedPlugins[$PluginName] = $Plugins[$PluginName];
1082 }
1083
1084 # return sorted list to caller
1085 return $SortedPlugins;
1086 }
1087
1096 public function FindPluginPhpFile($PageName)
1097 {
1098 # build list of possible locations for file
1099 $Locations = array(
1100 "local/plugins/%PLUGIN%/pages/%PAGE%.php",
1101 "plugins/%PLUGIN%/pages/%PAGE%.php",
1102 "local/plugins/%PLUGIN%/%PAGE%.php",
1103 "plugins/%PLUGIN%/%PAGE%.php",
1104 );
1105
1106 # look for file and return (possibly) updated page to caller
1107 return $this->FindPluginPageFile($PageName, $Locations);
1108 }
1119 public function FindPluginHtmlFile($PageName)
1120 {
1121 # build list of possible locations for file
1122 $Locations = array(
1123 "local/plugins/%PLUGIN%/interface/%ACTIVEUI%/%PAGE%.html",
1124 "plugins/%PLUGIN%/interface/%ACTIVEUI%/%PAGE%.html",
1125 "local/plugins/%PLUGIN%/interface/%DEFAULTUI%/%PAGE%.html",
1126 "plugins/%PLUGIN%/interface/%DEFAULTUI%/%PAGE%.html",
1127 "local/plugins/%PLUGIN%/%PAGE%.html",
1128 "plugins/%PLUGIN%/%PAGE%.html",
1129 );
1130
1131 # find HTML file
1132 $Params = $this->FindPluginPageFile($PageName, $Locations);
1133
1134 # if plugin HTML file was found
1135 if ($Params["PageName"] != $PageName)
1136 {
1137 # add subdirectories for plugin to search paths
1138 $Dir = preg_replace("%^local/%", "", dirname($Params["PageName"]));
1139 $this->AF->AddImageDirectories(array(
1140 "local/".$Dir."/images",
1141 $Dir."/images",
1142 ));
1143 $this->AF->AddIncludeDirectories(array(
1144 "local/".$Dir."/include",
1145 $Dir."/include",
1146 ));
1147 $this->AF->AddFunctionDirectories(array(
1148 "local/".$Dir."/include",
1149 $Dir."/include",
1150 ));
1151 }
1152
1153 # return possibly revised HTML file name to caller
1154 return $Params;
1155 }
1168 private function FindPluginPageFile($PageName, $Locations)
1169 {
1170 # set up return value assuming we will not find plugin page file
1171 $ReturnValue["PageName"] = $PageName;
1172
1173 # look for plugin name and plugin page name in base page name
1174 preg_match("/P_([A-Za-z].[A-Za-z0-9]*)_([A-Za-z0-9_-]+)/", $PageName, $Matches);
1175
1176 # if plugin name and plugin page name were found and plugin name is valid
1177 if (count($Matches) == 3)
1178 {
1179 # if plugin is valid and enabled and has its own subdirectory
1180 $PluginName = $Matches[1];
1181 if (isset($this->Plugins[$PluginName])
1182 && $this->PluginHasDir[$PluginName]
1183 && $this->Plugins[$PluginName]->IsEnabled())
1184 {
1185 # for each possible location
1186 $PageName = $Matches[2];
1187 $ActiveUI = $this->AF->ActiveUserInterface();
1188 $DefaultUI = $this->AF->DefaultUserInterface();
1189 foreach ($Locations as $Loc)
1190 {
1191 # make any needed substitutions into path
1192 $FileName = str_replace(
1193 array("%DEFAULTUI%", "%ACTIVEUI%", "%PLUGIN%", "%PAGE%"),
1194 array($DefaultUI, $ActiveUI, $PluginName, $PageName),
1195 $Loc);
1196
1197 # if file exists in this location
1198 if (file_exists($FileName))
1199 {
1200 # set return value to contain full plugin page file name
1201 $ReturnValue["PageName"] = $FileName;
1202
1203 # save plugin name as home of current page
1204 $this->PageFilePlugin = $PluginName;
1205
1206 # set G_Plugin to plugin associated with current page
1207 $GLOBALS["G_Plugin"] = $this->GetPluginForCurrentPage();
1208
1209 # stop looking
1210 break;
1211 }
1212 }
1213 }
1214 }
1215
1216 # return array containing page name or page file name to caller
1217 return $ReturnValue;
1218 }
1219 }
1220
1232 class PluginCaller
1233 {
1234
1241 public function __construct($PluginName, $MethodName)
1242 {
1243 $this->PluginName = $PluginName;
1244 $this->MethodName = $MethodName;
1245 }
1246
1252 public function CallPluginMethod()
1253 {
1254 $Args = func_get_args();
1255 $Plugin = self::$Manager->GetPlugin($this->PluginName);
1256 return call_user_func_array(array($Plugin, $this->MethodName), $Args);
1257 }
1258
1263 public function GetCallbackAsText()
1264 {
1265 return $this->PluginName."::".$this->MethodName;
1266 }
1267
1273 public function __sleep()
1274 {
1275 return array("PluginName", "MethodName");
1276 }
1277
1279 static public $Manager;
1280
1281 private $PluginName;
1282 private $MethodName;
1283 }
PluginManager\GetErrorMessages
GetErrorMessages()
Retrieve any error messages generated during plugin loading.
Definition: PluginManager.php:155
PluginManager
Manager to load and invoke plugins.
Definition: PluginManager.php:13
PluginManager\GetPlugin
GetPlugin($PluginName, $EvenIfNotReady=FALSE)
Retrieve specified plugin.
Definition: PluginManager.php:168
Database
SQL database abstraction object with smart query caching.
Definition: Database.php:22
PluginManager\UninstallPlugin
UninstallPlugin($PluginName)
Uninstall plugin and (optionally) delete any associated data.
Definition: PluginManager.php:290
PluginManager\GetDependents
GetDependents($PluginName)
Returns a list of plugins dependent on the specified plugin.
Definition: PluginManager.php:240
PluginManager\GetActivePluginList
GetActivePluginList()
Get list of active (i.e.
Definition: PluginManager.php:260
PluginManager\GetPluginAttributes
GetPluginAttributes()
Retrieve info about currently loaded plugins.
Definition: PluginManager.php:208
PluginManager\PluginEnabled
PluginEnabled($PluginName, $NewValue=NULL)
Get/set whether specified plugin is enabled.
Definition: PluginManager.php:279
PHP
PHP
Definition: RSSClient--Test.php:12
PluginManager\__construct
__construct($AppFramework, $PluginDirectories)
PluginManager class constructor.
Definition: PluginManager.php:25
PluginManager\GetPluginForCurrentPage
GetPluginForCurrentPage()
Retrieve plugin for current page (if any).
Definition: PluginManager.php:198
PluginManager\GetPlugins
GetPlugins()
Retrieve all loaded plugins.
Definition: PluginManager.php:186
ApplicationFramework\ORDER_LAST
const ORDER_LAST
Handle item last (i.e.
Definition: ApplicationFramework.php:2448
Plugin\SetApplicationFramework
static SetApplicationFramework($AF)
Set the application framework to be referenced within plugins.
Definition: Plugin.php:447
PluginManager\SetConfigValueLoader
static SetConfigValueLoader($Func)
Set function to load plugin configuration values from data.
Definition: PluginManager.php:323
PluginManager\LoadPlugins
LoadPlugins()
Load and initialize plugins.
Definition: PluginManager.php:49
ApplicationFramework\AddObjectDirectory
static AddObjectDirectory($Dir, $NamespacePrefixes=array(), $Callback=NULL)
Add directory to be searched for object files when autoloading.
Definition: ApplicationFramework.php:284