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