CWIS Developer Documentation
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 
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 ?>
GetErrorMessages()
Retrieve any error messages generated during plugin loading.
Manager to load and invoke plugins.
GetPlugin($PluginName, $EvenIfNotReady=FALSE)
Retrieve specified plugin.
SQL database abstraction object with smart query caching.
Definition: Database.php:22
UninstallPlugin($PluginName)
Uninstall plugin and (optionally) delete any associated data.
GetDependents($PluginName)
Returns a list of plugins dependent on the specified plugin.
GetActivePluginList()
Get list of active (i.e.
GetPluginAttributes()
Retrieve info about currently loaded plugins.
PluginEnabled($PluginName, $NewValue=NULL)
Get/set whether specified plugin is enabled.
__construct($AppFramework, $PluginDirectories)
PluginManager class constructor.
GetPluginForCurrentPage()
Retrieve plugin for current page (if any).
GetPlugins()
Retrieve all loaded plugins.
static SetApplicationFramework($AF)
Set the application framework to be referenced within plugins.
Definition: Plugin.php:447
static SetConfigValueLoader($Func)
Set function to load plugin configuration values from data.
LoadPlugins()
Load and initialize plugins.