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"),
38  $this->AF->HookEvent("EVENT_HTML_FILE_LOAD", array($this, "FindPluginHtmlFile"),
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
449  }
450  else
451  {
452  # add plugin directory to class autoloading list
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  {
487  $InterfaceDir."/%ACTIVEUI%/objects");
488  }
489  if (is_dir($InterfaceDir."/".$DefaultUI."/objects"))
490  {
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 ?>
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.
static AddObjectDirectory($Dir, $Prefix="", $ClassPattern=NULL, $ClassReplacement=NULL)
Add directory to be searched for object files when autoloading.
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.
const ORDER_LAST
Run hooked function last (i.e.
static SetApplicationFramework($AF)
Set the application framework to be referenced within plugins.
Definition: Plugin.php:447
LoadPlugins()
Load and initialize plugins.