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 $Result = $this->ReadyPlugin($Plugin);
100
101 # if making plugin ready failed
102 if ($Result !== NULL)
103 {
104 # save error messages
105 foreach ($Result as $Msg)
106 {
107 $ErrMsgs[$PluginName][] = $Msg;
108 }
109 }
110 else
111 {
112 # mark plugin as ready
113 $Plugin->IsReady(TRUE);
114 }
115 }
116 }
117
118 # check plugin dependencies again in case an install or upgrade failed
119 $DepErrMsgs = $this->CheckDependencies($this->Plugins, TRUE);
120
121 # for any plugins that were disabled because of dependencies
122 foreach ($DepErrMsgs as $PluginName => $Msgs)
123 {
124 # make sure all plugin hooks are undone
125 $this->UnhookPlugin($this->Plugins[$PluginName]);
126
127 # mark the plugin as unready
128 $this->Plugins[$PluginName]->IsReady(FALSE);
129
130 # record any errors that were reported
131 foreach ($Msgs as $Msg)
132 {
133 $ErrMsgs[$PluginName][] = $Msg;
134 }
135 }
136
137 # save plugin files names and any error messages for later use
138 $this->PluginFiles = $PluginFiles;
139 $this->ErrMsgs = $ErrMsgs;
140
141 # report to caller whether any problems were encountered
142 return count($ErrMsgs) ? FALSE : TRUE;
143 }
144
150 public function GetErrorMessages()
151 {
152 return $this->ErrMsgs;
153 }
154
163 public function GetPlugin($PluginName, $EvenIfNotReady = FALSE)
164 {
165 if (!$EvenIfNotReady && array_key_exists($PluginName, $this->Plugins)
166 && !$this->Plugins[$PluginName]->IsReady())
167 {
168 $Trace = debug_backtrace();
169 $Caller = basename($Trace[0]["file"]).":".$Trace[0]["line"];
170 throw new Exception("Attempt to access uninitialized plugin "
171 .$PluginName." from ".$Caller);
172 }
173 return isset($this->Plugins[$PluginName])
174 ? $this->Plugins[$PluginName] : NULL;
175 }
176
181 public function GetPlugins()
182 {
183 return $this->Plugins;
184 }
185
193 public function GetPluginForCurrentPage()
194 {
195 return $this->GetPlugin($this->PageFilePlugin);
196 }
197
203 public function GetPluginAttributes()
204 {
205 # for each loaded plugin
206 $Info = array();
207 foreach ($this->Plugins as $PluginName => $Plugin)
208 {
209 # retrieve plugin attributes
210 $Info[$PluginName] = $Plugin->GetAttributes();
211
212 # add in other values to attributes
213 $Info[$PluginName]["Enabled"] = $Plugin->IsEnabled();
214 $Info[$PluginName]["Installed"] = $Plugin->IsInstalled();
215 $Info[$PluginName]["ClassFile"] = $this->PluginFiles[$PluginName];
216 }
217
218 # sort plugins by name
219 uasort($Info, function ($A, $B) {
220 $AName = strtoupper($A["Name"]);
221 $BName = strtoupper($B["Name"]);
222 return ($AName == $BName) ? 0
223 : ($AName < $BName) ? -1 : 1;
224 });
225
226 # return plugin info to caller
227 return $Info;
228 }
229
235 public function GetDependents($PluginName)
236 {
237 $Dependents = array();
238 $AllAttribs = $this->GetPluginAttributes();
239 foreach ($AllAttribs as $Name => $Attribs)
240 {
241 if (array_key_exists($PluginName, $Attribs["Requires"]))
242 {
243 $Dependents[] = $Name;
244 $SubDependents = $this->GetDependents($Name);
245 $Dependents = array_merge($Dependents, $SubDependents);
246 }
247 }
248 return $Dependents;
249 }
250
255 public function GetActivePluginList()
256 {
257 $ActivePluginNames = array();
258 foreach ($this->Plugins as $PluginName => $Plugin)
259 {
260 if ($Plugin->IsReady())
261 {
262 $ActivePluginNames[] = $PluginName;
263 }
264 }
265 return $ActivePluginNames;
266 }
267
274 public function PluginEnabled($PluginName, $NewValue = NULL)
275 {
276 return !isset($this->Plugins[$PluginName]) ? FALSE
277 : $this->Plugins[$PluginName]->IsEnabled($NewValue);
278 }
279
285 public function UninstallPlugin($PluginName)
286 {
287 # assume success
288 $Result = NULL;
289
290 # if plugin is installed
291 if ($this->Plugins[$PluginName]->IsInstalled())
292 {
293 # call uninstall method for plugin
294 $Result = $this->Plugins[$PluginName]->Uninstall();
295
296 # if plugin uninstall method succeeded
297 if ($Result === NULL)
298 {
299 # remove plugin info from database
300 $this->DB->Query("DELETE FROM PluginInfo"
301 ." WHERE BaseName = '".addslashes($PluginName)."'");
302
303 # drop our data for the plugin
304 unset($this->Plugins[$PluginName]);
305 unset($this->PluginFiles[$PluginName]);
306 }
307 }
308
309 # report results (if any) to caller
310 return $Result;
311 }
312
313
314 # ---- PRIVATE INTERFACE -------------------------------------------------
315
316 private $AF;
317 private $DB;
318 private $DirsToSearch;
319 private $ErrMsgs = array();
320 private $PageFilePlugin = NULL;
321 private $Plugins = array();
322 private $PluginFiles = array();
323 private $PluginHasDir = array();
324
334 private function FindPlugins($DirsToSearch)
335 {
336 # for each directory
337 $PluginFiles = array();
338 foreach ($DirsToSearch as $Dir)
339 {
340 # if directory exists
341 if (is_dir($Dir))
342 {
343 # for each file in directory
344 $FileNames = scandir($Dir);
345 foreach ($FileNames as $FileName)
346 {
347 # if file looks like base plugin file
348 if (preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*\.php$/", $FileName))
349 {
350 # if we do not already have a plugin with that name
351 $PluginName = substr($FileName, 0, -4);
352 if (!isset($PluginFiles[$PluginName]))
353 {
354 # add file to list
355 $PluginFiles[$PluginName] = $Dir."/".$FileName;
356 }
357 }
358 # else if file looks like plugin directory
359 elseif (is_dir($Dir."/".$FileName)
360 && preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*/", $FileName))
361 {
362 # if there is a base plugin file in the directory
363 $PluginName = $FileName;
364 $PluginFile = $Dir."/".$PluginName."/".$PluginName.".php";
365 if (file_exists($PluginFile))
366 {
367 # add file to list
368 $PluginFiles[$PluginName] = $PluginFile;
369 }
370 else
371 {
372 # record error
373 $this->ErrMsgs[$PluginName][] =
374 "Expected plugin file <i>".$PluginName.".php</i> not"
375 ." found in plugin subdirectory <i>"
376 .$Dir."/".$PluginName."</i>";
377 }
378 }
379 }
380 }
381 }
382
383 # return info about found plugins to caller
384 return $PluginFiles;
385 }
386
394 private function LoadPlugin($PluginName, $PluginFileName)
395 {
396 # bring in plugin class file
397 include_once($PluginFileName);
398
399 # if plugin class was not defined by file
400 if (!class_exists($PluginName))
401 {
402 $ErrMsgs[] = "Expected class <i>".$PluginName
403 ."</i> not found in plugin file <i>"
404 .$PluginFileName."</i>";
405 }
406 else
407 {
408 # if plugin class is not a valid descendant of base plugin class
409 if (!is_subclass_of($PluginName, "Plugin"))
410 {
411 $ErrMsgs[] = "Plugin <b>".$PluginName."</b>"
412 ." could not be loaded because <i>".$PluginName."</i> class"
413 ." was not a subclass of base <i>Plugin</i> class";
414 }
415 else
416 {
417 # instantiate and register the plugin
418 $Plugin = new $PluginName($PluginName);
419
420 # check required plugin attributes
421 $RequiredAttribs = array("Name", "Version");
422 $Attribs = $Plugin->GetAttributes();
423 foreach ($RequiredAttribs as $AttribName)
424 {
425 if (!strlen($Attribs[$AttribName]))
426 {
427 $ErrMsgs[] = "Plugin <b>".$PluginName."</b>"
428 ." could not be loaded because it"
429 ." did not have a <i>"
430 .$AttribName."</i> attribute set.";
431 }
432 }
433
434 # if all required attributes were found
435 if (!isset($ErrMsgs))
436 {
437 # if plugin has its own subdirectory
438 $this->PluginHasDir[$PluginName] = preg_match(
439 "%/".$PluginName."/".$PluginName.".php\$%",
440 $PluginFileName) ? TRUE : FALSE;
441 if ($this->PluginHasDir[$PluginName])
442 {
443 # if plugin has its own object directory
444 $Dir = dirname($PluginFileName);
445 if (is_dir($Dir."/objects"))
446 {
447 # add object directory to class autoloading list
448 ApplicationFramework::AddObjectDirectory($Dir."/objects");
449 }
450 else
451 {
452 # add plugin directory to class autoloading list
453 ApplicationFramework::AddObjectDirectory($Dir);
454 }
455
456 # if plugin has its own interface directory
457 $InterfaceDir = $Dir."/interface";
458 if (is_dir($InterfaceDir))
459 {
460 # scan contents of interface directory for
461 # entries other than the default default
462 $InterfaceSubdirsFound = FALSE;
463 foreach (scandir($InterfaceDir) as $Entry)
464 {
465 if (($Entry != "default") && ($Entry[0] != "."))
466 {
467 $InterfaceSubdirsFound = TRUE;
468 break;
469 }
470 }
471
472 # if entries found other than the default default
473 if ($InterfaceSubdirsFound)
474 {
475 # add directory to those scanned for interfaces
476 $this->AF->AddInterfaceDirectories(
477 array($InterfaceDir."/%ACTIVEUI%/",
478 $InterfaceDir."/%DEFAULTUI%/"));
479 }
480
481 # add plugin interface object directories if present
482 $ActiveUI = $this->AF->ActiveUserInterface();
483 $DefaultUI = $this->AF->DefaultUserInterface();
484 if (is_dir($InterfaceDir."/".$ActiveUI."/objects"))
485 {
486 ApplicationFramework::AddObjectDirectory(
487 $InterfaceDir."/%ACTIVEUI%/objects");
488 }
489 if (is_dir($InterfaceDir."/".$DefaultUI."/objects"))
490 {
491 ApplicationFramework::AddObjectDirectory(
492 $InterfaceDir."/%DEFAULTUI%/objects");
493 }
494
495 # add plugin interface include directories if present
496 if (is_dir($InterfaceDir."/".$DefaultUI."/include"))
497 {
498 $this->AF->AddIncludeDirectories(
499 $InterfaceDir."/%DEFAULTUI%/include");
500 }
501 if (is_dir($InterfaceDir."/".$ActiveUI."/include"))
502 {
503 $this->AF->AddIncludeDirectories(
504 $InterfaceDir."/%ACTIVEUI%/include");
505 }
506 }
507 }
508 }
509 }
510 }
511
512 # return loaded plugin or error messages, as appropriate
513 return isset($ErrMsgs) ? $ErrMsgs : $Plugin;
514 }
515
521 private function ReadyPlugin(&$Plugin)
522 {
523 # install or upgrade plugin if needed
524 $PluginInstalled = $this->InstallPlugin($Plugin);
525
526 # if install/upgrade failed
527 if (is_string($PluginInstalled))
528 {
529 # report errors to caller
530 return array($PluginInstalled);
531 }
532
533 # set up plugin configuration options
534 $ErrMsgs = $Plugin->SetUpConfigOptions();
535
536 # if plugin configuration setup failed
537 if ($ErrMsgs !== NULL)
538 {
539 # report errors to caller
540 return is_array($ErrMsgs) ? $ErrMsgs : array($ErrMsgs);
541 }
542
543 # set default configuration values if necessary
544 if ($PluginInstalled)
545 {
546 $this->SetPluginDefaultConfigValues($Plugin);
547 }
548
549 # initialize the plugin
550 $ErrMsgs = $Plugin->Initialize();
551
552 # if initialization failed
553 if ($ErrMsgs !== NULL)
554 {
555 # report errors to caller
556 return is_array($ErrMsgs) ? $ErrMsgs : array($ErrMsgs);
557 }
558
559 # register and hook any events for plugin
560 $ErrMsgs = $this->HookPlugin($Plugin);
561
562 # make sure all hooks are undone if hooking failed
563 if ($ErrMsgs !== NULL)
564 {
565 $this->UnhookPlugin($Plugin);
566 }
567
568 # report result to caller
569 return $ErrMsgs;
570 }
571
577 private function HookPlugin(&$Plugin)
578 {
579 # register any events declared by plugin
580 $Events = $Plugin->DeclareEvents();
581 if (count($Events)) { $this->AF->RegisterEvent($Events); }
582
583 # if plugin has events that need to be hooked
584 $EventsToHook = $Plugin->HookEvents();
585 if (count($EventsToHook))
586 {
587 # for each event
588 $ErrMsgs = array();
589 foreach ($EventsToHook as $EventName => $PluginMethods)
590 {
591 # for each method to hook for the event
592 if (!is_array($PluginMethods))
593 { $PluginMethods = array($PluginMethods); }
594 foreach ($PluginMethods as $PluginMethod)
595 {
596 # if the event only allows static callbacks
597 if ($this->AF->IsStaticOnlyEvent($EventName))
598 {
599 # hook event with shell for static callback
600 $Caller = new PluginCaller(
601 $Plugin->GetBaseName(), $PluginMethod);
602 $Result = $this->AF->HookEvent(
603 $EventName,
604 array($Caller, "CallPluginMethod"));
605 }
606 else
607 {
608 # hook event
609 $Result = $this->AF->HookEvent(
610 $EventName, array($Plugin, $PluginMethod));
611 }
612
613 # record any errors
614 if ($Result === FALSE)
615 {
616 $ErrMsgs[] = "Unable to hook requested event <i>"
617 .$EventName."</i> for plugin <b>"
618 .$Plugin->GetBaseName()."</b>";
619 }
620 }
621 }
622
623 # if event hook setup failed
624 if (count($ErrMsgs))
625 {
626 # report errors to caller
627 return $ErrMsgs;
628 }
629 }
630
631 # report success to caller
632 return NULL;
633 }
634
639 private function UnhookPlugin(&$Plugin)
640 {
641 # if plugin had events to hook
642 $EventsToHook = $Plugin->HookEvents();
643 if (count($EventsToHook))
644 {
645 # for each event
646 $ErrMsgs = array();
647 foreach ($EventsToHook as $EventName => $PluginMethods)
648 {
649 # for each method to hook for the event
650 if (!is_array($PluginMethods))
651 { $PluginMethods = array($PluginMethods); }
652 foreach ($PluginMethods as $PluginMethod)
653 {
654 # if the event only allows static callbacks
655 if ($this->AF->IsStaticOnlyEvent($EventName))
656 {
657 # unhook event with shell for static callback
658 $Caller = new PluginCaller(
659 $Plugin->GetBaseName(), $PluginMethod);
660 $this->AF->UnhookEvent($EventName,
661 array($Caller, "CallPluginMethod"));
662 }
663 else
664 {
665 # unhook event
666 $this->AF->UnhookEvent(
667 $EventName, array($Plugin, $PluginMethod));
668 }
669 }
670 }
671 }
672 }
673
681 private function InstallPlugin(&$Plugin)
682 {
683 # if plugin has not been installed
684 $InstallOrUpgradePerformed = FALSE;
685 $PluginName = $Plugin->GetBaseName();
686 $Attribs = $Plugin->GetAttributes();
687 $LockName = __CLASS__.":Install:".$PluginName;
688 if (!$Plugin->IsInstalled())
689 {
690 # set default values if present
691 $this->SetPluginDefaultConfigValues($Plugin, TRUE);
692
693 # try to get lock to prevent anyone else from trying to run
694 # install or upgrade at the same time
695 $GotLock = $this->AF->GetLock($LockName, FALSE);
696
697 # if could not get lock
698 if (!$GotLock)
699 {
700 # return error
701 return "Installation of plugin <b>"
702 .$PluginName."</b> in progress.";
703 }
704
705 # install plugin
706 $ErrMsg = $Plugin->Install();
707 $InstallOrUpgradePerformed = TRUE;
708
709 # if install succeeded
710 if ($ErrMsg == NULL)
711 {
712 # mark plugin as installed
713 $Plugin->IsInstalled(TRUE);
714
715 # release lock
716 $this->AF->ReleaseLock($LockName);
717 }
718 else
719 {
720 # release lock
721 $this->AF->ReleaseLock($LockName);
722
723 # return error message about installation failure
724 return "Installation of plugin <b>"
725 .$PluginName."</b> failed: <i>".$ErrMsg."</i>";
726 }
727 }
728 else
729 {
730 # if plugin version is newer than version in database
731 if (version_compare($Attribs["Version"],
732 $Plugin->InstalledVersion()) == 1)
733 {
734 # set default values for any new configuration settings
735 $this->SetPluginDefaultConfigValues($Plugin);
736
737 # try to get lock to prevent anyone else from trying to run
738 # upgrade or install at the same time
739 $GotLock = $this->AF->GetLock($LockName, FALSE);
740
741 # if could not get lock
742 if (!$GotLock)
743 {
744 # return error
745 return "Upgrade of plugin <b>"
746 .$PluginName."</b> in progress.";
747 }
748
749 # upgrade plugin
750 $ErrMsg = $Plugin->Upgrade($Plugin->InstalledVersion());
751 $InstallOrUpgradePerformed = TRUE;
752
753 # if upgrade succeeded
754 if ($ErrMsg === NULL)
755 {
756 # update plugin version in database
757 $Plugin->InstalledVersion($Attribs["Version"]);
758
759 # release lock
760 $this->AF->ReleaseLock($LockName);
761 }
762 else
763 {
764 # release lock
765 $this->AF->ReleaseLock($LockName);
766
767 # report error message about upgrade failure
768 return "Upgrade of plugin <b>"
769 .$PluginName."</b> from version <i>"
770 .addslashes($Plugin->InstalledVersion())
771 ."</i> to version <i>"
772 .addslashes($Attribs["Version"])."</i> failed: <i>"
773 .$ErrMsg."</i>";
774 }
775 }
776 # else if plugin version is older than version in database
777 elseif (version_compare($Attribs["Version"],
778 $Plugin->InstalledVersion()) == -1)
779 {
780 # return error message about version conflict
781 return "Plugin <b>"
782 .$PluginName."</b> is older (<i>"
783 .addslashes($Attribs["Version"])
784 ."</i>) than previously-installed version (<i>"
785 .addslashes($Plugin->InstalledVersion())
786 ."</i>).";
787 }
788 }
789
790 # report result to caller
791 return $InstallOrUpgradePerformed;
792 }
793
801 private function SetPluginDefaultConfigValues($Plugin, $Overwrite = FALSE)
802 {
803 # if plugin has configuration info
804 $Attribs = $Plugin->GetAttributes();
805 if (isset($Attribs["CfgSetup"]))
806 {
807 foreach ($Attribs["CfgSetup"] as $CfgValName => $CfgSetup)
808 {
809 if (isset($CfgSetup["Default"]) && ($Overwrite
810 || ($Plugin->ConfigSetting($CfgValName) === NULL)))
811 {
812 $Plugin->ConfigSetting($CfgValName, $CfgSetup["Default"]);
813 }
814 }
815 }
816 }
817
825 private function CheckDependencies($Plugins, $CheckReady = FALSE)
826 {
827 # look until all enabled plugins check out okay
828 $ErrMsgs = array();
829 do
830 {
831 # start out assuming all plugins are okay
832 $AllOkay = TRUE;
833
834 # for each plugin
835 foreach ($Plugins as $PluginName => $Plugin)
836 {
837 # if plugin is enabled and not checking for ready
838 # or plugin is ready
839 if ($Plugin->IsEnabled() && (!$CheckReady || $Plugin->IsReady()))
840 {
841 # load plugin attributes
842 if (!isset($Attribs[$PluginName]))
843 {
844 $Attribs[$PluginName] = $Plugin->GetAttributes();
845 }
846
847 # for each dependency for this plugin
848 foreach ($Attribs[$PluginName]["Requires"]
849 as $ReqName => $ReqVersion)
850 {
851 # handle PHP version requirements
852 if ($ReqName == "PHP")
853 {
854 if (version_compare($ReqVersion, phpversion(), ">"))
855 {
856 $ErrMsgs[$PluginName][] = "PHP version "
857 ."<i>".$ReqVersion."</i>"
858 ." required by <b>".$PluginName."</b>"
859 ." was not available. (Current PHP version"
860 ." is <i>".phpversion()."</i>.)";
861 }
862 }
863 # handle PHP extension requirements
864 elseif (preg_match("/^PHPX_/", $ReqName))
865 {
866 list($Dummy, $ExtensionName) = explode("_", $ReqName, 2);
867 if (!extension_loaded($ExtensionName))
868 {
869 $ErrMsgs[$PluginName][] = "PHP extension "
870 ."<i>".$ExtensionName."</i>"
871 ." required by <b>".$PluginName."</b>"
872 ." was not available.";
873 }
874 elseif (($ReqVersion !== TRUE)
875 && (phpversion($ExtensionName) !== FALSE)
876 && version_compare($ReqVersion,
877 phpversion($ExtensionName), ">"))
878 {
879 $ErrMsgs[$PluginName][] = "PHP extension "
880 ."<i>".$ExtensionName."</i>"
881 ." version <i>".$ReqVersion."</i>"
882 ." required by <b>".$PluginName."</b>"
883 ." was not available. (Current version"
884 ." of extension <i>".$ExtensionName."</i>"
885 ." is <i>".phpversion($ExtensionName)."</i>.)";
886 }
887 }
888 # handle dependencies on other plugins
889 else
890 {
891 # load plugin attributes if not already loaded
892 if (isset($Plugins[$ReqName])
893 && !isset($Attribs[$ReqName]))
894 {
895 $Attribs[$ReqName] =
896 $Plugins[$ReqName]->GetAttributes();
897 }
898
899 # if target plugin is not present or is too old
900 # or is not enabled
901 # or (if appropriate) is not ready
902 if (!isset($Plugins[$ReqName])
903 || version_compare($ReqVersion,
904 $Attribs[$ReqName]["Version"], ">")
905 || !$Plugins[$ReqName]->IsEnabled()
906 || ($CheckReady
907 && !$Plugins[$ReqName]->IsReady()))
908 {
909 # add error message
910 $ErrMsgs[$PluginName][] = "Plugin <i>"
911 .$ReqName." ".$ReqVersion."</i>"
912 ." required by <b>".$PluginName."</b>"
913 ." was not available.";
914 }
915 }
916
917 # if problem was found with plugin
918 if (isset($ErrMsgs[$PluginName]))
919 {
920 # remove plugin from our list
921 unset($Plugins[$PluginName]);
922
923 # set flag to indicate a plugin had to be dropped
924 $AllOkay = FALSE;
925 }
926 }
927 }
928 }
929 } while ($AllOkay == FALSE);
930
931 # return messages about any dropped plugins back to caller
932 return $ErrMsgs;
933 }
934
943 private function SortPluginsByInitializationPrecedence($Plugins)
944 {
945 # load plugin attributes
946 foreach ($Plugins as $PluginName => $Plugin)
947 {
948 $PluginAttribs[$PluginName] = $Plugin->GetAttributes();
949 }
950
951 # determine initialization order
952 $PluginsAfterUs = array();
953 foreach ($PluginAttribs as $PluginName => $Attribs)
954 {
955 foreach ($Attribs["InitializeBefore"] as $OtherPluginName)
956 {
957 $PluginsAfterUs[$PluginName][] = $OtherPluginName;
958 }
959 foreach ($Attribs["InitializeAfter"] as $OtherPluginName)
960 {
961 $PluginsAfterUs[$OtherPluginName][] = $PluginName;
962 }
963 }
964
965 # infer other initialization order cues from lists of required plugins
966 foreach ($PluginAttribs as $PluginName => $Attribs)
967 {
968 # for each required plugin
969 foreach ($Attribs["Requires"]
970 as $RequiredPluginName => $RequiredPluginVersion)
971 {
972 # skip the requirement if it it not for another known plugin
973 if (!isset($PluginAttribs[$RequiredPluginName]))
974 {
975 continue;
976 }
977
978 # if there is not a requirement in the opposite direction
979 if (!array_key_exists($PluginName,
980 $PluginAttribs[$RequiredPluginName]["Requires"]))
981 {
982 # if the required plugin is not scheduled to be after us
983 if (!array_key_exists($PluginName, $PluginsAfterUs)
984 || !in_array($RequiredPluginName,
985 $PluginsAfterUs[$PluginName]))
986 {
987 # if we are not already scheduled to be after the required plugin
988 if (!array_key_exists($PluginName, $PluginsAfterUs)
989 || !in_array($RequiredPluginName,
990 $PluginsAfterUs[$PluginName]))
991 {
992 # schedule us to be after the required plugin
993 $PluginsAfterUs[$RequiredPluginName][] =
994 $PluginName;
995 }
996 }
997 }
998 }
999 }
1000
1001 # keep track of those plugins we have yet to do and those that are done
1002 $UnsortedPlugins = array_keys($Plugins);
1003 $PluginsProcessed = array();
1004
1005 # limit the number of iterations of the plugin ordering loop
1006 # to 10 times the number of plugins we have
1007 $MaxIterations = 10 * count($UnsortedPlugins);
1008 $IterationCount = 0;
1009
1010 # iterate through all the plugins that need processing
1011 while (($NextPlugin = array_shift($UnsortedPlugins)) !== NULL)
1012 {
1013 # check to be sure that we're not looping forever
1014 $IterationCount++;
1015 if ($IterationCount > $MaxIterations)
1016 {
1017 throw new Exception(
1018 "Max iteration count exceeded trying to determine plugin"
1019 ." loading order. Is there a dependency loop?");
1020 }
1021
1022 # if no plugins require this one, it can go last
1023 if (!isset($PluginsAfterUs[$NextPlugin]))
1024 {
1025 $PluginsProcessed[$NextPlugin] = $MaxIterations;
1026 }
1027 else
1028 {
1029 # for plugins that are required by others
1030 $Index = $MaxIterations;
1031 foreach ($PluginsAfterUs[$NextPlugin] as $GoBefore)
1032 {
1033 if (!isset($PluginsProcessed[$GoBefore]))
1034 {
1035 # if there is something that requires us which hasn't
1036 # yet been assigned an order, then we can't determine
1037 # our own place on this iteration
1038 array_push($UnsortedPlugins, $NextPlugin);
1039 continue 2;
1040 }
1041 else
1042 {
1043 # otherwise, make sure that we're loaded
1044 # before the earliest of the things that require us
1045 $Index = min($Index, $PluginsProcessed[$GoBefore] - 1);
1046 }
1047 }
1048 $PluginsProcessed[$NextPlugin] = $Index;
1049 }
1050 }
1051
1052 # arrange plugins according to our ordering
1053 asort($PluginsProcessed, SORT_NUMERIC);
1054 $SortedPlugins = array();
1055 foreach ($PluginsProcessed as $PluginName => $SortOrder)
1056 {
1057 $SortedPlugins[$PluginName] = $Plugins[$PluginName];
1058 }
1059
1060 # return sorted list to caller
1061 return $SortedPlugins;
1062 }
1063
1072 public function FindPluginPhpFile($PageName)
1073 {
1074 # build list of possible locations for file
1075 $Locations = array(
1076 "local/plugins/%PLUGIN%/pages/%PAGE%.php",
1077 "plugins/%PLUGIN%/pages/%PAGE%.php",
1078 "local/plugins/%PLUGIN%/%PAGE%.php",
1079 "plugins/%PLUGIN%/%PAGE%.php",
1080 );
1081
1082 # look for file and return (possibly) updated page to caller
1083 return $this->FindPluginPageFile($PageName, $Locations);
1084 }
1095 public function FindPluginHtmlFile($PageName)
1096 {
1097 # build list of possible locations for file
1098 $Locations = array(
1099 "local/plugins/%PLUGIN%/interface/%ACTIVEUI%/%PAGE%.html",
1100 "plugins/%PLUGIN%/interface/%ACTIVEUI%/%PAGE%.html",
1101 "local/plugins/%PLUGIN%/interface/%DEFAULTUI%/%PAGE%.html",
1102 "plugins/%PLUGIN%/interface/%DEFAULTUI%/%PAGE%.html",
1103 "local/plugins/%PLUGIN%/%PAGE%.html",
1104 "plugins/%PLUGIN%/%PAGE%.html",
1105 );
1106
1107 # find HTML file
1108 $Params = $this->FindPluginPageFile($PageName, $Locations);
1109
1110 # if plugin HTML file was found
1111 if ($Params["PageName"] != $PageName)
1112 {
1113 # add subdirectories for plugin to search paths
1114 $Dir = preg_replace("%^local/%", "", dirname($Params["PageName"]));
1115 $this->AF->AddImageDirectories(array(
1116 "local/".$Dir."/images",
1117 $Dir."/images",
1118 ));
1119 $this->AF->AddIncludeDirectories(array(
1120 "local/".$Dir."/include",
1121 $Dir."/include",
1122 ));
1123 $this->AF->AddFunctionDirectories(array(
1124 "local/".$Dir."/include",
1125 $Dir."/include",
1126 ));
1127 }
1128
1129 # return possibly revised HTML file name to caller
1130 return $Params;
1131 }
1144 private function FindPluginPageFile($PageName, $Locations)
1145 {
1146 # set up return value assuming we will not find plugin page file
1147 $ReturnValue["PageName"] = $PageName;
1148
1149 # look for plugin name and plugin page name in base page name
1150 preg_match("/P_([A-Za-z].[A-Za-z0-9]*)_([A-Za-z0-9_-]+)/", $PageName, $Matches);
1151
1152 # if plugin name and plugin page name were found and plugin name is valid
1153 if (count($Matches) == 3)
1154 {
1155 # if plugin is valid and enabled and has its own subdirectory
1156 $PluginName = $Matches[1];
1157 if (isset($this->Plugins[$PluginName])
1158 && $this->PluginHasDir[$PluginName]
1159 && $this->Plugins[$PluginName]->IsEnabled())
1160 {
1161 # for each possible location
1162 $PageName = $Matches[2];
1163 $ActiveUI = $this->AF->ActiveUserInterface();
1164 $DefaultUI = $this->AF->DefaultUserInterface();
1165 foreach ($Locations as $Loc)
1166 {
1167 # make any needed substitutions into path
1168 $FileName = str_replace(
1169 array("%DEFAULTUI%", "%ACTIVEUI%", "%PLUGIN%", "%PAGE%"),
1170 array($DefaultUI, $ActiveUI, $PluginName, $PageName),
1171 $Loc);
1172
1173 # if file exists in this location
1174 if (file_exists($FileName))
1175 {
1176 # set return value to contain full plugin page file name
1177 $ReturnValue["PageName"] = $FileName;
1178
1179 # save plugin name as home of current page
1180 $this->PageFilePlugin = $PluginName;
1181
1182 # set G_Plugin to plugin associated with current page
1183 $GLOBALS["G_Plugin"] = $this->GetPluginForCurrentPage();
1184
1185 # stop looking
1186 break;
1187 }
1188 }
1189 }
1190 }
1191
1192 # return array containing page name or page file name to caller
1193 return $ReturnValue;
1194 }
1195 }
1196
1208 class PluginCaller
1209 {
1210
1217 public function __construct($PluginName, $MethodName)
1218 {
1219 $this->PluginName = $PluginName;
1220 $this->MethodName = $MethodName;
1221 }
1222
1228 public function CallPluginMethod()
1229 {
1230 $Args = func_get_args();
1231 $Plugin = self::$Manager->GetPlugin($this->PluginName);
1232 return call_user_func_array(array($Plugin, $this->MethodName), $Args);
1233 }
1234
1239 public function GetCallbackAsText()
1240 {
1241 return $this->PluginName."::".$this->MethodName;
1242 }
1243
1249 public function __sleep()
1250 {
1251 return array("PluginName", "MethodName");
1252 }
1253
1255 static public $Manager;
1256
1257 private $PluginName;
1258 private $MethodName;
1259 }
1262 ?>
PluginManager\GetErrorMessages
GetErrorMessages()
Retrieve any error messages generated during plugin loading.
Definition: PluginManager.php:150
PluginManager
Manager to load and invoke plugins.
Definition: PluginManager.php:13
PluginManager\GetPlugin
GetPlugin($PluginName, $EvenIfNotReady=FALSE)
Retrieve specified plugin.
Definition: PluginManager.php:163
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:285
PluginManager\GetDependents
GetDependents($PluginName)
Returns a list of plugins dependent on the specified plugin.
Definition: PluginManager.php:235
ApplicationFramework\AddObjectDirectory
static AddObjectDirectory($Dir, $Prefix="", $ClassPattern=NULL, $ClassReplacement=NULL)
Add directory to be searched for object files when autoloading.
Definition: ApplicationFramework.php:225
PluginManager\GetActivePluginList
GetActivePluginList()
Get list of active (i.e.
Definition: PluginManager.php:255
PluginManager\GetPluginAttributes
GetPluginAttributes()
Retrieve info about currently loaded plugins.
Definition: PluginManager.php:203
PluginManager\PluginEnabled
PluginEnabled($PluginName, $NewValue=NULL)
Get/set whether specified plugin is enabled.
Definition: PluginManager.php:274
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:193
PluginManager\GetPlugins
GetPlugins()
Retrieve all loaded plugins.
Definition: PluginManager.php:181
ApplicationFramework\ORDER_LAST
const ORDER_LAST
Run hooked function last (i.e.
Definition: ApplicationFramework.php:2253
Plugin\SetApplicationFramework
static SetApplicationFramework($AF)
Set the application framework to be referenced within plugins.
Definition: Plugin.php:447
PluginManager\LoadPlugins
LoadPlugins()
Load and initialize plugins.
Definition: PluginManager.php:49