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_PAGE_LOAD", array($this, "FindPluginPhpFile")); 00026 $this->AF->HookEvent("EVENT_HTML_PAGE_LOAD", array($this, "FindPluginHtmlFile")); 00027 } 00028 00033 function LoadPlugins() 00034 { 00035 # clear any existing errors 00036 $this->ErrMsgs = array(); 00037 00038 # load list of all base plugin files 00039 $this->FindPlugins($this->DirsToSearch); 00040 00041 # for each plugin found 00042 foreach ($this->PluginNames as $PluginName) 00043 { 00044 # bring in plugin class file 00045 include_once($this->PluginFiles[$PluginName]); 00046 00047 # if plugin class was defined by file 00048 if (class_exists($PluginName)) 00049 { 00050 # if plugin class is a valid descendant of base plugin class 00051 $Plugin = new $PluginName; 00052 if (is_subclass_of($Plugin, "Plugin")) 00053 { 00054 # register the plugin 00055 $this->Plugins[$PluginName] = $Plugin; 00056 $this->PluginEnabled[$PluginName] = TRUE; 00057 $this->Plugins[$PluginName]->Register(); 00058 00059 # check required plugin attributes 00060 $Attribs[$PluginName] = $this->Plugins[$PluginName]->GetAttributes(); 00061 if (!strlen($Attribs[$PluginName]["Name"])) 00062 { 00063 $this->ErrMsgs[$PluginName][] = "Plugin <b>".$PluginName."</b>" 00064 ." could not be loaded because it" 00065 ." did not have a <i>Name</i> attribute set."; 00066 unset($this->PluginEnabled[$PluginName]); 00067 unset($this->Plugins[$PluginName]); 00068 } 00069 if (!strlen($Attribs[$PluginName]["Version"])) 00070 { 00071 $this->ErrMsgs[$PluginName][] = "Plugin <b>".$PluginName."</b>" 00072 ." could not be loaded because it" 00073 ." did not have a <i>Version</i> attribute set."; 00074 unset($this->PluginEnabled[$PluginName]); 00075 unset($this->Plugins[$PluginName]); 00076 } 00077 } 00078 else 00079 { 00080 $this->ErrMsgs[$PluginName][] = "Plugin <b>".$PluginName."</b>" 00081 ." could not be loaded because <i>".$PluginName."</i> was" 00082 ." not a subclass of base <i>Plugin</i> class"; 00083 } 00084 } 00085 else 00086 { 00087 $this->ErrMsgs[$PluginName][] = "Expected class <i>".$PluginName 00088 ."</i> not found in plugin file <i>" 00089 .$this->PluginFiles[$PluginName]."</i>"; 00090 } 00091 } 00092 00093 # install or upgrade plugins if needed 00094 foreach ($this->Plugins as $PluginName => $Plugin) 00095 { 00096 if ($this->PluginEnabled[$PluginName]) 00097 { 00098 $this->InstallPlugin($Plugin); 00099 } 00100 } 00101 00102 # check plugin dependencies 00103 $this->CheckDependencies(); 00104 00105 # initialize each plugin 00106 foreach ($this->Plugins as $Plugin) 00107 { 00108 if ($this->PluginEnabled[$PluginName]) 00109 { 00110 $ErrMsg = $Plugin->Initialize(); 00111 if ($ErrMsg !== NULL) 00112 { 00113 $this->ErrMsgs[$PluginName][] = "Initialization failed for" 00114 ." plugin <b>".$PluginName."</b>: <i>".$ErrMsg."</i>"; 00115 $this->PluginEnabled[$PluginName] = FALSE; 00116 } 00117 } 00118 } 00119 00120 # register any events declared by each plugin 00121 foreach ($this->Plugins as $PluginName => $Plugin) 00122 { 00123 if ($this->PluginEnabled[$PluginName]) 00124 { 00125 $Events = $Plugin->DeclareEvents(); 00126 if (count($Events)) { $this->AF->RegisterEvent($Events); } 00127 } 00128 } 00129 00130 # hook plugins to events 00131 foreach ($this->Plugins as $PluginName => $Plugin) 00132 { 00133 if ($this->PluginEnabled[$PluginName]) 00134 { 00135 $EventsToHook = $Plugin->HookEvents(); 00136 foreach ($EventsToHook as $EventName => $PluginMethod) 00137 { 00138 $Result = $this->AF->HookEvent( 00139 $EventName, array($Plugin, $PluginMethod)); 00140 if ($Result === FALSE) 00141 { 00142 $this->ErrMsgs[$PluginName][] = 00143 "Unable to hook requested event <i>" 00144 .$EventName."</i> for plugin <b>".$PluginName."</b>"; 00145 } 00146 } 00147 } 00148 } 00149 00150 # limit plugin directory list to only active plugins 00151 foreach ($this->PluginEnabled as $PluginName => $Enabled) 00152 { 00153 if (isset($this->PluginDirs[$PluginName]) && !$Enabled) 00154 { 00155 unset($this->PluginDirs[$PluginName]); 00156 } 00157 } 00158 00159 # add plugin directories to list to be searched for object files 00160 $ObjDirs = array(); 00161 foreach ($this->PluginDirs as $Dir) 00162 { 00163 $ObjDirs[$Dir] = ""; 00164 } 00165 $this->AF->AddObjectDirectories($ObjDirs); 00166 00167 # report to caller whether any problems were encountered 00168 return count($this->ErrMsgs) ? FALSE : TRUE; 00169 } 00170 00175 function GetErrorMessages() 00176 { 00177 return $this->ErrMsgs; 00178 } 00179 00184 function GetPluginAttributes() 00185 { 00186 $Info = array(); 00187 foreach ($this->Plugins as $PluginName => $Plugin) 00188 { 00189 $Info[$PluginName] = $Plugin->GetAttributes(); 00190 $Info[$PluginName]["Enabled"] = 00191 $this->PluginInfo[$PluginName]["Enabled"]; 00192 } 00193 return $Info; 00194 } 00195 00202 function PluginEnabled($PluginName, $NewValue = NULL) 00203 { 00204 if ($NewValue !== NULL) 00205 { 00206 $this->DB->Query("UPDATE PluginInfo" 00207 ." SET Enabled = ".($NewValue ? "1" : "0") 00208 ." WHERE BaseName = '".addslashes($PluginName)."'"); 00209 $this->PluginEnabled[$PluginName] = $NewValue; 00210 $this->PluginInfo[$PluginName]["Enabled"] = $NewValue; 00211 } 00212 return $this->PluginEnabled[$PluginName]; 00213 } 00214 00215 00216 # ---- PRIVATE INTERFACE ------------------------------------------------- 00217 00218 private $Plugins = array(); 00219 private $PluginFiles = array(); 00220 private $PluginNames = array(); 00221 private $PluginDirs = array(); 00222 private $PluginInfo = array(); 00223 private $PluginEnabled = array(); 00224 private $AF; 00225 private $DirsToSearch; 00226 private $ErrMsgs = array(); 00227 private $DB; 00228 00229 private function FindPlugins($DirsToSearch) 00230 { 00231 # for each directory 00232 $PluginFiles = array(); 00233 foreach ($DirsToSearch as $Dir) 00234 { 00235 # if directory exists 00236 if (is_dir($Dir)) 00237 { 00238 # for each file in directory 00239 $FileNames = scandir($Dir); 00240 foreach ($FileNames as $FileName) 00241 { 00242 # if file looks like base plugin file 00243 if (preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*\.php$/", $FileName)) 00244 { 00245 # add file to list 00246 $PluginName = preg_replace("/\.php$/", "", $FileName); 00247 $this->PluginNames[$PluginName] = $PluginName; 00248 $this->PluginFiles[$PluginName] = $Dir."/".$FileName; 00249 } 00250 # else if file looks like plugin directory 00251 elseif (is_dir($Dir."/".$FileName) 00252 && preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*/", $FileName)) 00253 { 00254 # if there is a base plugin file in the directory 00255 $PossibleFile = $Dir."/".$FileName."/".$FileName.".php"; 00256 if (file_exists($PossibleFile)) 00257 { 00258 # add plugin and directory to lists 00259 $this->PluginNames[$FileName] = $FileName; 00260 $this->PluginFiles[$FileName] = $PossibleFile; 00261 $this->PluginDirs[$FileName] = $Dir."/".$FileName; 00262 } 00263 else 00264 { 00265 $this->ErrMsgs[$FileName][] = 00266 "Expected plugin file <i>".$FileName.".php</i> not" 00267 ." found in plugin subdirectory <i>" 00268 .$Dir."/".$FileName."</i>"; 00269 } 00270 } 00271 } 00272 } 00273 } 00274 00275 # return list of base plugin files to caller 00276 return $PluginFiles; 00277 } 00278 00279 private function InstallPlugin($Plugin) 00280 { 00281 # retrieve info about plugin from database 00282 $PluginName = get_class($Plugin); 00283 $this->DB->Query("SELECT * FROM PluginInfo" 00284 ." WHERE BaseName = '".addslashes($PluginName)."'"); 00285 00286 # if plugin was not found in database 00287 $Attribs = $Plugin->GetAttributes(); 00288 if ($this->DB->NumRowsSelected() == 0) 00289 { 00290 # add plugin to database 00291 $this->DB->Query("INSERT INTO PluginInfo" 00292 ." (BaseName, Version, Installed, Enabled)" 00293 ." VALUES ('".addslashes($PluginName)."', " 00294 ." '".addslashes($Attribs["Version"])."', " 00295 ."0, " 00296 ." ".($Attribs["EnabledByDefault"] ? 1 : 0).")"); 00297 00298 # read plugin settings back in 00299 $this->DB->Query("SELECT * FROM PluginInfo" 00300 ." WHERE BaseName = '".addslashes($PluginName)."'"); 00301 } 00302 00303 # store plugin settings for later use 00304 $this->PluginInfo[$PluginName] = $this->DB->FetchRow(); 00305 $this->PluginEnabled[$PluginName] = $this->PluginInfo[$PluginName]["Enabled"]; 00306 00307 # if plugin is enabled 00308 if ($this->PluginEnabled[$PluginName]) 00309 { 00310 # if plugin has not been installed 00311 if (!$this->PluginInfo[$PluginName]["Installed"]) 00312 { 00313 # install plugin 00314 $ErrMsg = $Plugin->Install(); 00315 00316 # if install succeeded 00317 if ($ErrMsg == NULL) 00318 { 00319 # mark plugin as installed 00320 $this->DB->Query("UPDATE PluginInfo SET Installed = 1" 00321 ." WHERE BaseName = '".addslashes($PluginName)."'"); 00322 } 00323 else 00324 { 00325 # disable plugin 00326 $this->PluginEnabled[$PluginName] = FALSE; 00327 00328 # record error message about installation failure 00329 $this->ErrMsgs[$PluginName][] = "Installation of plugin <b>" 00330 .$PluginName."</b> failed: <i>".$ErrMsg."</i>";; 00331 } 00332 } 00333 else 00334 { 00335 # if plugin version is newer than version in database 00336 if (version_compare($Attribs["Version"], 00337 $this->PluginInfo[$PluginName]["Version"]) == 1) 00338 { 00339 # upgrade plugin 00340 $ErrMsg = $Plugin->Upgrade($this->PluginInfo[$PluginName]["Version"]); 00341 00342 # if upgrade succeeded 00343 if ($ErrMsg == NULL) 00344 { 00345 # update plugin version in database 00346 $Attribs = $Plugin->GetAttributes(); 00347 $this->DB->Query("UPDATE PluginInfo" 00348 ." SET Version = '".addslashes($Attribs["Version"])."'" 00349 ." WHERE BaseName = '".addslashes($PluginName)."'"); 00350 $this->PluginInfo[$PluginName]["Version"] = $Attribs["Version"]; 00351 } 00352 else 00353 { 00354 # disable plugin 00355 $this->PluginEnabled[$PluginName] = FALSE; 00356 00357 # record error message about upgrade failure 00358 $this->ErrMsgs[$PluginName][] = "Upgrade of plugin <b>" 00359 .$PluginName."</b> from version <i>" 00360 .addslashes($this->PluginInfo[$PluginName]["Version"]) 00361 ."</i> to version <i>" 00362 .addslashes($Attribs["Version"])."</i> failed: <i>" 00363 .$ErrMsg."</i>"; 00364 } 00365 } 00366 # else if plugin version is older than version in database 00367 elseif (version_compare($Attribs["Version"], 00368 $this->PluginInfo[$PluginName]["Version"]) == -1) 00369 { 00370 # disable plugin 00371 $this->PluginEnabled[$PluginName] = FALSE; 00372 00373 # record error message about version conflict 00374 $this->ErrMsgs[$PluginName][] = "Plugin <b>" 00375 .$PluginName."</b> is older (<i>" 00376 .addslashes($Attribs["Version"]) 00377 ."</i>) than previously-installed version (<i>" 00378 .addslashes($this->PluginInfo[$PluginName]["Version"])."</i>)."; 00379 } 00380 } 00381 } 00382 } 00383 00384 private function CheckDependencies() 00385 { 00386 # look until all enabled plugins check out okay 00387 do 00388 { 00389 # start out assuming all plugins are okay 00390 $AllOkay = TRUE; 00391 00392 # for each plugin 00393 foreach ($this->Plugins as $PluginName => $Plugin) 00394 { 00395 # if plugin is currently enabled 00396 if ($this->PluginEnabled[$PluginName]) 00397 { 00398 # load plugin attributes 00399 if (!isset($Attribs[$PluginName])) 00400 { 00401 $Attribs[$PluginName] = 00402 $this->Plugins[$PluginName]->GetAttributes(); 00403 } 00404 00405 # for each dependency for this plugin 00406 foreach ($Attribs[$PluginName]["Requires"] 00407 as $ReqName => $ReqVersion) 00408 { 00409 # if target plugin is not present or is disabled or is too old 00410 if (isset($this->Plugins[$ReqName]) 00411 && !isset($Attribs[$ReqName])) 00412 { 00413 $Attribs[$ReqName] = 00414 $this->Plugins[$ReqName]->GetAttributes(); 00415 } 00416 if (!isset($this->PluginEnabled[$ReqName]) 00417 || !$this->PluginEnabled[$ReqName] 00418 || (version_compare($ReqVersion, 00419 $Attribs[$ReqName]["Version"], ">"))) 00420 { 00421 # add error message and disable plugin 00422 $this->ErrMsgs[$PluginName][] = "Plugin <i>" 00423 .$ReqName." ".$ReqVersion."</i>" 00424 ." required by <b>".$PluginName."</b>" 00425 ." was not available."; 00426 $this->PluginEnabled[$PluginName] = FALSE; 00427 00428 # set flag indicating plugin did not check out 00429 $AllOkay = FALSE; 00430 } 00431 } 00432 } 00433 } 00434 } while ($AllOkay == FALSE); 00435 } 00436 00438 function FindPluginPhpFile($PageName) 00439 { 00440 return $this->FindPluginPageFile($PageName, "php"); 00441 } 00445 function FindPluginHtmlFile($PageName) 00446 { 00447 return $this->FindPluginPageFile($PageName, "html"); 00448 } 00451 private function FindPluginPageFile($PageName, $Suffix) 00452 { 00453 # set up return value assuming we will not find plugin page file 00454 $ReturnValue["PageName"] = $PageName; 00455 00456 # look for plugin name and plugin page name in base page name 00457 preg_match("/P_([A-Za-z].[A-Za-z0-9]*)_([A-Za-z0-9_-]+)/", $PageName, $Matches); 00458 00459 # if base page name contained name of existing plugin with its own subdirectory 00460 if ((count($Matches) == 3) && isset($this->PluginDirs[$Matches[1]])) 00461 { 00462 # if PHP file with specified name exists in plugin subdirectory 00463 $PageFile = $this->PluginDirs[$Matches[1]]."/".$Matches[2].".".$Suffix; 00464 if (file_exists($PageFile)) 00465 { 00466 # set return value to contain full plugin PHP file name 00467 $ReturnValue["PageName"] = $PageFile; 00468 } 00469 } 00470 00471 # return array containing page name or page file name to caller 00472 return $ReturnValue; 00473 } 00474 } 00475 00476 ?>