Search:

# CWIS Developers Documentation

- Main Page
- Classes
- Files

- File List
- File Members

# PluginManager.php

Go to the documentation of this file.

    00001 <?PHP
    00002
    00006 class PluginManager {
    00007
    00008     # ---- PUBLIC INTERFACE --------------------------------------------------
    00009
    00015     function __construct($AppFramework, $PluginDirectories)
    00016     {
    00017         # save framework and directory list for later use
    00018         $this->AF = $AppFramework;
    00019         $this->DirsToSearch = $PluginDirectories;
    00020
    00021         # get our own database handle
    00022         $this->DB = new Database();
    00023
    00024         # hook into events to load plugin PHP and HTML files
    00025         $this->AF->HookEvent("EVENT_PHP_FILE_LOAD", array($this, "FindPluginPhpFile"),
    00026                 ApplicationFramework::ORDER_LAST);
    00027         $this->AF->HookEvent("EVENT_HTML_FILE_LOAD", array($this, "FindPluginHtmlFile"),
    00028                 ApplicationFramework::ORDER_LAST);
    00029
    00030         # tell PluginCaller helper object how to get to us
    00031         PluginCaller::$Manager = $this;
    00032     }
    00033
    00038     function LoadPlugins()
    00039     {
    00040         # clear any existing errors
    00041         $this->ErrMsgs = array();
    00042
    00043         # load list of all base plugin files
    00044         $this->FindPlugins($this->DirsToSearch);
    00045
    00046         # for each plugin found
    00047         foreach ($this->PluginNames as $PluginName)
    00048         {
    00049             # bring in plugin class file
    00050             include_once($this->PluginFiles[$PluginName]);
    00051
    00052             # if plugin class was defined by file
    00053             if (class_exists($PluginName))
    00054             {
    00055                 # if plugin class is a valid descendant of base plugin class
    00056                 $Plugin = new $PluginName;
    00057                 if (is_subclass_of($Plugin, "Plugin"))
    00058                 {
    00059                     # set hooks needed for plugin to access plugin manager services
    00060                     $Plugin->SetCfgSaveCallback(array(__CLASS__, "CfgSaveCallback"));
    00061
    00062                     # register the plugin
    00063                     $this->Plugins[$PluginName] = $Plugin;
    00064                     $this->PluginEnabled[$PluginName] = TRUE;
    00065                     $this->Plugins[$PluginName]->Register();
    00066
    00067                     # check required plugin attributes
    00068                     $Attribs[$PluginName] = $this->Plugins[$PluginName]->GetAttributes();
    00069                     if (!strlen($Attribs[$PluginName]["Name"]))
    00070                     {
    00071                         $this->ErrMsgs[$PluginName][] = "Plugin <b>".$PluginName."</b>"
    00072                                 ." could not be loaded because it"
    00073                                 ." did not have a <i>Name</i> attribute set.";
    00074                         unset($this->PluginEnabled[$PluginName]);
    00075                         unset($this->Plugins[$PluginName]);
    00076                     }
    00077                     if (!strlen($Attribs[$PluginName]["Version"]))
    00078                     {
    00079                         $this->ErrMsgs[$PluginName][] = "Plugin <b>".$PluginName."</b>"
    00080                                 ." could not be loaded because it"
    00081                                 ." did not have a <i>Version</i> attribute set.";
    00082                         unset($this->PluginEnabled[$PluginName]);
    00083                         unset($this->Plugins[$PluginName]);
    00084                     }
    00085                 }
    00086                 else
    00087                 {
    00088                     $this->ErrMsgs[$PluginName][] = "Plugin <b>".$PluginName."</b>"
    00089                             ." could not be loaded because <i>".$PluginName."</i> was"
    00090                             ." not a subclass of base <i>Plugin</i> class";
    00091                 }
    00092             }
    00093             else
    00094             {
    00095                 $this->ErrMsgs[$PluginName][] = "Expected class <i>".$PluginName
    00096                         ."</i> not found in plugin file <i>"
    00097                         .$this->PluginFiles[$PluginName]."</i>";
    00098             }
    00099         }
    00100
    00101         # check plugin dependencies
    00102         $this->CheckDependencies();
    00103
    00104         # load plugin configurations
    00105         $this->DB->Query("SELECT BaseName,Cfg FROM PluginInfo");
    00106         $Cfgs = $this->DB->FetchColumn("Cfg", "BaseName");
    00107
    00108         foreach ($this->Plugins as $PluginName => $Plugin)
    00109         {
    00110             if ($this->PluginEnabled[$PluginName])
    00111             {
    00112                 # set configuration values if available
    00113                 if (isset($Cfgs[$PluginName]))
    00114                 {
    00115                     $Plugin->SetAllCfg(unserialize($Cfgs[$PluginName]));
    00116                 }
    00117
    00118                 # install or upgrade plugins if needed
    00119                 $this->InstallPlugin($Plugin);
    00120             }
    00121         }
    00122
    00123         # initialize each plugin
    00124         foreach ($this->Plugins as $PluginName => $Plugin)
    00125         {
    00126             if ($this->PluginEnabled[$PluginName])
    00127             {
    00128                 $ErrMsg = $Plugin->Initialize();
    00129                 if ($ErrMsg !== NULL)
    00130                 {
    00131                     $this->ErrMsgs[$PluginName][] = "Initialization failed for"
    00132                             ." plugin <b>".$PluginName."</b>: <i>".$ErrMsg."</i>";
    00133                     $this->PluginEnabled[$PluginName] = FALSE;
    00134                 }
    00135             }
    00136         }
    00137
    00138         # register any events declared by each plugin
    00139         foreach ($this->Plugins as $PluginName => $Plugin)
    00140         {
    00141             if ($this->PluginEnabled[$PluginName])
    00142             {
    00143                 $Events = $Plugin->DeclareEvents();
    00144                 if (count($Events)) {  $this->AF->RegisterEvent($Events);  }
    00145             }
    00146         }
    00147
    00148         # hook plugins to events
    00149         foreach ($this->Plugins as $PluginName => $Plugin)
    00150         {
    00151             if ($this->PluginEnabled[$PluginName])
    00152             {
    00153                 $EventsToHook = $Plugin->HookEvents();
    00154                 if (count($EventsToHook))
    00155                 {
    00156                     foreach ($EventsToHook as $EventName => $PluginMethod)
    00157                     {
    00158                         if ($this->AF->IsStaticOnlyEvent($EventName))
    00159                         {
    00160                             $Caller = new PluginCaller($PluginName, $PluginMethod);
    00161                             $Result = $this->AF->HookEvent(
    00162                                     $EventName, array($Caller, "CallPluginMethod"));
    00163                         }
    00164                         else
    00165                         {
    00166                             $Result = $this->AF->HookEvent(
    00167                                     $EventName, array($Plugin, $PluginMethod));
    00168                         }
    00169                         if ($Result === FALSE)
    00170                         {
    00171                             $this->ErrMsgs[$PluginName][] =
    00172                                     "Unable to hook requested event <i>"
    00173                                     .$EventName."</i> for plugin <b>".$PluginName."</b>";
    00174                         }
    00175                     }
    00176                 }
    00177             }
    00178         }
    00179
    00180         # limit plugin directory list to only active plugins
    00181         foreach ($this->PluginEnabled as $PluginName => $Enabled)
    00182         {
    00183             if (isset($this->PluginDirs[$PluginName]) && !$Enabled)
    00184             {
    00185                 unset($this->PluginDirs[$PluginName]);
    00186             }
    00187         }
    00188
    00189         # add plugin directories to list to be searched for object files
    00190         $ObjDirs = array();
    00191         foreach ($this->PluginDirs as $Dir)
    00192         {
    00193             $ObjDirs[$Dir] = "";
    00194         }
    00195         $this->AF->AddObjectDirectories($ObjDirs);
    00196
    00197         # report to caller whether any problems were encountered
    00198         return count($this->ErrMsgs) ? FALSE : TRUE;
    00199     }
    00200
    00205     function GetErrorMessages()
    00206     {
    00207         return $this->ErrMsgs;
    00208     }
    00209
    00215     function GetPlugin($PluginName)
    00216     {
    00217         return isset($this->Plugins[$PluginName])
    00218                 ? $this->Plugins[$PluginName] : NULL;
    00219     }
    00220
    00228     function GetPluginForCurrentPage()
    00229     {
    00230         return $this->GetPlugin($this->PageFilePlugin);
    00231     }
    00232
    00237     function GetPluginAttributes()
    00238     {
    00239         $Info = array();
    00240         foreach ($this->Plugins as $PluginName => $Plugin)
    00241         {
    00242             $Info[$PluginName] = $Plugin->GetAttributes();
    00243             $Info[$PluginName]["Enabled"] =
    00244                     isset($this->PluginInfo[$PluginName]["Enabled"])
    00245                     ? $this->PluginInfo[$PluginName]["Enabled"] : FALSE;
    00246         }
    00247         return $Info;
    00248     }
    00249
    00254     public function GetActivePluginList()
    00255     {
    00256         return array_keys($this->PluginEnabled, 1);
    00257     }
    00258
    00265     function PluginEnabled($PluginName, $NewValue = NULL)
    00266     {
    00267         if ($NewValue !== NULL)
    00268         {
    00269             $this->DB->Query("UPDATE PluginInfo"
    00270                     ." SET Enabled = ".($NewValue ? "1" : "0")
    00271                     ." WHERE BaseName = '".addslashes($PluginName)."'");
    00272             $this->PluginEnabled[$PluginName] = $NewValue;
    00273             $this->PluginInfo[$PluginName]["Enabled"] = $NewValue;
    00274         }
    00275         return $this->PluginEnabled[$PluginName];
    00276     }
    00277
    00278
    00279     # ---- PRIVATE INTERFACE -------------------------------------------------
    00280
    00281     private $Plugins = array();
    00282     private $PluginFiles = array();
    00283     private $PluginNames = array();
    00284     private $PluginDirs = array();
    00285     private $PluginInfo = array();
    00286     private $PluginEnabled = array();
    00287     private $PageFilePlugin = NULL;
    00288     private $AF;
    00289     private $DirsToSearch;
    00290     private $ErrMsgs = array();
    00291     private $DB;
    00292
    00293     private function FindPlugins($DirsToSearch)
    00294     {
    00295         # for each directory
    00296         $PluginFiles = array();
    00297         foreach ($DirsToSearch as $Dir)
    00298         {
    00299             # if directory exists
    00300             if (is_dir($Dir))
    00301             {
    00302                 # for each file in directory
    00303                 $FileNames = scandir($Dir);
    00304                 foreach ($FileNames as $FileName)
    00305                 {
    00306                     # if file looks like base plugin file
    00307                     if (preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*\.php$/", $FileName))
    00308                     {
    00309                         # add file to list
    00310                         $PluginName = preg_replace("/\.php$/", "", $FileName);
    00311                         $this->PluginNames[$PluginName] = $PluginName;
    00312                         $this->PluginFiles[$PluginName] = $Dir."/".$FileName;
    00313                     }
    00314                     # else if file looks like plugin directory
    00315                     elseif (is_dir($Dir."/".$FileName)
    00316                             && preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*/", $FileName))
    00317                     {
    00318                         # if there is a base plugin file in the directory
    00319                         $PossibleFile = $Dir."/".$FileName."/".$FileName.".php";
    00320                         if (file_exists($PossibleFile))
    00321                         {
    00322                             # add plugin and directory to lists
    00323                             $this->PluginNames[$FileName] = $FileName;
    00324                             $this->PluginFiles[$FileName] = $PossibleFile;
    00325                             $this->PluginDirs[$FileName] = $Dir."/".$FileName;
    00326                         }
    00327                         else
    00328                         {
    00329                             $this->ErrMsgs[$FileName][] =
    00330                                     "Expected plugin file <i>".$FileName.".php</i> not"
    00331                                     ." found in plugin subdirectory <i>"
    00332                                     .$Dir."/".$FileName."</i>";
    00333                         }
    00334                     }
    00335                 }
    00336             }
    00337         }
    00338
    00339         # return list of base plugin files to caller
    00340         return $PluginFiles;
    00341     }
    00342
    00343     private function InstallPlugin($Plugin)
    00344     {
    00345         # cache all plugin info from database
    00346         if (count($this->PluginInfo) == 0)
    00347         {
    00348             $this->DB->Query("SELECT * FROM PluginInfo");
    00349             while ($Row = $this->DB->FetchRow())
    00350             {
    00351                 $this->PluginInfo[$Row["BaseName"]] = $Row;
    00352             }
    00353         }
    00354
    00355         # if plugin was not found in database
    00356         $PluginName = get_class($Plugin);
    00357         $Attribs = $Plugin->GetAttributes();
    00358         if (!isset($this->PluginInfo[$PluginName]))
    00359         {
    00360             # add plugin to database
    00361             $this->DB->Query("INSERT INTO PluginInfo"
    00362                     ." (BaseName, Version, Installed, Enabled)"
    00363                     ." VALUES ('".addslashes($PluginName)."', "
    00364                     ." '".addslashes($Attribs["Version"])."', "
    00365                     ."0, "
    00366                     ." ".($Attribs["EnabledByDefault"] ? 1 : 0).")");
    00367
    00368             # read plugin settings back in
    00369             $this->DB->Query("SELECT * FROM PluginInfo"
    00370                  ." WHERE BaseName = '".addslashes($PluginName)."'");
    00371             $this->PluginInfo[$PluginName] = $this->DB->FetchRow();
    00372         }
    00373
    00374         # store plugin settings for later use
    00375         $this->PluginEnabled[$PluginName] = $this->PluginInfo[$PluginName]["Enabled"];
    00376
    00377         # if plugin is enabled
    00378         if ($this->PluginEnabled[$PluginName])
    00379         {
    00380             # if plugin has not been installed
    00381             if (!$this->PluginInfo[$PluginName]["Installed"])
    00382             {
    00383                 # install plugin
    00384                 $ErrMsg = $Plugin->Install();
    00385
    00386                 # if install succeeded
    00387                 if ($ErrMsg == NULL)
    00388                 {
    00389                     # mark plugin as installed
    00390                     $this->DB->Query("UPDATE PluginInfo SET Installed = 1"
    00391                             ." WHERE BaseName = '".addslashes($PluginName)."'");
    00392                     $this->PluginInfo[$PluginName]["Installed"] = 1;
    00393                 }
    00394                 else
    00395                 {
    00396                     # disable plugin
    00397                     $this->PluginEnabled[$PluginName] = FALSE;
    00398
    00399                     # record error message about installation failure
    00400                     $this->ErrMsgs[$PluginName][] = "Installation of plugin <b>"
    00401                             .$PluginName."</b> failed: <i>".$ErrMsg."</i>";;
    00402                 }
    00403             }
    00404             else
    00405             {
    00406                 # if plugin version is newer than version in database
    00407                 if (version_compare($Attribs["Version"],
    00408                         $this->PluginInfo[$PluginName]["Version"]) == 1)
    00409                 {
    00410                     # upgrade plugin
    00411                     $ErrMsg = $Plugin->Upgrade($this->PluginInfo[$PluginName]["Version"]);
    00412
    00413                     # if upgrade succeeded
    00414                     if ($ErrMsg == NULL)
    00415                     {
    00416                         # update plugin version in database
    00417                         $Attribs = $Plugin->GetAttributes();
    00418                         $this->DB->Query("UPDATE PluginInfo"
    00419                                 ." SET Version = '".addslashes($Attribs["Version"])."'"
    00420                                 ." WHERE BaseName = '".addslashes($PluginName)."'");
    00421                         $this->PluginInfo[$PluginName]["Version"] = $Attribs["Version"];
    00422                     }
    00423                     else
    00424                     {
    00425                         # disable plugin
    00426                         $this->PluginEnabled[$PluginName] = FALSE;
    00427
    00428                         # record error message about upgrade failure
    00429                         $this->ErrMsgs[$PluginName][] = "Upgrade of plugin <b>"
    00430                                 .$PluginName."</b> from version <i>"
    00431                                 .addslashes($this->PluginInfo[$PluginName]["Version"])
    00432                                 ."</i> to version <i>"
    00433                                 .addslashes($Attribs["Version"])."</i> failed: <i>"
    00434                                 .$ErrMsg."</i>";
    00435                     }
    00436                 }
    00437                 # else if plugin version is older than version in database
    00438                 elseif (version_compare($Attribs["Version"],
    00439                         $this->PluginInfo[$PluginName]["Version"]) == -1)
    00440                 {
    00441                     # disable plugin
    00442                     $this->PluginEnabled[$PluginName] = FALSE;
    00443
    00444                     # record error message about version conflict
    00445                     $this->ErrMsgs[$PluginName][] = "Plugin <b>"
    00446                             .$PluginName."</b> is older (<i>"
    00447                             .addslashes($Attribs["Version"])
    00448                             ."</i>) than previously-installed version (<i>"
    00449                             .addslashes($this->PluginInfo[$PluginName]["Version"])."</i>).";
    00450                 }
    00451             }
    00452         }
    00453     }
    00454
    00455     private function CheckDependencies()
    00456     {
    00457         # look until all enabled plugins check out okay
    00458         do
    00459         {
    00460             # start out assuming all plugins are okay
    00461             $AllOkay = TRUE;
    00462
    00463             # for each plugin
    00464             foreach ($this->Plugins as $PluginName => $Plugin)
    00465             {
    00466                 # if plugin is currently enabled
    00467                 if ($this->PluginEnabled[$PluginName])
    00468                 {
    00469                     # load plugin attributes
    00470                     if (!isset($Attribs[$PluginName]))
    00471                     {
    00472                         $Attribs[$PluginName] =
    00473                                 $this->Plugins[$PluginName]->GetAttributes();
    00474                     }
    00475
    00476                     # for each dependency for this plugin
    00477                     foreach ($Attribs[$PluginName]["Requires"]
    00478                             as $ReqName => $ReqVersion)
    00479                     {
    00480                         # handle PHP version requirements
    00481                         if ($ReqName ==  "PHP")
    00482                         {
    00483                             if (version_compare($ReqVersion, phpversion(), ">"))
    00484                             {
    00485                                 $this->ErrMsgs[$PluginName][] = "PHP version "
    00486                                     ."<i>".$ReqVersion."</i>"
    00487                                     ." required by <b>".$PluginName."</b>"
    00488                                     ." was not available.  (Current PHP version"
    00489                                     ." is <i>".phpversion()."</i>.)";
    00490                             }
    00491                         }
    00492                         # handle PHP extension requirements
    00493                         elseif (preg_match("/^PHPX_/", $ReqName))
    00494                         {
    00495                             list($Dummy, $ExtensionName) = split("_", $ReqName, 2);
    00496                             if (!extension_loaded($ExtensionName))
    00497                             {
    00498                                 $this->ErrMsgs[$PluginName][] = "PHP extension "
    00499                                     ."<i>".$ExtensionName."</i>"
    00500                                     ." required by <b>".$PluginName."</b>"
    00501                                     ." was not available.";
    00502                             }
    00503                         }
    00504                         # handle dependencies on other plugins
    00505                         else
    00506                         {
    00507                             # load plugin attributes if not already loaded
    00508                             if (isset($this->Plugins[$ReqName])
    00509                                     && !isset($Attribs[$ReqName]))
    00510                             {
    00511                                 $Attribs[$ReqName] =
    00512                                         $this->Plugins[$ReqName]->GetAttributes();
    00513                             }
    00514
    00515                             # if target plugin is not present or is disabled or is too old
    00516                             if (!isset($this->PluginEnabled[$ReqName])
    00517                                     || !$this->PluginEnabled[$ReqName]
    00518                                     || (version_compare($ReqVersion,
    00519                                             $Attribs[$ReqName]["Version"], ">")))
    00520                             {
    00521                                 # add error message and disable plugin
    00522                                 $this->ErrMsgs[$PluginName][] = "Plugin <i>"
    00523                                         .$ReqName." ".$ReqVersion."</i>"
    00524                                         ." required by <b>".$PluginName."</b>"
    00525                                         ." was not available.";
    00526                                 $this->PluginEnabled[$PluginName] = FALSE;
    00527
    00528                                 # set flag indicating plugin did not check out
    00529                                 $AllOkay = FALSE;
    00530                             }
    00531                         }
    00532                     }
    00533                 }
    00534             }
    00535         } while ($AllOkay == FALSE);
    00536     }
    00537
    00539     function FindPluginPhpFile($PageName)
    00540     {
    00541         return $this->FindPluginPageFile($PageName, "php");
    00542     }
    00546     function FindPluginHtmlFile($PageName)
    00547     {
    00548         return $this->FindPluginPageFile($PageName, "html");
    00549     }
    00552     private function FindPluginPageFile($PageName, $Suffix)
    00553     {
    00554         # set up return value assuming we will not find plugin page file
    00555         $ReturnValue["PageName"] = $PageName;
    00556
    00557         # look for plugin name and plugin page name in base page name
    00558         preg_match("/P_([A-Za-z].[A-Za-z0-9]*)_([A-Za-z0-9_-]+)/", $PageName, $Matches);
    00559
    00560         # if base page name contained name of existing plugin with its own subdirectory
    00561         if ((count($Matches) == 3) && isset($this->PluginDirs[$Matches[1]]))
    00562         {
    00563             # if PHP file with specified name exists in plugin subdirectory
    00564             $PageFile = $this->PluginDirs[$Matches[1]]."/".$Matches[2].".".$Suffix;
    00565             if (file_exists($PageFile))
    00566             {
    00567                 # set return value to contain full plugin PHP file name
    00568                 $ReturnValue["PageName"] = $PageFile;
    00569
    00570                 # save plugin name as home of current page
    00571                 $this->PageFilePlugin = $Matches[1];
    00572             }
    00573         }
    00574
    00575         # return array containing page name or page file name to caller
    00576         return $ReturnValue;
    00577     }
    00578
    00580     static function CfgSaveCallback($BaseName, $Cfg)
    00581     {
    00582         $DB = new Database();
    00583         $DB->Query("UPDATE PluginInfo SET Cfg = '".addslashes(serialize($Cfg))
    00584                 ."' WHERE BaseName = '".addslashes($BaseName)."'");
    00585     }
    00587 }
    00588
    00600 class PluginCaller {
    00601
    00602     function __construct($PluginName, $MethodName)
    00603     {
    00604         $this->PluginName = $PluginName;
    00605         $this->MethodName = $MethodName;
    00606     }
    00607
    00608     function CallPluginMethod()
    00609     {
    00610         $Args = func_get_args();
    00611         $Plugin = self::$Manager->GetPlugin($this->PluginName);
    00612         return call_user_func_array(array($Plugin, $this->MethodName), $Args);
    00613     }
    00614
    00615     function GetCallbackAsText()
    00616     {
    00617         return $this->PluginName."::".$this->MethodName;
    00618     }
    00619
    00620     function __sleep()
    00621     {
    00622         return array("PluginName", "MethodName");
    00623     }
    00624
    00625     static public $Manager;
    00626
    00627     private $PluginName;
    00628     private $MethodName;
    00629 }
    00632 ?>