| [ Index ] |
PHP Cross Reference of Akelos Framework |
[Summary view] [Print] [Text view]
1 <?php 2 3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 4 5 // +----------------------------------------------------------------------+ 6 // | Akelos Framework - http://www.akelos.org | 7 // +----------------------------------------------------------------------+ 8 // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez | 9 // | Released under the GNU Lesser General Public License, see LICENSE.txt| 10 // +----------------------------------------------------------------------+ 11 12 13 /** 14 * Plugin manager 15 * 16 * @package Plugins 17 * @subpackage Manager 18 * @author Bermi Ferrer <bermi a.t akelos c.om> 2007 19 * @copyright Copyright (c) 2002-2007, Akelos Media, S.L. http://www.akelos.org 20 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html> 21 */ 22 23 @set_time_limit(0); 24 @ini_set('memory_limit', -1); 25 26 require_once (AK_LIB_DIR.DS.'AkPlugin.php'); 27 28 defined('AK_PLUGINS_MAIN_REPOSITORY') ? null : define('AK_PLUGINS_MAIN_REPOSITORY', 'http://svn.akelos.org/plugins'); 29 defined('AK_PLUGINS_REPOSITORY_DISCOVERY_PAGE') ? null : define('AK_PLUGINS_REPOSITORY_DISCOVERY_PAGE', 'http://wiki.akelos.org/plugins'); 30 31 /** 32 * Plugin manager 33 * 34 * @package Plugins 35 * @subpackage Manager 36 * @author Bermi Ferrer <bermi a.t akelos c.om> 2007 37 * @copyright Copyright (c) 2002-2007, Akelos Media, S.L. http://www.akelos.org 38 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html> 39 */ 40 class AkPluginManager extends AkObject 41 { 42 43 /** 44 * Main repository, must be an Apache mod_svn interface to subversion. Defaults to AK_PLUGINS_MAIN_REPOSITORY. 45 * @var string 46 * @access public 47 */ 48 var $main_repository = AK_PLUGINS_MAIN_REPOSITORY; 49 50 /** 51 * Repository discovery page. 52 * 53 * A wiki page containing links to repositories. Links on that wiki page 54 * must link to an http:// protocol (no SSL yet) and end in plugins. 55 * Defaults to AK_PLUGINS_REPOSITORY_DISCOVERY_PAGE 56 * @var string 57 * @access public 58 */ 59 var $respository_discovery_page = AK_PLUGINS_REPOSITORY_DISCOVERY_PAGE; 60 61 62 63 /** 64 * Gets a list of available repositories. 65 * 66 * @param boolean $force_reload Forces reloading, useful for testing and when running as an application server. 67 * @return array List of repository URLs 68 * @access public 69 */ 70 function getAvailableRepositories($force_reload = false) 71 { 72 if(!empty($this->tmp_repositories)){ 73 return $this->tmp_repositories; 74 } 75 76 if($force_reload || empty($this->repositories)){ 77 $this->repositories = array($this->main_repository); 78 if(file_exists($this->_getRepositoriesConfigPath())){ 79 $repository_candidates = array_diff(array_map('trim', explode("\n",Ak::file_get_contents($this->_getRepositoriesConfigPath()))), array('')); 80 if(!empty($repository_candidates)){ 81 foreach ($repository_candidates as $repository_candidate){ 82 if(strlen($repository_candidate) > 0 && $repository_candidate[0] != '#' && strstr($repository_candidate,'plugins')){ 83 $this->repositories[] = $repository_candidate; 84 } 85 } 86 } 87 } 88 } 89 return $this->repositories; 90 } 91 92 93 94 /** 95 * Ads a repository to the know repositories list. 96 * 97 * @param string $repository_path An Apache mod_svn interface to subversion. 98 * @return void 99 * @access public 100 */ 101 function addRepository($repository_path) 102 { 103 if(!in_array(trim($repository_path), $this->getAvailableRepositories(true))){ 104 Ak::file_add_contents($this->_getRepositoriesConfigPath(), $repository_path."\n"); 105 } 106 } 107 108 109 110 /** 111 * Removes a repository to the know repositories list. 112 * 113 * @param string $repository_path An Apache mod_svn interface to subversion. 114 * @return boolean Returns false if the repository was not available 115 * @access public 116 */ 117 function removeRepository($repository_path) 118 { 119 if(file_exists($this->_getRepositoriesConfigPath())){ 120 $repositories = Ak::file_get_contents($this->_getRepositoriesConfigPath()); 121 if(!strstr($repositories, $repository_path)){ 122 return false; 123 } 124 $repositories = str_replace(array($repository_path, "\r", "\n\n"), array('', "\n", "\n"), $repositories); 125 Ak::file_put_contents($this->_getRepositoriesConfigPath(), $repositories); 126 } 127 } 128 129 130 131 /** 132 * Gets a list of available plugins. 133 * 134 * Goes through each trusted plugin server and retrieves the name of the 135 * folders (plugins) on the repository path. 136 * 137 * @param boolean $force_update If it is not set to true, it will only check remote sources once per hour 138 * @return array Returns an array containing "plugin_name" => "repository URL" 139 * @access public 140 */ 141 function getPlugins($force_update = false) 142 { 143 if($force_update || !is_file($this->_getRepositoriesCahePath()) || filemtime($this->_getRepositoriesCahePath()) > 3600){ 144 if(!$this->_updateRemotePluginsList()){ 145 return array(); 146 } 147 } 148 149 return array_map('trim', Ak::convert('yaml', 'array', Ak::file_get_contents($this->_getRepositoriesCahePath()))); 150 } 151 152 153 154 /** 155 * Retrieves a list of installed plugins 156 * 157 * @return array Returns an array with the plugins available at AK_PLUGINS_DIR 158 * @access public 159 */ 160 function getInstalledPlugins() 161 { 162 $Loader = new AkPluginLoader(); 163 return $Loader->getAvailablePlugins(); 164 } 165 166 167 168 /** 169 * Installs a plugin 170 * 171 * Install a plugin from a remote resource. 172 * 173 * Plugins can have an Akelos installer at located at "plugin_name/installer/plugin_name_installer.php" 174 * If the installer is available, it will run the "PluginNameInstaller::install()" method, which will trigger 175 * all the up_* methods for the installer. 176 * 177 * @param string $plugin_name Plugin name 178 * @param unknown $repository An Apache mod_svn interface to subversion. If not provided it will use a trusted repository. 179 * @param array $options 180 * - externals: Use svn:externals to grab the plugin. Enables plugin updates and plugin versioning. 181 * - checkout: Use svn checkout to grab the plugin. Enables updating but does not add a svn:externals entry. 182 * - revision: Checks out the given revision from subversion. Ignored if subversion is not used. 183 * - force: Overwrite existing files. 184 * @return mixed Returns false if the plugin can't be found. 185 * @access public 186 */ 187 function installPlugin($plugin_name, $repository = null, $options = array()) 188 { 189 $default_options = array( 190 'externals' => false, 191 'checkout' => false, 192 'force' => false, 193 'revision' => null, 194 ); 195 196 $options = array_merge($default_options, $options); 197 198 $plugin_name = Ak::sanitize_include($plugin_name, 'high'); 199 200 $install_method = $this->guessBestInstallMethod($options); 201 202 if($install_method != 'local directory'){ 203 $repository = $this->getRepositoryForPlugin($plugin_name, $repository); 204 } 205 if(!$options['force'] && is_dir(AK_PLUGINS_DIR.DS.$plugin_name)){ 206 trigger_error(Ak::t('Destination directory is not empty. Use force option to overwrite exiting files.'), E_USER_NOTICE); 207 }else{ 208 $method = '_installUsing'.AkInflector::camelize($install_method); 209 $this->$method($plugin_name, rtrim($repository, '/'), $options['revision'], $options['force']); 210 $this->_runInstaller($plugin_name, 'install', $options); 211 } 212 } 213 214 function guessBestInstallMethod($options = array()) 215 { 216 if(defined('AK_BEST_PLUGIN_INSTALL_METHOD') && in_array(AK_BEST_PLUGIN_INSTALL_METHOD, 217 array('local directory', 'checkout', 'export', 'http'))){ 218 return AK_BEST_PLUGIN_INSTALL_METHOD; 219 } 220 if(!empty($options['parameters']) && is_dir($options['parameters'])){ 221 return 'local directory'; 222 }elseif($this->canUseSvn()){ 223 if(!empty($options['externals']) && $this->_shouldUseSvnExternals()){ 224 return 'externals'; 225 }elseif(!empty($options['checkout']) && $this->_shouldUseSvnCheckout()){ 226 return 'checkout'; 227 } 228 return 'export'; 229 }else{ 230 return 'http'; 231 } 232 } 233 234 function canUseSvn() 235 { 236 return strstr(`svn --version`, 'CollabNet'); 237 } 238 239 240 /** 241 * Updates a plugin if there are changes. 242 * 243 * Uses subversion update if available. If http update is used, it will 244 * download the whole plugin unless there is a CHANGELOG file, in which case 245 * it will only perform the update if there are changes. 246 * 247 * @param string $plugin_name Plugin name 248 * @param string $repository An Apache mod_svn interface to subversion. If not provided it will use a trusted repository. 249 * @return null 250 * @access public 251 */ 252 function updatePlugin($plugin_name, $repository = null) 253 { 254 $options = array( 255 'externals' => false, 256 'checkout' => false 257 ); 258 259 $plugin_name = Ak::sanitize_include($plugin_name, 'high'); 260 261 $method = '_updateUsing'.AkInflector::camelize($this->guessBestInstallMethod($options)); 262 $this->$method($plugin_name, rtrim($this->getRepositoryForPlugin($plugin_name, $repository), '/')); 263 264 $this->_runInstaller($plugin_name, 'install'); 265 } 266 267 268 /** 269 * Uninstalls an existing plugin 270 * 271 * Plugins can have an Akelos installer at located at "plugin_name/installer/plugin_name_installer.php" 272 * If the installer is available, it will run the "PluginNameInstaller::uninstall()" method, which will trigger 273 * all the down_* methods for the installer. 274 * 275 * @param string $plugin_name Plugin name 276 * @return void 277 * @access public 278 */ 279 function uninstallPlugin($plugin_name) 280 { 281 $plugin_name = Ak::sanitize_include($plugin_name, 'high'); 282 $this->_runInstaller($plugin_name, 'uninstall'); 283 if(is_dir(AK_PLUGINS_DIR.DS.$plugin_name)){ 284 Ak::directory_delete(AK_PLUGINS_DIR.DS.$plugin_name); 285 } 286 if($this->_shouldUseSvnExternals()){ 287 $this->_uninstallExternals($plugin_name); 288 } 289 } 290 291 292 /** 293 * Gets a list of repositories available at the web page defined by AK_PLUGINS_REPOSITORY_DISCOVERY_PAGE (http://wiki.akelos.org/plugins by default) 294 * 295 * @return array An array of non trusted repositories available at http://wiki.akelos.org/plugins 296 * @access public 297 */ 298 function getDiscoveredRepositories() 299 { 300 return array_diff($this->_getRepositoriesFromRemotePage(), $this->getAvailableRepositories(true)); 301 } 302 303 304 /** 305 * Returns the repository for a given $plugin_name 306 * 307 * @param string $plugin_name The name of the plugin 308 * @param string $repository If a repository name is provided it will check for the plugin name existance. 309 * @return mixed Repository URL or false if plugin can't be found 310 * @access public 311 */ 312 function getRepositoryForPlugin($plugin_name, $repository = null) 313 { 314 if(empty($repository)){ 315 $available_plugins = $this->getPlugins(); 316 }else{ 317 $available_plugins = array(); 318 $this->_addAvailablePlugins_($repository, &$available_plugins); 319 } 320 321 if(empty($available_plugins[$plugin_name])){ 322 trigger_error(Ak::t('Could not find %plugin_name plugin', array('%plugin_name' => $plugin_name)), E_USER_NOTICE); 323 return false; 324 }elseif (empty($repository)){ 325 $repository = $available_plugins[$plugin_name]; 326 } 327 return $repository; 328 } 329 330 /** 331 * Runs the plugin installer/uninstaller if available 332 * 333 * Plugins can have an Akelos installer at located at "plugin_name/installer/plugin_name_installer.php" 334 * If the installer is available, it will run the "PluginNameInstaller::install/uninstall()" method, which will trigger 335 * all the up/down_* methods for the installer. 336 * 337 * @param string $plugin_name The name of the plugin 338 * @param string $install_or_uninstall What to do, options are install or uninstall 339 * @return void 340 * @access private 341 */ 342 function _runInstaller($plugin_name, $install_or_uninstall = 'install', $options = array()) 343 { 344 $plugin_dir = AK_PLUGINS_DIR.DS.$plugin_name; 345 if(file_exists($plugin_dir.DS.'installer'.DS.$plugin_name.'_installer.php')){ 346 require_once (AK_LIB_DIR.DS.'AkInstaller.php'); 347 require_once (AK_LIB_DIR.DS.'AkPluginInstaller.php'); 348 require_once($plugin_dir.DS.'installer'.DS.$plugin_name.'_installer.php'); 349 $class_name = AkInflector::camelize($plugin_name.'_installer'); 350 if(class_exists($class_name)){ 351 $Installer =& new $class_name(null,$plugin_name); 352 $Installer->options = $options; 353 $Installer->db->debug = false; 354 $Installer->warn_if_same_version = false; 355 $Installer->$install_or_uninstall(); 356 } 357 } 358 } 359 360 361 /** 362 * Retrieves the URL's from the AK_PLUGINS_REPOSITORY_DISCOVERY_PAGE (http://wiki.akelos.org/plugins by default) 363 * 364 * Plugins in that page must follow this convention: 365 * 366 * * Only http:// protocol. No https:// or svn:// support yet 367 * * The URL must en in plugins to be fetched automatically 368 * 369 * @return array An array of existing repository URLs 370 * @access private 371 */ 372 function _getRepositoriesFromRemotePage() 373 { 374 375 $repositories = array(); 376 if(preg_match_all('/href="(http:\/\/(?!wiki\.akelos\.org)[^"]*plugins)/', Ak::url_get_contents($this->respository_discovery_page), $matches)){ 377 $repositories = array_unique($matches[1]); 378 } 379 return $repositories; 380 } 381 382 /** 383 * Copy recursively a remote svn dir into a local path. 384 * 385 * Downloads recursively the contents of remote directories from a mod_svn Apache subversion interface to a local destination. 386 * 387 * File or directory permissions are not copied, so you will need to use installers to fix it if required. 388 * 389 * @param string $source An Apache mod_svn interface to subversion URL. 390 * @param string $destination Destination directory 391 * @return void 392 * @access private 393 */ 394 function _copyRemoteDir($source, $destination) 395 { 396 $dir_name = trim(substr($source, strrpos(rtrim($source, '/'), '/')),'/'); 397 Ak::make_dir($destination.DS.$dir_name); 398 399 list($directories, $files) = $this->_parseRemoteAndGetDirectoriesAndFiles($source); 400 401 foreach ($files as $file){ 402 $this->_copyRemoteFile($source.$file, $destination.DS.$dir_name.DS.$file); 403 } 404 405 foreach ($directories as $directory){ 406 $this->_copyRemoteDir($source.$directory.'/', $destination.DS.$dir_name); 407 } 408 } 409 410 411 412 /** 413 * Copies a remote file into a local destination 414 * 415 * @param string $source Source URL 416 * @param string $destination Destination directory 417 * @return void 418 * @access private 419 */ 420 function _copyRemoteFile($source, $destination) 421 { 422 Ak::file_put_contents($destination, Ak::url_get_contents($source)); 423 } 424 425 426 427 /** 428 * Performs an update of available cached plugins. 429 * 430 * @return boolean 431 * @access private 432 */ 433 function _updateRemotePluginsList() 434 { 435 $new_plugins = array(); 436 foreach ($this->getAvailableRepositories() as $repository){ 437 $this->_addAvailablePlugins_($repository, $new_plugins); 438 } 439 if(empty($new_plugins)){ 440 trigger_error(Ak::t('Could not fetch remote plugins from one of these repositories: %repositories', array('%repositories' => "\n".join("\n", $this->getAvailableRepositories()))), E_USER_NOTICE); 441 return false; 442 } 443 return Ak::file_put_contents($this->_getRepositoriesCahePath(), Ak::convert('array', 'yaml', $new_plugins)); 444 } 445 446 447 448 /** 449 * Modifies $plugins_list adding the plugins available at $repository 450 * 451 * @param string $repository Repository URL 452 * @param array $plugins_list Plugins list in the format 'plugin_name' => 'repository' 453 * @return void 454 * @access private 455 */ 456 function _addAvailablePlugins_($repository, &$plugins_list) 457 { 458 list($directories) = $this->_parseRemoteAndGetDirectoriesAndFiles($repository); 459 foreach ($directories as $plugin){ 460 if(empty($plugins_list[$plugin])){ 461 $plugins_list[$plugin] = $repository; 462 } 463 } 464 } 465 466 467 468 /** 469 * Parses a remote Apache svn web page and returns a list of available files and directories 470 * 471 * @param string $remote_path Repository URL 472 * @return array an array like array($directories, $files). Use list($directories, $files) = $this->_parseRemoteAndGetDirectoriesAndFiles($remote_path) for getting the results of this method 473 * @access private 474 */ 475 function _parseRemoteAndGetDirectoriesAndFiles($remote_path) 476 { 477 $directories = $files = array(); 478 $remote_contents = Ak::url_get_contents(rtrim($remote_path, '/').'/'); 479 480 if(preg_match_all('/href="([A-Za-z\-_0-9]+)\/"/', $remote_contents, $matches)){ 481 foreach ($matches[1] as $directory){ 482 $directories[] = trim($directory); 483 } 484 } 485 if(preg_match_all('/href="(\.?[A-Za-z\-_0-9\.]+)"/', $remote_contents, $matches)){ 486 foreach ($matches[1] as $file){ 487 $files[] = trim($file); 488 } 489 } 490 return array($directories, $files); 491 } 492 493 494 495 /** 496 * Trusted repositories location 497 * 498 * By default trusted repositories are located at config/plugin_repositories.txt 499 * 500 * @return string Trusted repositories path 501 * @access private 502 */ 503 function _getRepositoriesConfigPath() 504 { 505 if(empty($this->tmp_repositories)){ 506 return AK_CONFIG_DIR.DS.'plugin_repositories.txt'; 507 }else{ 508 return AK_TMP_DIR.DS.'plugin_repositories.'.md5(serialize($this->tmp_repositories)); 509 } 510 } 511 512 513 514 /** 515 * Cached informations about available plugins 516 * 517 * @return string Plugin information cache path. By default AK_TMP_DIR.DS.'plugin_repositories.yaml' 518 * @access private 519 */ 520 function _getRepositoriesCahePath() 521 { 522 return AK_TMP_DIR.DS.'plugin_repositories.yaml'; 523 } 524 525 526 527 function _shouldUseSvnExternals() 528 { 529 return is_dir(AK_PLUGINS_DIR.DS.'.svn'); 530 } 531 532 function _shouldUseSvnCheckout() 533 { 534 return is_dir(AK_PLUGINS_DIR.DS.'.svn'); 535 } 536 537 function _installUsingCheckout($name, $uri, $rev = null, $force = false) 538 { 539 $rev = empty($rev) ? '' : " -r $rev "; 540 $force = $force ? ' --force ' : ''; 541 $plugin_dir = AK_PLUGINS_DIR.DS.$name; 542 `svn co $force $rev $uri/$name $plugin_dir`; 543 } 544 545 function _updateUsingCheckout($name) 546 { 547 $plugin_dir = AK_PLUGINS_DIR.DS.$name; 548 `svn update $plugin_dir`; 549 } 550 551 function _installUsingLocalDirectory($name, $path, $rev = null) 552 { 553 $source = $path.DS.$name; 554 $plugin_dir = AK_PLUGINS_DIR; 555 $command = AK_OS == 'UNIX' ? 'cp -rf ' : 'xcopy /h /r /k /x /y /S /E '; 556 `$command $source $plugin_dir`; 557 } 558 559 function _updateUsingLocalDirectory($name) 560 { 561 trigger_error(Ak::t('Updating from local targets it\'s not supported yet. Please use install --force instead.')); 562 } 563 564 function _installUsingExport($name, $uri, $rev = null, $force = false) 565 { 566 $rev = empty($rev) ? '' : " -r $rev "; 567 $force = $force ? ' --force ' : ''; 568 $plugin_dir = AK_PLUGINS_DIR.DS.$name; 569 `svn export $force $rev $uri/$name $plugin_dir`; 570 } 571 572 function _updateUsingExport($name, $uri) 573 { 574 $plugin_dir = AK_PLUGINS_DIR.DS.$name; 575 `svn export --force $uri/$name $plugin_dir`; 576 } 577 578 function _installUsingExternals($name, $uri, $rev = null, $force = false) 579 { 580 $extras = empty($rev) ? '' : " -r $rev "; 581 $extras .= ($force ? ' --force ' : ''); 582 $externals = $this->_getExternals(); 583 $externals[$name] = $uri; 584 $this->_setExternals($externals, $extras); 585 $this->_installUsingCheckout($name, $uri, $rev, $force); 586 } 587 588 function _updateUsingExternals($name) 589 { 590 $this->_updateUsingCheckout($name); 591 } 592 593 function _updateUsingHttp($name, $uri) 594 { 595 if(is_file(AK_PLUGINS_DIR.DS.$name.DS.'CHANGELOG') && 596 md5(Ak::url_get_contents(rtrim($uri, '/').'/'.$name.'/CHANGELOG')) == md5_file(AK_PLUGINS_DIR.DS.$name.DS.'CHANGELOG')){ 597 return false; 598 } 599 $this->_copyRemoteDir(rtrim($uri, '/').'/'.$name.'/', AK_PLUGINS_DIR); 600 } 601 602 603 function _setExternals($items, $extras = '') 604 { 605 $externals = array(); 606 foreach ($items as $name => $uri){ 607 $externals[] = "$name ".rtrim($uri, '/'); 608 } 609 $tmp_file = AK_TMP_DIR.DS.Ak::uuid(); 610 $plugins_dir = AK_PLUGINS_DIR; 611 Ak::file_put_contents($tmp_file, join("\n", $externals)); 612 `svn propset $extras -q svn:externals -F "$tmp_file" "$plugins_dir"`; 613 Ak::file_delete($tmp_file); 614 } 615 616 function _uninstallExternals($name) 617 { 618 $externals = $this->_getExternals(); 619 unset($externals[$name]); 620 $this->_setExternals($externals); 621 } 622 623 function _getExternals() 624 { 625 if($this->_shouldUseSvnExternals()){ 626 $plugins_dir = AK_PLUGINS_DIR; 627 $svn_externals = array_diff(array_map('trim',(array)explode("\n", `svn propget svn:externals "$plugins_dir"`)), array('')); 628 $externals = array(); 629 foreach ($svn_externals as $svn_external){ 630 list($name, $uri) = explode(' ', trim($svn_external)); 631 $externals[$name] = $uri; 632 } 633 return $externals; 634 }else{ 635 return array(); 636 } 637 } 638 639 function _installUsingHttp($name, $uri) 640 { 641 $this->_copyRemoteDir(rtrim($uri, '/').'/'.$name.'/', AK_PLUGINS_DIR); 642 } 643 644 } 645 646 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Oct 27 12:43:49 2008 | Cross-referenced by PHPXref 0.6 |