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
318 static public function SetConfigValueLoader($Func)
319 {
320 if (!is_callable($Func))
321 {
322 throw new InvalidArgumentException(
323 "Invalid configuration value loading function supplied.");
324 }
325 self::$CfgValueLoader = $Func;
326 }
327
328
329 # ---- PRIVATE INTERFACE -------------------------------------------------
330
331 private $AF;
332 private $DB;
333 private $DirsToSearch;
334 private $ErrMsgs = array();
335 private $PageFilePlugin = NULL;
336 private $Plugins = array();
337 private $PluginFiles = array();
338 private $PluginHasDir = array();
339
340 static private $CfgValueLoader;
341
351 private function FindPlugins($DirsToSearch)
352 {
353 # for each directory
354 $PluginFiles = array();
355 foreach ($DirsToSearch as $Dir)
356 {
357 # if directory exists
358 if (is_dir($Dir))
359 {
360 # for each file in directory
361 $FileNames = scandir($Dir);
362 foreach ($FileNames as $FileName)
363 {
364 # if file looks like base plugin file
365 if (preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*\.php$/", $FileName))
366 {
367 # if we do not already have a plugin with that name
368 $PluginName = substr($FileName, 0, -4);
369 if (!isset($PluginFiles[$PluginName]))
370 {
371 # add file to list
372 $PluginFiles[$PluginName] = $Dir."/".$FileName;
373 }
374 }
375 # else if file looks like plugin directory
376 elseif (is_dir($Dir."/".$FileName)
377 && preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*/", $FileName))
378 {
379 # if there is a base plugin file in the directory
380 $PluginName = $FileName;
381 $PluginFile = $Dir."/".$PluginName."/".$PluginName.".php";
382 if (file_exists($PluginFile))
383 {
384 # add file to list
385 $PluginFiles[$PluginName] = $PluginFile;
386 }
387 else
388 {
389 # record error
390 $this->ErrMsgs[$PluginName][] =
391 "Expected plugin file <i>".$PluginName.".php</i> not"
392 ." found in plugin subdirectory <i>"
393 .$Dir."/".$PluginName."</i>";
394 }
395 }
396 }
397 }
398 }
399
400 # return info about found plugins to caller
401 return $PluginFiles;
402 }
403
411 private function LoadPlugin($PluginName, $PluginFileName)
412 {
413 # bring in plugin class file
414 include_once($PluginFileName);
415
416 # if plugin class was not defined by file
417 if (!class_exists($PluginName))
418 {
419 $ErrMsgs[] = "Expected class <i>".$PluginName
420 ."</i> not found in plugin file <i>"
421 .$PluginFileName."</i>";
422 }
423 else
424 {
425 # if plugin class is not a valid descendant of base plugin class
426 if (!is_subclass_of($PluginName, "Plugin"))
427 {
428 $ErrMsgs[] = "Plugin <b>".$PluginName."</b>"
429 ." could not be loaded because <i>".$PluginName."</i> class"
430 ." was not a subclass of base <i>Plugin</i> class";
431 }
432 else
433 {
434 # instantiate and register the plugin
435 $Plugin = new $PluginName($PluginName);
436
437 # check required plugin attributes
438 $RequiredAttribs = array("Name", "Version");
439 $Attribs = $Plugin->GetAttributes();
440 foreach ($RequiredAttribs as $AttribName)
441 {
442 if (!strlen($Attribs[$AttribName]))
443 {
444 $ErrMsgs[] = "Plugin <b>".$PluginName."</b>"
445 ." could not be loaded because it"
446 ." did not have a <i>"
447 .$AttribName."</i> attribute set.";
448 }
449 }
450
451 # if all required attributes were found
452 if (!isset($ErrMsgs))
453 {
454 # if plugin has its own subdirectory
455 $this->PluginHasDir[$PluginName] = preg_match(
456 "%/".$PluginName."/".$PluginName.".php\$%",
457 $PluginFileName) ? TRUE : FALSE;
458 if ($this->PluginHasDir[$PluginName])
459 {
460 # if plugin has its own object directory
461 $Dir = dirname($PluginFileName);
462 if (is_dir($Dir."/objects"))
463 {
464 # add object directory to class autoloading list
465 ApplicationFramework::AddObjectDirectory($Dir."/objects");
466 }
467 else
468 {
469 # add plugin directory to class autoloading list
470 ApplicationFramework::AddObjectDirectory($Dir);
471 }
472
473 # if plugin has its own interface directory
474 $InterfaceDir = $Dir."/interface";
475 if (is_dir($InterfaceDir))
476 {
477 # scan contents of interface directory for
478 # entries other than the default default
479 $InterfaceSubdirsFound = FALSE;
480 foreach (scandir($InterfaceDir) as $Entry)
481 {
482 if (($Entry != "default") && ($Entry[0] != "."))
483 {
484 $InterfaceSubdirsFound = TRUE;
485 break;
486 }
487 }
488
489 # if entries found other than the default default
490 if ($InterfaceSubdirsFound)
491 {
492 # add directory to those scanned for interfaces
493 $this->AF->AddInterfaceDirectories(
494 array($InterfaceDir."/%ACTIVEUI%/",
495 $InterfaceDir."/%DEFAULTUI%/"));
496 }
497
498 # add plugin interface object directories if present
499 $ActiveUI = $this->AF->ActiveUserInterface();
500 $DefaultUI = $this->AF->DefaultUserInterface();
501 if (is_dir($InterfaceDir."/".$ActiveUI."/objects"))
502 {
503 ApplicationFramework::AddObjectDirectory(
504 $InterfaceDir."/%ACTIVEUI%/objects");
505 }
506 if (is_dir($InterfaceDir."/".$DefaultUI."/objects"))
507 {
508 ApplicationFramework::AddObjectDirectory(
509 $InterfaceDir."/%DEFAULTUI%/objects");
510 }
511
512 # add plugin interface include directories if present
513 if (is_dir($InterfaceDir."/".$DefaultUI."/include"))
514 {
515 $this->AF->AddIncludeDirectories(
516 $InterfaceDir."/%DEFAULTUI%/include");
517 }
518 if (is_dir($InterfaceDir."/".$ActiveUI."/include"))
519 {
520 $this->AF->AddIncludeDirectories(
521 $InterfaceDir."/%ACTIVEUI%/include");
522 }
523 }
524 }
525 }
526 }
527 }
528
529 # return loaded plugin or error messages, as appropriate
530 return isset($ErrMsgs) ? $ErrMsgs : $Plugin;
531 }
532
538 private function ReadyPlugin(&$Plugin)
539 {
540 # install or upgrade plugin if needed
541 $PluginInstalled = $this->InstallPlugin($Plugin);
542
543 # if install/upgrade failed
544 if (is_string($PluginInstalled))
545 {
546 # report errors to caller
547 return array($PluginInstalled);
548 }
549
550 # set up plugin configuration options
551 $ErrMsgs = $Plugin->SetUpConfigOptions();
552
553 # if plugin configuration setup failed
554 if ($ErrMsgs !== NULL)
555 {
556 # report errors to caller
557 return is_array($ErrMsgs) ? $ErrMsgs : array($ErrMsgs);
558 }
559
560 # set default configuration values if necessary
561 if ($PluginInstalled)
562 {
563 $this->SetPluginDefaultConfigValues($Plugin);
564 }
565
566 # initialize the plugin
567 $ErrMsgs = $Plugin->Initialize();
568
569 # if initialization failed
570 if ($ErrMsgs !== NULL)
571 {
572 # report errors to caller
573 return is_array($ErrMsgs) ? $ErrMsgs : array($ErrMsgs);
574 }
575
576 # register and hook any events for plugin
577 $ErrMsgs = $this->HookPlugin($Plugin);
578
579 # make sure all hooks are undone if hooking failed
580 if ($ErrMsgs !== NULL)
581 {
582 $this->UnhookPlugin($Plugin);
583 }
584
585 # report result to caller
586 return $ErrMsgs;
587 }
588
594 private function HookPlugin(&$Plugin)
595 {
596 # register any events declared by plugin
597 $Events = $Plugin->DeclareEvents();
598 if (count($Events)) { $this->AF->RegisterEvent($Events); }
599
600 # if plugin has events that need to be hooked
601 $EventsToHook = $Plugin->HookEvents();
602 if (count($EventsToHook))
603 {
604 # for each event
605 $ErrMsgs = array();
606 foreach ($EventsToHook as $EventName => $PluginMethods)
607 {
608 # for each method to hook for the event
609 if (!is_array($PluginMethods))
610 { $PluginMethods = array($PluginMethods); }
611 foreach ($PluginMethods as $PluginMethod)
612 {
613 # if the event only allows static callbacks
614 if ($this->AF->IsStaticOnlyEvent($EventName))
615 {
616 # hook event with shell for static callback
617 $Caller = new PluginCaller(
618 $Plugin->GetBaseName(), $PluginMethod);
619 $Result = $this->AF->HookEvent(
620 $EventName,
621 array($Caller, "CallPluginMethod"));
622 }
623 else
624 {
625 # hook event
626 $Result = $this->AF->HookEvent(
627 $EventName, array($Plugin, $PluginMethod));
628 }
629
630 # record any errors
631 if ($Result === FALSE)
632 {
633 $ErrMsgs[] = "Unable to hook requested event <i>"
634 .$EventName."</i> for plugin <b>"
635 .$Plugin->GetBaseName()."</b>";
636 }
637 }
638 }
639
640 # if event hook setup failed
641 if (count($ErrMsgs))
642 {
643 # report errors to caller
644 return $ErrMsgs;
645 }
646 }
647
648 # report success to caller
649 return NULL;
650 }
651
656 private function UnhookPlugin(&$Plugin)
657 {
658 # if plugin had events to hook
659 $EventsToHook = $Plugin->HookEvents();
660 if (count($EventsToHook))
661 {
662 # for each event
663 $ErrMsgs = array();
664 foreach ($EventsToHook as $EventName => $PluginMethods)
665 {
666 # for each method to hook for the event
667 if (!is_array($PluginMethods))
668 { $PluginMethods = array($PluginMethods); }
669 foreach ($PluginMethods as $PluginMethod)
670 {
671 # if the event only allows static callbacks
672 if ($this->AF->IsStaticOnlyEvent($EventName))
673 {
674 # unhook event with shell for static callback
675 $Caller = new PluginCaller(
676 $Plugin->GetBaseName(), $PluginMethod);
677 $this->AF->UnhookEvent($EventName,
678 array($Caller, "CallPluginMethod"));
679 }
680 else
681 {
682 # unhook event
683 $this->AF->UnhookEvent(
684 $EventName, array($Plugin, $PluginMethod));
685 }
686 }
687 }
688 }
689 }
690
698 private function InstallPlugin(&$Plugin)
699 {
700 # if plugin has not been installed
701 $InstallOrUpgradePerformed = FALSE;
702 $PluginName = $Plugin->GetBaseName();
703 $Attribs = $Plugin->GetAttributes();
704 $LockName = __CLASS__.":Install:".$PluginName;
705 if (!$Plugin->IsInstalled())
706 {
707 # set default values if present
708 $this->SetPluginDefaultConfigValues($Plugin, TRUE);
709
710 # try to get lock to prevent anyone else from trying to run
711 # install or upgrade at the same time
712 $GotLock = $this->AF->GetLock($LockName, FALSE);
713
714 # if could not get lock
715 if (!$GotLock)
716 {
717 # return error
718 return "Installation of plugin <b>"
719 .$PluginName."</b> in progress.";
720 }
721
722 # install plugin
723 $ErrMsg = $Plugin->Install();
724 $InstallOrUpgradePerformed = TRUE;
725
726 # if install succeeded
727 if ($ErrMsg == NULL)
728 {
729 # mark plugin as installed
730 $Plugin->IsInstalled(TRUE);
731
732 # release lock
733 $this->AF->ReleaseLock($LockName);
734 }
735 else
736 {
737 # release lock
738 $this->AF->ReleaseLock($LockName);
739
740 # return error message about installation failure
741 return "Installation of plugin <b>"
742 .$PluginName."</b> failed: <i>".$ErrMsg."</i>";
743 }
744 }
745 else
746 {
747 # if plugin version is newer than version in database
748 if (version_compare($Attribs["Version"],
749 $Plugin->InstalledVersion()) == 1)
750 {
751 # set default values for any new configuration settings
752 $this->SetPluginDefaultConfigValues($Plugin);
753
754 # try to get lock to prevent anyone else from trying to run
755 # upgrade or install at the same time
756 $GotLock = $this->AF->GetLock($LockName, FALSE);
757
758 # if could not get lock
759 if (!$GotLock)
760 {
761 # return error
762 return "Upgrade of plugin <b>"
763 .$PluginName."</b> in progress.";
764 }
765
766 # upgrade plugin
767 $ErrMsg = $Plugin->Upgrade($Plugin->InstalledVersion());
768 $InstallOrUpgradePerformed = TRUE;
769
770 # if upgrade succeeded
771 if ($ErrMsg === NULL)
772 {
773 # update plugin version in database
774 $Plugin->InstalledVersion($Attribs["Version"]);
775
776 # release lock
777 $this->AF->ReleaseLock($LockName);
778 }
779 else
780 {
781 # release lock
782 $this->AF->ReleaseLock($LockName);
783
784 # report error message about upgrade failure
785 return "Upgrade of plugin <b>"
786 .$PluginName."</b> from version <i>"
787 .addslashes($Plugin->InstalledVersion())
788 ."</i> to version <i>"
789 .addslashes($Attribs["Version"])."</i> failed: <i>"
790 .$ErrMsg."</i>";
791 }
792 }
793 # else if plugin version is older than version in database
794 elseif (version_compare($Attribs["Version"],
795 $Plugin->InstalledVersion()) == -1)
796 {
797 # return error message about version conflict
798 return "Plugin <b>"
799 .$PluginName."</b> is older (<i>"
800 .addslashes($Attribs["Version"])
801 ."</i>) than previously-installed version (<i>"
802 .addslashes($Plugin->InstalledVersion())
803 ."</i>).";
804 }
805 }
806
807 # report result to caller
808 return $InstallOrUpgradePerformed;
809 }
810
818 private function SetPluginDefaultConfigValues($Plugin, $Overwrite = FALSE)
819 {
820 # if plugin has configuration info
821 $Attribs = $Plugin->GetAttributes();
822 if (isset($Attribs["CfgSetup"]))
823 {
824 foreach ($Attribs["CfgSetup"] as $CfgValName => $CfgSetup)
825 {
826 if (isset($CfgSetup["Default"]) && ($Overwrite
827 || ($Plugin->ConfigSetting($CfgValName) === NULL)))
828 {
829 if (isset(self::$CfgValueLoader))
830 {
831 $Plugin->ConfigSetting($CfgValName,
832 call_user_func(self::$CfgValueLoader,
833 $CfgSetup["Type"], $CfgSetup["Default"]));
834 }
835 else
836 {
837 $Plugin->ConfigSetting($CfgValName, $CfgSetup["Default"]);
838 }
839 }
840 }
841 }
842 }
843
851 private function CheckDependencies($Plugins, $CheckReady = FALSE)
852 {
853 # look until all enabled plugins check out okay
854 $ErrMsgs = array();
855 do
856 {
857 # start out assuming all plugins are okay
858 $AllOkay = TRUE;
859
860 # for each plugin
861 foreach ($Plugins as $PluginName => $Plugin)
862 {
863 # if plugin is enabled and not checking for ready
864 # or plugin is ready
865 if ($Plugin->IsEnabled() && (!$CheckReady || $Plugin->IsReady()))
866 {
867 # load plugin attributes
868 if (!isset($Attribs[$PluginName]))
869 {
870 $Attribs[$PluginName] = $Plugin->GetAttributes();
871 }
872
873 # for each dependency for this plugin
874 foreach ($Attribs[$PluginName]["Requires"]
875 as $ReqName => $ReqVersion)
876 {
877 # handle PHP version requirements
878 if ($ReqName == "PHP")
879 {
880 if (version_compare($ReqVersion, phpversion(), ">"))
881 {
882 $ErrMsgs[$PluginName][] = "PHP version "
883 ."<i>".$ReqVersion."</i>"
884 ." required by <b>".$PluginName."</b>"
885 ." was not available. (Current PHP version"
886 ." is <i>".phpversion()."</i>.)";
887 }
888 }
889 # handle PHP extension requirements
890 elseif (preg_match("/^PHPX_/", $ReqName))
891 {
892 list($Dummy, $ExtensionName) = explode("_", $ReqName, 2);
893 if (!extension_loaded($ExtensionName))
894 {
895 $ErrMsgs[$PluginName][] = "PHP extension "
896 ."<i>".$ExtensionName."</i>"
897 ." required by <b>".$PluginName."</b>"
898 ." was not available.";
899 }
900 elseif (($ReqVersion !== TRUE)
901 && (phpversion($ExtensionName) !== FALSE)
902 && version_compare($ReqVersion,
903 phpversion($ExtensionName), ">"))
904 {
905 $ErrMsgs[$PluginName][] = "PHP extension "
906 ."<i>".$ExtensionName."</i>"
907 ." version <i>".$ReqVersion."</i>"
908 ." required by <b>".$PluginName."</b>"
909 ." was not available. (Current version"
910 ." of extension <i>".$ExtensionName."</i>"
911 ." is <i>".phpversion($ExtensionName)."</i>.)";
912 }
913 }
914 # handle dependencies on other plugins
915 else
916 {
917 # load plugin attributes if not already loaded
918 if (isset($Plugins[$ReqName])
919 && !isset($Attribs[$ReqName]))
920 {
921 $Attribs[$ReqName] =
922 $Plugins[$ReqName]->GetAttributes();
923 }
924
925 # if target plugin is not present or is too old
926 # or is not enabled
927 # or (if appropriate) is not ready
928 if (!isset($Plugins[$ReqName])
929 || version_compare($ReqVersion,
930 $Attribs[$ReqName]["Version"], ">")
931 || !$Plugins[$ReqName]->IsEnabled()
932 || ($CheckReady
933 && !$Plugins[$ReqName]->IsReady()))
934 {
935 # add error message
936 $ErrMsgs[$PluginName][] = "Plugin <i>"
937 .$ReqName." ".$ReqVersion."</i>"
938 ." required by <b>".$PluginName."</b>"
939 ." was not available.";
940 }
941 }
942
943 # if problem was found with plugin
944 if (isset($ErrMsgs[$PluginName]))
945 {
946 # remove plugin from our list
947 unset($Plugins[$PluginName]);
948
949 # set flag to indicate a plugin had to be dropped
950 $AllOkay = FALSE;
951 }
952 }
953 }
954 }
955 } while ($AllOkay == FALSE);
956
957 # return messages about any dropped plugins back to caller
958 return $ErrMsgs;
959 }
960
969 private function SortPluginsByInitializationPrecedence($Plugins)
970 {
971 # load plugin attributes
972 foreach ($Plugins as $PluginName => $Plugin)
973 {
974 $PluginAttribs[$PluginName] = $Plugin->GetAttributes();
975 }
976
977 # determine initialization order
978 $PluginsAfterUs = array();
979 foreach ($PluginAttribs as $PluginName => $Attribs)
980 {
981 foreach ($Attribs["InitializeBefore"] as $OtherPluginName)
982 {
983 $PluginsAfterUs[$PluginName][] = $OtherPluginName;
984 }
985 foreach ($Attribs["InitializeAfter"] as $OtherPluginName)
986 {
987 $PluginsAfterUs[$OtherPluginName][] = $PluginName;
988 }
989 }
990
991 # infer other initialization order cues from lists of required plugins
992 foreach ($PluginAttribs as $PluginName => $Attribs)
993 {
994 # for each required plugin
995 foreach ($Attribs["Requires"]
996 as $RequiredPluginName => $RequiredPluginVersion)
997 {
998 # skip the requirement if it it not for another known plugin
999 if (!isset($PluginAttribs[$RequiredPluginName]))
1000 {
1001 continue;
1002 }
1003
1004 # if there is not a requirement in the opposite direction
1005 if (!array_key_exists($PluginName,
1006 $PluginAttribs[$RequiredPluginName]["Requires"]))
1007 {
1008 # if the required plugin is not scheduled to be after us
1009 if (!array_key_exists($PluginName, $PluginsAfterUs)
1010 || !in_array($RequiredPluginName,
1011 $PluginsAfterUs[$PluginName]))
1012 {
1013 # if we are not already scheduled to be after the required plugin
1014 if (!array_key_exists($PluginName, $PluginsAfterUs)
1015 || !in_array($RequiredPluginName,
1016 $PluginsAfterUs[$PluginName]))
1017 {
1018 # schedule us to be after the required plugin
1019 $PluginsAfterUs[$RequiredPluginName][] =
1020 $PluginName;
1021 }
1022 }
1023 }
1024 }
1025 }
1026
1027 # keep track of those plugins we have yet to do and those that are done
1028 $UnsortedPlugins = array_keys($Plugins);
1029 $PluginsProcessed = array();
1030
1031 # limit the number of iterations of the plugin ordering loop
1032 # to 10 times the number of plugins we have
1033 $MaxIterations = 10 * count($UnsortedPlugins);
1034 $IterationCount = 0;
1035
1036 # iterate through all the plugins that need processing
1037 while (($NextPlugin = array_shift($UnsortedPlugins)) !== NULL)
1038 {
1039 # check to be sure that we're not looping forever
1040 $IterationCount++;
1041 if ($IterationCount > $MaxIterations)
1042 {
1043 throw new Exception(
1044 "Max iteration count exceeded trying to determine plugin"
1045 ." loading order. Is there a dependency loop?");
1046 }
1047
1048 # if no plugins require this one, it can go last
1049 if (!isset($PluginsAfterUs[$NextPlugin]))
1050 {
1051 $PluginsProcessed[$NextPlugin] = $MaxIterations;
1052 }
1053 else
1054 {
1055 # for plugins that are required by others
1056 $Index = $MaxIterations;
1057 foreach ($PluginsAfterUs[$NextPlugin] as $GoBefore)
1058 {
1059 if (!isset($PluginsProcessed[$GoBefore]))
1060 {
1061 # if there is something that requires us which hasn't
1062 # yet been assigned an order, then we can't determine
1063 # our own place on this iteration
1064 array_push($UnsortedPlugins, $NextPlugin);
1065 continue 2;
1066 }
1067 else
1068 {
1069 # otherwise, make sure that we're loaded
1070 # before the earliest of the things that require us
1071 $Index = min($Index, $PluginsProcessed[$GoBefore] - 1);
1072 }
1073 }
1074 $PluginsProcessed[$NextPlugin] = $Index;
1075 }
1076 }
1077
1078 # arrange plugins according to our ordering
1079 asort($PluginsProcessed, SORT_NUMERIC);
1080 $SortedPlugins = array();
1081 foreach ($PluginsProcessed as $PluginName => $SortOrder)
1082 {
1083 $SortedPlugins[$PluginName] = $Plugins[$PluginName];
1084 }
1085
1086 # return sorted list to caller
1087 return $SortedPlugins;
1088 }
1089
1098 public function FindPluginPhpFile($PageName)
1099 {
1100 # build list of possible locations for file
1101 $Locations = array(
1102 "local/plugins/%PLUGIN%/pages/%PAGE%.php",
1103 "plugins/%PLUGIN%/pages/%PAGE%.php",
1104 "local/plugins/%PLUGIN%/%PAGE%.php",
1105 "plugins/%PLUGIN%/%PAGE%.php",
1106 );
1107
1108 # look for file and return (possibly) updated page to caller
1109 return $this->FindPluginPageFile($PageName, $Locations);
1110 }
1121 public function FindPluginHtmlFile($PageName)
1122 {
1123 # build list of possible locations for file
1124 $Locations = array(
1125 "local/plugins/%PLUGIN%/interface/%ACTIVEUI%/%PAGE%.html",
1126 "plugins/%PLUGIN%/interface/%ACTIVEUI%/%PAGE%.html",
1127 "local/plugins/%PLUGIN%/interface/%DEFAULTUI%/%PAGE%.html",
1128 "plugins/%PLUGIN%/interface/%DEFAULTUI%/%PAGE%.html",
1129 "local/plugins/%PLUGIN%/%PAGE%.html",
1130 "plugins/%PLUGIN%/%PAGE%.html",
1131 );
1132
1133 # find HTML file
1134 $Params = $this->FindPluginPageFile($PageName, $Locations);
1135
1136 # if plugin HTML file was found
1137 if ($Params["PageName"] != $PageName)
1138 {
1139 # add subdirectories for plugin to search paths
1140 $Dir = preg_replace("%^local/%", "", dirname($Params["PageName"]));
1141 $this->AF->AddImageDirectories(array(
1142 "local/".$Dir."/images",
1143 $Dir."/images",
1144 ));
1145 $this->AF->AddIncludeDirectories(array(
1146 "local/".$Dir."/include",
1147 $Dir."/include",
1148 ));
1149 $this->AF->AddFunctionDirectories(array(
1150 "local/".$Dir."/include",
1151 $Dir."/include",
1152 ));
1153 }
1154
1155 # return possibly revised HTML file name to caller
1156 return $Params;
1157 }
1170 private function FindPluginPageFile($PageName, $Locations)
1171 {
1172 # set up return value assuming we will not find plugin page file
1173 $ReturnValue["PageName"] = $PageName;
1174
1175 # look for plugin name and plugin page name in base page name
1176 preg_match("/P_([A-Za-z].[A-Za-z0-9]*)_([A-Za-z0-9_-]+)/", $PageName, $Matches);
1177
1178 # if plugin name and plugin page name were found and plugin name is valid
1179 if (count($Matches) == 3)
1180 {
1181 # if plugin is valid and enabled and has its own subdirectory
1182 $PluginName = $Matches[1];
1183 if (isset($this->Plugins[$PluginName])
1184 && $this->PluginHasDir[$PluginName]
1185 && $this->Plugins[$PluginName]->IsEnabled())
1186 {
1187 # for each possible location
1188 $PageName = $Matches[2];
1189 $ActiveUI = $this->AF->ActiveUserInterface();
1190 $DefaultUI = $this->AF->DefaultUserInterface();
1191 foreach ($Locations as $Loc)
1192 {
1193 # make any needed substitutions into path
1194 $FileName = str_replace(
1195 array("%DEFAULTUI%", "%ACTIVEUI%", "%PLUGIN%", "%PAGE%"),
1196 array($DefaultUI, $ActiveUI, $PluginName, $PageName),
1197 $Loc);
1198
1199 # if file exists in this location
1200 if (file_exists($FileName))
1201 {
1202 # set return value to contain full plugin page file name
1203 $ReturnValue["PageName"] = $FileName;
1204
1205 # save plugin name as home of current page
1206 $this->PageFilePlugin = $PluginName;
1207
1208 # set G_Plugin to plugin associated with current page
1209 $GLOBALS["G_Plugin"] = $this->GetPluginForCurrentPage();
1210
1211 # stop looking
1212 break;
1213 }
1214 }
1215 }
1216 }
1217
1218 # return array containing page name or page file name to caller
1219 return $ReturnValue;
1220 }
1221 }
1222
1234 class PluginCaller
1235 {
1236
1243 public function __construct($PluginName, $MethodName)
1244 {
1245 $this->PluginName = $PluginName;
1246 $this->MethodName = $MethodName;
1247 }
1248
1254 public function CallPluginMethod()
1255 {
1256 $Args = func_get_args();
1257 $Plugin = self::$Manager->GetPlugin($this->PluginName);
1258 return call_user_func_array(array($Plugin, $this->MethodName), $Args);
1259 }
1260
1265 public function GetCallbackAsText()
1266 {
1267 return $this->PluginName."::".$this->MethodName;
1268 }
1269
1275 public function __sleep()
1276 {
1277 return array("PluginName", "MethodName");
1278 }
1279
1281 static public $Manager;
1282
1283 private $PluginName;
1284 private $MethodName;
1285 }
1288 ?>
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
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
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:318
PluginManager\LoadPlugins
LoadPlugins()
Load and initialize plugins.
Definition: PluginManager.php:49