PageRenderTime 294ms CodeModel.GetById 134ms app.highlight 42ms RepoModel.GetById 102ms app.codeStats 1ms

/Devblocks.class.php

https://github.com/rmiddle/devblocks
PHP | 5093 lines | 3748 code | 745 blank | 600 comment | 456 complexity | 4899a3fbd1abe25fcd0f0adcaa994fe3 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2include_once(DEVBLOCKS_PATH . "api/Model.php");
   3include_once(DEVBLOCKS_PATH . "api/DAO.php");
   4include_once(DEVBLOCKS_PATH . "api/Extension.php");
   5
   6define('PLATFORM_BUILD',2010120101);
   7
   8/**
   9 * A platform container for plugin/extension registries.
  10 *
  11 * @author Jeff Standen <jeff@webgroupmedia.com>
  12 */
  13class DevblocksPlatform extends DevblocksEngine {
  14    const CACHE_ACL = 'devblocks_acl';
  15    const CACHE_EVENT_POINTS = 'devblocks_event_points';
  16    const CACHE_EVENTS = 'devblocks_events';
  17    const CACHE_EXTENSIONS = 'devblocks_extensions';
  18    const CACHE_PLUGINS = 'devblocks_plugins';
  19    const CACHE_POINTS = 'devblocks_points';
  20    const CACHE_SETTINGS = 'devblocks_settings';
  21    const CACHE_STORAGE_PROFILES = 'devblocks_storage_profiles';
  22    const CACHE_TABLES = 'devblocks_tables';
  23    const CACHE_TAG_TRANSLATIONS = 'devblocks_translations';
  24    
  25    static private $extensionDelegate = null;
  26    
  27    static private $start_time = 0;
  28    static private $start_memory = 0;
  29    static private $start_peak_memory = 0;
  30    
  31    static private $locale = 'en_US';
  32    
  33    static private $_tmp_files = array();
  34    
  35    private function __construct() { return false; }
  36
  37	/**
  38	 * @param mixed $value
  39	 * @param string $type
  40	 * @param mixed $default
  41	 * @return mixed
  42	 */
  43	static function importVar($value,$type=null,$default=null) {
  44		if(is_null($value) && !is_null($default))
  45			$value = $default;
  46		
  47		// Sanitize input
  48		switch($type) {
  49			case 'array':
  50				@settype($value,$type);
  51				break;
  52			case 'bit':
  53				$value = !empty($value) ? 1 : 0;
  54				break;
  55			case 'boolean':
  56				$value = !empty($value) ? true : false;
  57				break;
  58			case 'float':
  59				$value = floatval($value);
  60				break;
  61			case 'integer':
  62				$value = intval($value);
  63				break;
  64			case 'string':
  65				$value = (string) $value;
  66				break;
  67			case 'timestamp':
  68				if(!is_numeric($value)) {
  69					try {
  70						$value = strtotime($value);	
  71					} catch(Exception $e) {}
  72				} else {
  73					$value = abs(intval($value));	
  74				}
  75				break;
  76			default:
  77				@settype($value,$type);
  78				break;
  79		}
  80		
  81		return $value;		
  82	}    
  83    
  84	/**
  85	 * @param mixed $var
  86	 * @param string $cast
  87	 * @param mixed $default
  88	 * @return mixed
  89	 */
  90	static function importGPC($var,$cast=null,$default=null) {
  91	    if(!is_null($var)) {
  92	        if(is_string($var)) {
  93	            $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
  94	        } elseif(is_array($var)) {
  95                foreach($var as $k => $v) {
  96                    $var[$k] = get_magic_quotes_gpc() ? stripslashes($v) : $v;
  97                }
  98	        }
  99	        
 100	    } elseif (is_null($var) && !is_null($default)) {
 101	        $var = $default;
 102	    }
 103
 104	    if(!is_null($cast))
 105	    	$var = self::importVar($var, $cast, $default);
 106	    
 107	    return $var;
 108	}
 109
 110	/**
 111	 * Returns a string as a regexp. 
 112	 * "*bob" returns "/(.*?)bob/".
 113	 */
 114	static function parseStringAsRegExp($string) {
 115		$pattern = str_replace(array('*'),'__any__', $string);
 116		$pattern = sprintf("/%s/i",str_replace(array('__any__'),'(.*?)', preg_quote($pattern)));
 117		return $pattern;
 118	}
 119	
 120	/**
 121	 * Returns a formatted string as a number of bytes (e.g. 200M = 209715200)
 122	 * 
 123	 * @param string $string
 124	 * @return integer|FALSE
 125	 */
 126	static function parseBytesString($string) {
 127	    if(is_numeric($string)) { 
 128	        return intval($string);
 129	        
 130	    } else { 
 131	        $value = intval($string); 
 132	        $unit = strtolower(substr($string, -1)); 
 133	         
 134	        switch($unit) { 
 135	            default: 
 136	            case 'm': 
 137	                return $value * 1048576; // 1024^2
 138	                break; 
 139	            case 'g': 
 140	                return $value * 1073741824; // 1024^3 
 141	                break;
 142	            case 'k': 
 143	                return $value * 1024; // 1024^1
 144	                break; 
 145	        }
 146	    }
 147	    
 148	    return FALSE;
 149	}
 150	
 151	static function parseCrlfString($string) {
 152		$parts = preg_split("/[\r\n]/", $string);
 153		
 154		// Remove any empty tokens
 155		foreach($parts as $idx => $part) {
 156			$parts[$idx] = trim($part);
 157			if(0 == strlen($parts[$idx])) 
 158				unset($parts[$idx]);
 159		}
 160		
 161		return $parts;
 162	}
 163	
 164	/**
 165	 * Return a string as a regular expression, parsing * into a non-greedy 
 166	 * wildcard, etc.
 167	 *
 168	 * @param string $arg
 169	 * @return string
 170	 */
 171	static function strToRegExp($arg, $is_partial=false) {
 172		$arg = str_replace(array('*'),array('__WILD__'),$arg);
 173		
 174		return sprintf("/%s%s%s/i",
 175			($is_partial ? '' : '^'),
 176			str_replace(array('__WILD__','/'),array('.*?','\/'),preg_quote($arg)),
 177			($is_partial ? '' : '$')
 178		);
 179	}
 180	
 181	/**
 182	 * Return a string with only its alphanumeric characters
 183	 *
 184	 * @param string $arg
 185	 * @return string
 186	 */
 187	static function strAlphaNum($arg) {
 188		return preg_replace("/[^A-Z0-9\.]/i","", $arg);
 189	}
 190	
 191	/**
 192	 * Return a string with only its alphanumeric characters or punctuation
 193	 *
 194	 * @param string $arg
 195	 * @return string
 196	 */
 197	static function strAlphaNumDash($arg) {
 198		return preg_replace("/[^A-Z0-9_\-\.]/i","", $arg);
 199	}	
 200
 201	/**
 202	 * Return a string with only its alphanumeric characters or underscore
 203	 *
 204	 * @param string $arg
 205	 * @return string
 206	 */
 207	static function strAlphaNumUnder($arg) {
 208		return preg_replace("/[^A-Z0-9_]/i","", $arg);
 209	}	
 210	
 211	static function stripHTML($str) {
 212		// Strip all CRLF and tabs, spacify </TD>
 213		$str = str_ireplace(
 214			array("\r","\n","\t","</td>"),
 215			array('','',' ',' '),
 216			trim($str)
 217		);
 218		
 219		// Handle XHTML variations
 220		$str = str_ireplace(
 221			array("<br />", "<br/>"),
 222			"<br>",
 223			$str
 224		);
 225		
 226		// Turn block tags into a linefeed
 227		$str = str_ireplace(
 228			array('<BR>','<P>','</P>','<HR>','</TR>','</H1>','</H2>','</H3>','</H4>','</H5>','</H6>','</DIV>'),
 229			"\n",
 230			$str
 231		);		
 232		
 233		// Strip tags
 234		$search = array(
 235			'@<script[^>]*?>.*?</script>@si',
 236		    '@<style[^>]*?>.*?</style>@siU',
 237		    '@<[\/\!]*?[^<>]*?>@si',
 238		    '@<![\s\S]*?--[ \t\n\r]*>@',
 239		);
 240		$str = preg_replace($search, '', $str);
 241		
 242		// Flatten multiple spaces into a single
 243		$str = preg_replace('# +#', ' ', $str);
 244
 245		// Translate HTML entities into text
 246		$str = html_entity_decode($str, ENT_COMPAT, LANG_CHARSET_CODE);
 247
 248		// Loop through each line, ltrim, and concat if not empty
 249		$lines = explode("\n", $str);
 250		if(is_array($lines)) {
 251			$str = '';
 252			$blanks = 0;
 253			foreach($lines as $idx => $line) {
 254				$lines[$idx] = ltrim($line);
 255				
 256				if(empty($lines[$idx])) {
 257					if(++$blanks >= 2)
 258						unset($lines[$idx]);
 259						//continue; // skip more than 2 blank lines in a row
 260				} else {
 261					$blanks = 0;
 262				}
 263			}
 264			$str = implode("\n", $lines);
 265		}
 266		unset($lines);
 267		
 268		// Clean up bytes (needed after HTML entities)
 269		$str = mb_convert_encoding($str, LANG_CHARSET_CODE, LANG_CHARSET_CODE);
 270		
 271		return $str;
 272	}	
 273	
 274	static function parseMarkdown($text) {
 275		static $parser = null;
 276		
 277		if(is_null($parser))
 278			$parser = new Markdown_Parser();
 279			
 280		return $parser->transform($text);
 281	}
 282	
 283	static function parseRss($url) {
 284		// [TODO] curl | file_get_contents() support
 285		// [TODO] rss://
 286		
 287		if(null == (@$data = file_get_contents($url)))
 288			return false;
 289		
 290		if(null == (@$xml = simplexml_load_string($data)))
 291			return false;
 292			
 293		// [TODO] Better detection of RDF/RSS/Atom + versions 
 294		$root_tag = strtolower(dom_import_simplexml($xml)->tagName);
 295		
 296		if('feed'==$root_tag && count($xml->entry)) { // Atom
 297			$feed = array(
 298				'title' => (string) $xml->title,
 299				'url' => $url,
 300				'items' => array(),
 301			);
 302			
 303			if(!count($xml))
 304				return $feed;
 305	
 306			foreach($xml->entry as $entry) {
 307				$id = (string) $entry->id;
 308				$date = (string) $entry->published;
 309				$title = (string) $entry->title;
 310				$content = (string) $entry->summary;
 311				$link = '';
 312				
 313				// Link as the <id> element
 314				if(preg_match("/^(.*)\:\/\/(.*$)/i", $id, $matches)) {
 315					$link = $id;
 316				// Link as 'alternative' attrib
 317				} elseif(count($entry->link)) {
 318					foreach($entry->link as $link) {
 319						if(0==strcasecmp('alternate',(string)$link['rel'])) {
 320							$link = (string) $link['href'];
 321							break;
 322						}
 323					}
 324				}
 325				 
 326				$feed['items'][] = array(
 327					'date' => strtotime($date),
 328					'link' => $link,
 329					'title' => $title,
 330					'content' => $content,
 331				);
 332			}
 333			
 334		} elseif('rdf:rdf'==$root_tag && count($xml->item)) { // RDF
 335			$feed = array(
 336				'title' => (string) $xml->channel->title,
 337				'url' => $url,
 338				'items' => array(),
 339			);
 340			
 341			if(!count($xml))
 342				return $feed;
 343	
 344			foreach($xml->item as $item) {
 345				$date = (string) $item->pubDate;
 346				$link = (string) $item->link; 
 347				$title = (string) $item->title;
 348				$content = (string) $item->description;
 349				
 350				$feed['items'][] = array(
 351					'date' => strtotime($date),
 352					'link' => $link,
 353					'title' => $title,
 354					'content' => $content,
 355				);
 356			}
 357			
 358		} elseif('rss'==$root_tag && count($xml->channel->item)) { // RSS
 359			$feed = array(
 360				'title' => (string) $xml->channel->title,
 361				'url' => $url,
 362				'items' => array(),
 363			);
 364			
 365			if(!count($xml))
 366				return $feed;
 367	
 368			foreach($xml->channel->item as $item) {
 369				$date = (string) $item->pubDate;
 370				$link = (string) $item->link; 
 371				$title = (string) $item->title;
 372				$content = (string) $item->description;
 373				
 374				$feed['items'][] = array(
 375					'date' => strtotime($date),
 376					'link' => $link,
 377					'title' => $title,
 378					'content' => $content,
 379				);
 380			}
 381		}
 382
 383		if(empty($feed))
 384			return false;
 385		
 386		return $feed;
 387	}
 388	
 389	/**
 390	 * Returns a string as alphanumerics delimited by underscores.
 391	 * For example: "Devs: 1000 Ways to Improve Sales" becomes 
 392	 * "devs_1000_ways_to_improve_sales", which is suitable for 
 393	 * displaying in a URL of a blog, faq, etc.
 394	 *
 395	 * @param string $str
 396	 * @return string
 397	 */
 398	static function getStringAsURI($str) {
 399		$str = strtolower($str);
 400		
 401		// turn non [a-z, 0-9, _] into whitespace
 402		$str = preg_replace("/[^0-9a-z]/",' ',$str);
 403		
 404		// condense whitespace to a single underscore
 405		$str = preg_replace('/\s\s+/', ' ', $str);
 406
 407		// replace spaces with underscore
 408		$str = str_replace(' ','_',$str);
 409
 410		// remove a leading/trailing underscores
 411		$str = trim($str, '_');
 412		
 413		return $str;
 414	}
 415	
 416	/**
 417	 * Takes a comma-separated value string and returns an array of tokens.
 418	 * [TODO] Move to a FormHelper service?
 419	 * 
 420	 * @param string $string
 421	 * @return array
 422	 */
 423	static function parseCsvString($string) {
 424		$tokens = explode(',', $string);
 425
 426		if(!is_array($tokens))
 427			return array();
 428		
 429		foreach($tokens as $k => $v) {
 430			$tokens[$k] = trim($v);
 431			if(0==strlen($tokens[$k]))
 432				unset($tokens[$k]);
 433		}
 434		
 435		return $tokens;
 436	}
 437	
 438	/**
 439	 * Clears any platform-level plugin caches.
 440	 * 
 441	 */
 442	static function clearCache($one_cache=null) {
 443	    $cache = self::getCacheService(); /* @var $cache _DevblocksCacheManager */
 444
 445	    if(!empty($one_cache)) {
 446	    	$cache->remove($one_cache);
 447	    	
 448	    } else { // All
 449		    $cache->remove(self::CACHE_ACL);
 450		    $cache->remove(self::CACHE_PLUGINS);
 451		    $cache->remove(self::CACHE_EVENT_POINTS);
 452		    $cache->remove(self::CACHE_EVENTS);
 453		    $cache->remove(self::CACHE_EXTENSIONS);
 454		    $cache->remove(self::CACHE_POINTS);
 455		    $cache->remove(self::CACHE_SETTINGS);
 456		    $cache->remove(self::CACHE_TABLES);
 457		    $cache->remove(_DevblocksClassLoadManager::CACHE_CLASS_MAP);
 458		    
 459		    // Clear all locale caches
 460		    $langs = DAO_Translation::getDefinedLangCodes();
 461		    if(is_array($langs) && !empty($langs))
 462		    foreach($langs as $lang_code => $lang_name) {
 463		    	$cache->remove(self::CACHE_TAG_TRANSLATIONS . '_' . $lang_code);
 464		    }
 465	    }
 466
 467	    // Cache-specific 'after' actions
 468	    switch($one_cache) {
 469	    	case self::CACHE_PLUGINS:
 470	    	case self::CACHE_EXTENSIONS:
 471	    	case NULL:
 472				self::getPluginRegistry();
 473				self::getExtensionRegistry();
 474	    		break;
 475	    }
 476	}
 477
 478	public static function loadClass($className) {
 479		$classloader = self::getClassLoaderService();
 480		return $classloader->loadClass($className);
 481	}
 482	
 483	public static function registerClasses($file,$classes=array()) {
 484		$classloader = self::getClassLoaderService();
 485		return $classloader->registerClasses($file,$classes);
 486	}
 487	
 488	public static function getStartTime() {
 489		return self::$start_time;
 490	}
 491	
 492	public static function getStartMemory() {
 493		return self::$start_memory;
 494	}
 495	
 496	public static function getStartPeakMemory() {
 497		return self::$start_peak_memory;
 498	}
 499	
 500	/**
 501	 * @return resource $fp
 502	 */
 503	public static function getTempFile() {
 504		// Generate a new temporary file
 505		$file_name = tempnam(APP_TEMP_PATH, 'tmp');
 506		
 507		// Open the file pointer
 508		$fp = fopen($file_name, "w+b");
 509		
 510		// Manually keep track of these temporary files
 511		self::$_tmp_files[intval($fp)] = $file_name;
 512		return $fp;
 513	}
 514	
 515	/**
 516	 * @return resource $fp
 517	 */
 518	public static function getTempFileInfo($fp) {
 519		// If we're asking about a specific temporary file
 520		if(!empty($fp)) {
 521			if(@isset(self::$_tmp_files[intval($fp)]))
 522				return self::$_tmp_files[intval($fp)];
 523			return false;
 524		}
 525	}
 526
 527	/**
 528	 * Checks whether the active database has any tables.
 529	 * 
 530	 * @return boolean
 531	 */
 532	static function isDatabaseEmpty() {
 533		$tables = self::getDatabaseTables();
 534	    return empty($tables);
 535	}
 536	
 537	static function getDatabaseTables() {
 538	    $cache = self::getCacheService();
 539	    $tables = array();
 540	    
 541	    if(null === ($tables = $cache->load(self::CACHE_TABLES))) {
 542	        $db = self::getDatabaseService();
 543	        
 544	        // Make sure the database connection is valid or error out.
 545	        if(is_null($db) || !$db->isConnected())
 546	        	return array();
 547	        
 548	        $tables = $db->metaTables();
 549	        $cache->save($tables, self::CACHE_TABLES);
 550	    }
 551	    return $tables;
 552	}
 553
 554	/**
 555	 * Checks to see if the application needs to patch
 556	 *
 557	 * @return boolean
 558	 */
 559	static function versionConsistencyCheck() {
 560		$cache = DevblocksPlatform::getCacheService(); /* @var _DevblocksCacheManager $cache */ 
 561		
 562		if(null === ($build_cache = $cache->load("devblocks_app_build"))
 563			|| $build_cache != APP_BUILD) {
 564				
 565			// If build changed, clear cache regardless of patch status
 566			// [TODO] We need to find a nicer way to not clear a shared memcached cluster when only one desk needs to
 567			$cache = DevblocksPlatform::getCacheService(); /* @var $cache _DevblocksCacheManager */
 568			$cache->clean();
 569			
 570			// Re-read manifests
 571			DevblocksPlatform::readPlugins();
 572			
 573			if(self::_needsToPatch()) {
 574				return false; // the update script will handle new caches
 575			} else {
 576				$cache->save(APP_BUILD, "devblocks_app_build");
 577				DAO_Translation::reloadPluginStrings(); // reload strings even without DB changes
 578				return true;
 579			}
 580		}
 581		
 582		return true;
 583	}
 584	
 585	/**
 586	 * Enter description here...
 587	 *
 588	 * @return boolean
 589	 */
 590	static private function _needsToPatch() {
 591		 $plugins = DevblocksPlatform::getPluginRegistry();
 592		 
 593		 // First install or upgrade
 594		 if(empty($plugins))
 595		 	return true;
 596
 597		 foreach($plugins as $plugin) { /* @var $plugin DevblocksPluginManifest */
 598		 	if($plugin->enabled) {
 599		 		foreach($plugin->getPatches() as $patch) { /* @var $patch DevblocksPatch */
 600		 			if(!$patch->hasRun())
 601		 				return true;
 602		 		}
 603		 	}
 604		 }
 605		 
 606		 return false;
 607	}
 608	
 609	/**
 610	 * Returns the list of extensions on a given extension point.
 611	 *
 612	 * @static
 613	 * @param string $point
 614	 * @return DevblocksExtensionManifest[]
 615	 */
 616	static function getExtensions($point,$as_instances=false, $ignore_acl=false) {
 617	    $results = array();
 618	    $extensions = DevblocksPlatform::getExtensionRegistry($ignore_acl);
 619
 620	    if(is_array($extensions))
 621	    foreach($extensions as $extension) { /* @var $extension DevblocksExtensionManifest */
 622	        if($extension->point == $point) {
 623	            $results[$extension->id] = ($as_instances) ? $extension->createInstance() : $extension;
 624	        }
 625	    }
 626	    return $results;
 627	}
 628
 629	/**
 630	 * Returns the manifest of a given extension ID.
 631	 *
 632	 * @static
 633	 * @param string $extension_id
 634	 * @param boolean $as_instance
 635	 * @return DevblocksExtensionManifest
 636	 */
 637	static function getExtension($extension_id, $as_instance=false, $ignore_acl=false) {
 638	    $result = null;
 639	    $extensions = DevblocksPlatform::getExtensionRegistry($ignore_acl);
 640
 641	    if(is_array($extensions))
 642	    foreach($extensions as $extension) { /* @var $extension DevblocksExtensionManifest */
 643	        if($extension->id == $extension_id) {
 644	            $result = $extension;
 645	            break;
 646	        }
 647	    }
 648
 649	    if($as_instance && !is_null($result)) {
 650	    	return $result->createInstance();
 651	    }
 652	    
 653	    return $result;
 654	}
 655
 656//	static function getExtensionPoints() {
 657//	    $cache = self::getCacheService();
 658//	    if(null !== ($points = $cache->load(self::CACHE_POINTS)))
 659//	        return $points;
 660//
 661//	    $extensions = DevblocksPlatform::getExtensionRegistry();
 662//	    foreach($extensions as $extension) { /* @var $extension DevblocksExtensionManifest */
 663//	        $point = $extension->point;
 664//	        if(!isset($points[$point])) {
 665//	            $p = new DevblocksExtensionPoint();
 666//	            $p->id = $point;
 667//	            $points[$point] = $p;
 668//	        }
 669//	        	
 670//	        $points[$point]->extensions[$extension->id] = $extension;
 671//	    }
 672//
 673//	    $cache->save($points, self::CACHE_POINTS);
 674//	    return $points;
 675//	}
 676
 677	/**
 678	 * Returns an array of all contributed extension manifests.
 679	 *
 680	 * @static 
 681	 * @return DevblocksExtensionManifest[]
 682	 */
 683	static function getExtensionRegistry($ignore_acl=false) {
 684	    $cache = self::getCacheService();
 685	    static $acl_extensions = null;
 686	    
 687	    if(null === ($extensions = $cache->load(self::CACHE_EXTENSIONS))) {
 688		    $db = DevblocksPlatform::getDatabaseService();
 689		    if(is_null($db)) return;
 690	
 691		    $prefix = (APP_DB_PREFIX != '') ? APP_DB_PREFIX.'_' : ''; // [TODO] Cleanup
 692	
 693		    $sql = sprintf("SELECT e.id , e.plugin_id, e.point, e.pos, e.name , e.file , e.class, e.params ".
 694				"FROM %sextension e ".
 695				"INNER JOIN %splugin p ON (e.plugin_id=p.id) ".
 696				"WHERE p.enabled = 1 ".
 697				"ORDER BY e.id ASC, e.plugin_id ASC, e.pos ASC",
 698					$prefix,
 699					$prefix
 700				);
 701			$results = $db->GetArray($sql); 
 702				
 703			foreach($results as $row) {
 704			    $extension = new DevblocksExtensionManifest();
 705			    $extension->id = $row['id'];
 706			    $extension->plugin_id = $row['plugin_id'];
 707			    $extension->point = $row['point'];
 708			    $extension->name = $row['name'];
 709			    $extension->file = $row['file'];
 710			    $extension->class = $row['class'];
 711			    $extension->params = @unserialize($row['params']);
 712		
 713			    if(empty($extension->params))
 714					$extension->params = array();
 715				$extensions[$extension->id] = $extension;
 716			}
 717
 718			$cache->save($extensions, self::CACHE_EXTENSIONS);
 719			$acl_extensions = null;
 720		}
 721		
 722		if(!$ignore_acl) {
 723			// If we don't have a cache in this request
 724			if(null == $acl_extensions) {
 725				// Check with an extension delegate if we have one
 726				if(class_exists(self::$extensionDelegate) && method_exists('DevblocksExtensionDelegate','shouldLoadExtension')) {
 727					if(is_array($extensions))
 728					foreach($extensions as $id => $extension) {
 729						// Ask the delegate if we should load the extension
 730						if(!call_user_func(array(self::$extensionDelegate,'shouldLoadExtension'),$extension))
 731							unset($extensions[$id]);
 732					}
 733				}
 734				// Cache for duration of request
 735				$acl_extensions = $extensions;
 736			} else {
 737				$extensions = $acl_extensions;
 738			}
 739		}
 740		
 741		return $extensions;
 742	}
 743
 744	/**
 745	 * @return DevblocksEventPoint[]
 746	 */
 747	static function getEventPointRegistry() {
 748	    $cache = self::getCacheService();
 749	    if(null !== ($events = $cache->load(self::CACHE_EVENT_POINTS)))
 750    	    return $events;
 751
 752        $events = array();
 753        $plugins = self::getPluginRegistry();
 754    	 
 755		// [JAS]: Event point hashing/caching
 756		if(is_array($plugins))
 757		foreach($plugins as $plugin) { /* @var $plugin DevblocksPluginManifest */
 758            $events = array_merge($events,$plugin->event_points);
 759		}
 760    	
 761		$cache->save($events, self::CACHE_EVENT_POINTS);
 762		return $events;
 763	}
 764	
 765	/**
 766	 * @return DevblocksAclPrivilege[]
 767	 */
 768	static function getAclRegistry() {
 769	    $cache = self::getCacheService();
 770	    if(null !== ($acl = $cache->load(self::CACHE_ACL)))
 771    	    return $acl;
 772
 773        $acl = array();
 774
 775	    $db = DevblocksPlatform::getDatabaseService();
 776	    if(is_null($db)) return;
 777
 778        //$plugins = self::getPluginRegistry();
 779	    $prefix = (APP_DB_PREFIX != '') ? APP_DB_PREFIX.'_' : ''; // [TODO] Cleanup
 780
 781	    $sql = sprintf("SELECT a.id, a.plugin_id, a.label ".
 782			"FROM %sacl a ".
 783			"INNER JOIN %splugin p ON (a.plugin_id=p.id) ".
 784			"WHERE p.enabled = 1 ".
 785			"ORDER BY a.plugin_id, a.id ASC",
 786			$prefix,
 787			$prefix
 788		);
 789		$results = $db->GetArray($sql); 
 790		
 791		foreach($results as $row) {
 792			$priv = new DevblocksAclPrivilege();
 793			$priv->id = $row['id'];
 794			$priv->plugin_id = $row['plugin_id'];
 795			$priv->label = $row['label'];
 796			
 797		    $acl[$priv->id] = $priv;
 798		}
 799        
 800		$cache->save($acl, self::CACHE_ACL);
 801		return $acl;
 802	}
 803	
 804	static function getEventRegistry() {
 805	    $cache = self::getCacheService();
 806	    if(null !== ($events = $cache->load(self::CACHE_EVENTS)))
 807    	    return $events;
 808	    
 809    	$extensions = self::getExtensions('devblocks.listener.event',false,true);
 810    	$events = array('*');
 811    	 
 812		// [JAS]: Event point hashing/caching
 813		if(is_array($extensions))
 814		foreach($extensions as $extension) { /* @var $extension DevblocksExtensionManifest */
 815            @$evts = $extension->params['events'][0];
 816            
 817            // Global listeners (every point)
 818            if(empty($evts) && !is_array($evts)) {
 819                $events['*'][] = $extension->id;
 820                continue;
 821            }
 822            
 823            if(is_array($evts))
 824            foreach(array_keys($evts) as $evt) {
 825                $events[$evt][] = $extension->id; 
 826            }
 827		}
 828    	
 829		$cache->save($events, self::CACHE_EVENTS);
 830		return $events;
 831	}
 832	
 833	/**
 834	 * Returns an array of all contributed plugin manifests.
 835	 *
 836	 * @static
 837	 * @return DevblocksPluginManifest[]
 838	 */
 839	static function getPluginRegistry() {
 840	    $cache = self::getCacheService();
 841	    if(null !== ($plugins = $cache->load(self::CACHE_PLUGINS)))
 842    	    return $plugins;
 843
 844	    $db = DevblocksPlatform::getDatabaseService();
 845	    if(is_null($db))
 846	    	return;
 847
 848		$plugins = array();
 849	    	
 850	    $prefix = (APP_DB_PREFIX != '') ? APP_DB_PREFIX.'_' : ''; // [TODO] Cleanup
 851
 852	    $sql = sprintf("SELECT p.* ".
 853			"FROM %splugin p ".
 854			"ORDER BY p.enabled DESC, p.name ASC ",
 855			$prefix
 856		);
 857		$results = $db->GetArray($sql); 
 858
 859		foreach($results as $row) {
 860		    $plugin = new DevblocksPluginManifest();
 861		    @$plugin->id = $row['id'];
 862		    @$plugin->enabled = intval($row['enabled']);
 863		    @$plugin->name = $row['name'];
 864		    @$plugin->description = $row['description'];
 865		    @$plugin->author = $row['author'];
 866		    @$plugin->revision = intval($row['revision']);
 867		    @$plugin->link = $row['link'];
 868		    @$plugin->dir = $row['dir'];
 869
 870		    // JSON decode
 871		    if(isset($row['manifest_cache_json'])
 872		    	&& null != ($manifest_cache_json = $row['manifest_cache_json'])) {
 873		    	$plugin->manifest_cache = json_decode($manifest_cache_json, true);
 874		    }
 875
 876		    if(file_exists(APP_PATH . DIRECTORY_SEPARATOR . $plugin->dir . DIRECTORY_SEPARATOR . 'plugin.xml'))
 877	        	$plugins[$plugin->id] = $plugin;
 878		}
 879
 880		$sql = sprintf("SELECT p.id, p.name, p.params, p.plugin_id ".
 881		    "FROM %sevent_point p ",
 882		    $prefix
 883		);
 884		$results = $db->GetArray($sql); 
 885
 886		foreach($results as $row) {
 887		    $point = new DevblocksEventPoint();
 888		    $point->id = $row['id'];
 889		    $point->name = $row['name'];
 890		    $point->plugin_id = $row['plugin_id'];
 891		    
 892		    $params = $row['params'];
 893		    $point->params = !empty($params) ? unserialize($params) : array();
 894
 895		    if(isset($plugins[$point->plugin_id])) {
 896		    	$plugins[$point->plugin_id]->event_points[$point->id] = $point;
 897		    }
 898		}
 899		
 900		self::_sortPluginsByDependency($plugins);
 901		
 902		$cache->save($plugins, self::CACHE_PLUGINS);
 903		return $plugins;
 904	}
 905	
 906	static private function _sortPluginsByDependency(&$plugins) {
 907		$dependencies = array();
 908		$seen = array();
 909		$order = array();
 910		
 911        // Dependencies
 912		foreach($plugins as $plugin) {
 913			@$deps = $plugin->manifest_cache['dependencies'];
 914			$dependencies[$plugin->id] = is_array($deps) ? $deps : array();
 915		}
 916		
 917		foreach($plugins as $plugin)
 918			self::_recursiveDependency($plugin->id, $dependencies, $seen, $order);
 919
 920		$original = $plugins;
 921		$plugins = array();
 922			
 923		foreach($order as $order_id) {
 924			$plugins[$order_id] = $original[$order_id];
 925		}
 926	}
 927
 928	static private function _recursiveDependency($id, $deps, &$seen, &$order, $level=0) {
 929		if(isset($seen[$id]))
 930			return true;
 931	
 932		if(isset($deps[$id]) && !empty($deps[$id])) {
 933			foreach($deps[$id] as $dep) {
 934				if(!self::_recursiveDependency($dep, $deps, $seen, $order, ++$level))
 935					return false;
 936			}
 937		}
 938		
 939		if(!isset($seen[$id])) {
 940			$order[] = $id;
 941			$seen[$id] = true;
 942		}
 943		
 944		return true;
 945	}	
 946	
 947	/**
 948	 * Enter description here...
 949	 *
 950	 * @param string $id
 951	 * @return DevblocksPluginManifest
 952	 */
 953	static function getPlugin($id) {
 954	    $plugins = DevblocksPlatform::getPluginRegistry();
 955
 956	    if(isset($plugins[$id]))
 957	    	return $plugins[$id];
 958		
 959	    return null;
 960	}
 961
 962	/**
 963	 * Reads and caches manifests from the features + plugins directories.
 964	 *
 965	 * @static 
 966	 * @return DevblocksPluginManifest[]
 967	 */
 968	static function readPlugins() {
 969		$scan_dirs = array(
 970			'features',
 971			'storage/plugins',
 972		);
 973		
 974	    $plugins = array();
 975
 976	    // Devblocks
 977	    if(null !== ($manifest = self::_readPluginManifest('libs/devblocks')))
 978	    	$plugin[] = $manifest;
 979	    	
 980	    // Application
 981	    if(is_array($scan_dirs))
 982	    foreach($scan_dirs as $scan_dir) {
 983	    	$scan_path = APP_PATH . '/' . $scan_dir;
 984		    if (is_dir($scan_path)) {
 985		        if ($dh = opendir($scan_path)) {
 986		            while (($file = readdir($dh)) !== false) {
 987		                if($file=="." || $file == "..")
 988		                	continue;
 989		                	
 990		                $plugin_path = $scan_path . '/' . $file;
 991		                $rel_path = $scan_dir . '/' . $file;
 992		                
 993		                if(is_dir($plugin_path) && file_exists($plugin_path.'/plugin.xml')) {
 994		                    $manifest = self::_readPluginManifest($rel_path); /* @var $manifest DevblocksPluginManifest */
 995	
 996		                    if(null != $manifest) {
 997		                        $plugins[$manifest->id] = $manifest;
 998		                    }
 999		                }
1000		            }
1001		            closedir($dh);
1002		        }
1003		    }
1004	    }
1005	    
1006	    DevblocksPlatform::clearCache(DevblocksPlatform::CACHE_PLUGINS);
1007	    
1008	    return $plugins;
1009	}
1010
1011	/**
1012	 * @return _DevblocksPluginSettingsManager
1013	 */
1014	static function getPluginSettingsService() {
1015		return _DevblocksPluginSettingsManager::getInstance();
1016	}
1017	
1018	static function getPluginSetting($plugin_id, $key, $default=null) {
1019		$settings = self::getPluginSettingsService();
1020		return $settings->get($plugin_id, $key, $default);
1021	}
1022	
1023	static function setPluginSetting($plugin_id, $key, $value) {
1024		$settings = self::getPluginSettingsService();
1025		return $settings->set($plugin_id,  $key, $value);
1026	}
1027
1028	/**
1029	 * @return _DevblocksLogManager
1030	 */
1031	static function getConsoleLog() {
1032		return _DevblocksLogManager::getConsoleLog();
1033	}
1034	
1035	/**
1036	 * @return _DevblocksCacheManager
1037	 */
1038	static function getCacheService() {
1039	    return _DevblocksCacheManager::getInstance();
1040	}
1041	
1042	/**
1043	 * Enter description here...
1044	 *
1045	 * @return _DevblocksDatabaseManager
1046	 */
1047	static function getDatabaseService() {
1048	    return _DevblocksDatabaseManager::getInstance();
1049	}
1050
1051	/**
1052	 * @return _DevblocksUrlManager
1053	 */
1054	static function getUrlService() {
1055	    return _DevblocksUrlManager::getInstance();
1056	}
1057
1058	/**
1059	 * @return _DevblocksEmailManager
1060	 */
1061	static function getMailService() {
1062	    return _DevblocksEmailManager::getInstance();
1063	}
1064
1065	/**
1066	 * @return _DevblocksEventManager
1067	 */
1068	static function getEventService() {
1069	    return _DevblocksEventManager::getInstance();
1070	}
1071	
1072	/**
1073	 * @return DevblocksProxy
1074	 */
1075	static function getProxyService() {
1076	    return DevblocksProxy::getProxy();
1077	}
1078	
1079	/**
1080	 * @return _DevblocksClassLoadManager
1081	 */
1082	static function getClassLoaderService() {
1083		return _DevblocksClassLoadManager::getInstance();
1084	}
1085	
1086	/**
1087	 * @return _DevblocksSessionManager
1088	 */
1089	static function getSessionService() {
1090	    return _DevblocksSessionManager::getInstance();
1091	}
1092	
1093	/**
1094	 * @return _DevblocksOpenIDManager 
1095	 */
1096	static function getOpenIDService() {
1097		return _DevblocksOpenIDManager::getInstance();
1098	}
1099	
1100	/**
1101	 * @return _DevblocksSearchEngineMysqlFulltext
1102	 */
1103	static function getSearchService() {
1104		return _DevblocksSearchManager::getInstance();
1105	}
1106	
1107	/**
1108	 * @param $profile_id | $extension_id, $options
1109	 * @return Extension_DevblocksStorageEngine
1110	 */
1111	static function getStorageService() {
1112		$args = func_get_args();
1113
1114		if(empty($args))
1115			return false;
1116		
1117		$profile = $args[0];
1118		$params = array();
1119		
1120		// Handle $profile polymorphism
1121		if($profile instanceof Model_DevblocksStorageProfile) {
1122			$extension = $profile->extension_id;
1123			$params = $profile->params;
1124		} else if(is_numeric($profile)) {
1125			$storage_profile = DAO_DevblocksStorageProfile::get($profile);
1126			$extension = $storage_profile->extension_id;
1127			$params = $storage_profile->params;
1128		} else if(is_string($profile)) {
1129			$extension = $profile;
1130			
1131			if(isset($args[1]) && is_array($args[1]))
1132				$params = $args[1];
1133		}
1134		
1135	    return _DevblocksStorageManager::getEngine($extension, $params);
1136	}
1137
1138	/**
1139	 * @return Smarty
1140	 */
1141	static function getTemplateService() {
1142	    return _DevblocksTemplateManager::getInstance();
1143	}
1144
1145	/**
1146	 * 
1147	 * @param string $set
1148	 * @return DevblocksTemplate[]
1149	 */
1150	static function getTemplates($set=null) {
1151		$templates = array();
1152		$plugins = self::getPluginRegistry();
1153		
1154		if(is_array($plugins))
1155		foreach($plugins as $plugin) {
1156			if(isset($plugin->manifest_cache['templates']) && is_array($plugin->manifest_cache['templates']))
1157			foreach($plugin->manifest_cache['templates'] as $tpl) {
1158				if(null === $set || 0 == strcasecmp($set, $tpl['set'])) {
1159					$template = new DevblocksTemplate();
1160					$template->plugin_id = $tpl['plugin_id'];
1161					$template->set = $tpl['set'];
1162					$template->path = $tpl['path'];
1163					$templates[] = $template;
1164				}
1165			}
1166		}
1167		
1168		return $templates;
1169	}
1170	
1171	/**
1172	 * @return _DevblocksTemplateBuilder
1173	 */
1174	static function getTemplateBuilder() {
1175	    return _DevblocksTemplateBuilder::getInstance();
1176	}
1177
1178	/**
1179	 * @return _DevblocksDateManager
1180	 */
1181	static function getDateService($datestamp=null) {
1182		return _DevblocksDateManager::getInstance();
1183	}
1184
1185	static function setLocale($locale) {
1186		@setlocale(LC_ALL, $locale);
1187		self::$locale = $locale;
1188	}
1189	
1190	static function getLocale() {
1191		if(!empty(self::$locale))
1192			return self::$locale;
1193			
1194		return 'en_US';
1195	}
1196	
1197	/**
1198	 * @return _DevblocksTranslationManager
1199	 */
1200	static function getTranslationService() {
1201		static $languages = array();
1202		$locale = DevblocksPlatform::getLocale();
1203
1204		// Registry
1205		if(isset($languages[$locale])) {
1206			return $languages[$locale];
1207		}
1208						
1209		$cache = self::getCacheService();
1210	    
1211	    if(null === ($map = $cache->load(self::CACHE_TAG_TRANSLATIONS.'_'.$locale))) { /* @var $cache _DevblocksCacheManager */
1212			$map = array();
1213			$map_en = DAO_Translation::getMapByLang('en_US');
1214			if(0 != strcasecmp('en_US', $locale))
1215				$map_loc = DAO_Translation::getMapByLang($locale);
1216			
1217			// Loop through the English string objects
1218			if(is_array($map_en))
1219			foreach($map_en as $string_id => $obj_string_en) {
1220				$string = '';
1221				
1222				// If we have a locale to check
1223				if(isset($map_loc) && is_array($map_loc)) {
1224					@$obj_string_loc = $map_loc[$string_id];
1225					@$string =
1226						(!empty($obj_string_loc->string_override))
1227						? $obj_string_loc->string_override
1228						: $obj_string_loc->string_default;
1229				}
1230				
1231				// If we didn't hit, load the English default
1232				if(empty($string))
1233				@$string = 
1234					(!empty($obj_string_en->string_override))
1235					? $obj_string_en->string_override
1236					: $obj_string_en->string_default;
1237					
1238				// If we found any match
1239				if(!empty($string))
1240					$map[$string_id] = $string;
1241			}
1242			unset($obj_string_en);
1243			unset($obj_string_loc);
1244			unset($map_en);
1245			unset($map_loc);
1246			
1247			// Cache with tag (tag allows easy clean for multiple langs at once)
1248			$cache->save($map,self::CACHE_TAG_TRANSLATIONS.'_'.$locale);
1249	    }
1250	    
1251		$translate = _DevblocksTranslationManager::getInstance();
1252		$translate->addLocale($locale, $map);
1253		$translate->setLocale($locale);
1254	    
1255		$languages[$locale] = $translate;
1256
1257	    return $translate;
1258	}
1259
1260	/**
1261	 * Enter description here...
1262	 *
1263	 * @return DevblocksHttpRequest
1264	 */
1265	static function getHttpRequest() {
1266	    return self::$request;
1267	}
1268
1269	/**
1270	 * @param DevblocksHttpRequest $request
1271	 */
1272	static function setHttpRequest(DevblocksHttpRequest $request) {
1273	    self::$request = $request;
1274	}
1275
1276	/**
1277	 * Enter description here...
1278	 *
1279	 * @return DevblocksHttpRequest
1280	 */
1281	static function getHttpResponse() {
1282	    return self::$response;
1283	}
1284
1285	/**
1286	 * @param DevblocksHttpResponse $response
1287	 */
1288	static function setHttpResponse(DevblocksHttpResponse $response) {
1289	    self::$response = $response;
1290	}
1291
1292	/**
1293	 * Initializes the plugin platform (paths, etc).
1294	 *
1295	 * @static 
1296	 * @return void
1297	 */
1298	static function init() {
1299		self::$start_time = microtime(true);
1300		if(function_exists('memory_get_usage') && function_exists('memory_get_peak_usage')) {
1301			self::$start_memory = memory_get_usage();
1302			self::$start_peak_memory = memory_get_peak_usage();
1303		}
1304		
1305		// Encoding (mbstring)
1306		mb_internal_encoding(LANG_CHARSET_CODE);
1307		
1308	    // [JAS] [MDF]: Automatically determine the relative webpath to Devblocks files
1309	    @$proxyhost = $_SERVER['HTTP_DEVBLOCKSPROXYHOST'];
1310	    @$proxybase = $_SERVER['HTTP_DEVBLOCKSPROXYBASE'];
1311    
1312		// App path (always backend)
1313	
1314		$app_self = $_SERVER["SCRIPT_NAME"];
1315		
1316        if(DEVBLOCKS_REWRITE) {
1317            $pos = strrpos($app_self,'/');
1318            $app_self = substr($app_self,0,$pos) . '/';
1319        } else {
1320            $pos = strrpos($app_self,'index.php');
1321            if(false === $pos) $pos = strrpos($app_self,'ajax.php');
1322            $app_self = substr($app_self,0,$pos);
1323        }
1324		
1325		// Context path (abstracted: proxies or backend)
1326		
1327        if(!empty($proxybase)) { // proxy
1328            $context_self = $proxybase . '/';
1329		} else { // non-proxy
1330			$context_self = $app_self;
1331		}
1332		
1333        @define('DEVBLOCKS_WEBPATH',$context_self);
1334        @define('DEVBLOCKS_APP_WEBPATH',$app_self);
1335        
1336        // Register shutdown function
1337        register_shutdown_function(array('DevblocksPlatform','shutdown'));
1338	}
1339	
1340	static function shutdown() {
1341		// Clean up any temporary files
1342		while(null != ($tmpfile = array_pop(self::$_tmp_files))) {
1343			@unlink($tmpfile);
1344		}
1345	}
1346
1347	static function setExtensionDelegate($class) {
1348		if(!empty($class) && class_exists($class, true))
1349			self::$extensionDelegate = $class;
1350	}
1351	
1352	static function redirect(DevblocksHttpIO $httpIO) {
1353		$url_service = self::getUrlService();
1354		session_write_close();
1355		$url = $url_service->writeDevblocksHttpIO($httpIO, true);
1356		header('Location: '.$url);
1357		exit;
1358	}
1359};
1360
1361abstract class DevblocksEngine {
1362	protected static $request = null;
1363	protected static $response = null;
1364	
1365	/**
1366	 * Reads and caches a single manifest from a given plugin directory.
1367	 * 
1368	 * @static 
1369	 * @private
1370	 * @param string $dir
1371	 * @return DevblocksPluginManifest
1372	 */
1373	static protected function _readPluginManifest($rel_dir, $persist=true) {
1374		$manifest_file = APP_PATH . '/' . $rel_dir . '/plugin.xml'; 
1375		
1376		if(!file_exists($manifest_file))
1377			return NULL;
1378		
1379		$plugin = simplexml_load_file($manifest_file);
1380		$prefix = (APP_DB_PREFIX != '') ? APP_DB_PREFIX.'_' : ''; // [TODO] Cleanup
1381				
1382		$manifest = new DevblocksPluginManifest();
1383		$manifest->id = (string) $plugin->id;
1384		$manifest->dir = $rel_dir;
1385		$manifest->description = (string) $plugin->description;
1386		$manifest->author = (string) $plugin->author;
1387		$manifest->revision = (integer) $plugin->revision;
1388		$manifest->link = (string) $plugin->link;
1389		$manifest->name = (string) $plugin->name;
1390		
1391		// Dependencies
1392		if(isset($plugin->dependencies)) {
1393			if(isset($plugin->dependencies->require))
1394			foreach($plugin->dependencies->require as $eDependency) {
1395				$depends_on = (string) $eDependency['plugin_id'];
1396				$manifest->manifest_cache['dependencies'][] = $depends_on;
1397			}
1398		}
1399		
1400		// Patches
1401		if(isset($plugin->patches)) {
1402			if(isset($plugin->patches->patch))
1403			foreach($plugin->patches->patch as $ePatch) {
1404				$patch_version = (string) $ePatch['version'];
1405				$patch_revision = (string) $ePatch['revision'];
1406				$patch_file = (string) $ePatch['file'];
1407				$manifest->manifest_cache['patches'][] = array(
1408					'version' => $patch_version,
1409					'revision' => $patch_revision,
1410					'file' => $patch_file,
1411				);
1412			}
1413		}
1414		
1415		// Templates
1416		if(isset($plugin->templates)) {
1417			foreach($plugin->templates as $eTemplates) {
1418				$template_set = (string) $eTemplates['set'];
1419				
1420				if(isset($eTemplates->template))
1421				foreach($eTemplates->template as $eTemplate) {
1422					$manifest->manifest_cache['templates'][] = array(
1423						'plugin_id' => $manifest->id,
1424						'set' => $template_set,
1425						'path' => (string) $eTemplate['path'],
1426					);
1427				}
1428			}
1429		}
1430		
1431		// Image
1432		if(isset($plugin->image)) {
1433			$manifest->manifest_cache['plugin_image'] = (string) $plugin->image;
1434		}
1435			
1436		if(!$persist)
1437			return $manifest;
1438		
1439		$db = DevblocksPlatform::getDatabaseService();
1440		if(is_null($db)) 
1441			return;
1442			
1443		// Persist manifest
1444		if($db->GetOne(sprintf("SELECT id FROM ${prefix}plugin WHERE id = %s", $db->qstr($manifest->id)))) { // update
1445			$db->Execute(sprintf(
1446				"UPDATE ${prefix}plugin ".
1447				"SET name=%s,description=%s,author=%s,revision=%s,link=%s,dir=%s,manifest_cache_json=%s ".
1448				"WHERE id=%s",
1449				$db->qstr($manifest->name),
1450				$db->qstr($manifest->description),
1451				$db->qstr($manifest->author),
1452				$db->qstr($manifest->revision),
1453				$db->qstr($manifest->link),
1454				$db->qstr($manifest->dir),
1455				$db->qstr(json_encode($manifest->manifest_cache)),
1456				$db->qstr($manifest->id)
1457			));
1458			
1459		} else { // insert
1460			$enabled = ('devblocks.core'==$manifest->id) ? 1 : 0;
1461			$db->Execute(sprintf(
1462				"INSERT INTO ${prefix}plugin (id,enabled,name,description,author,revision,link,dir,manifest_cache_json) ".
1463				"VALUES (%s,%d,%s,%s,%s,%s,%s,%s,%s)",
1464				$db->qstr($manifest->id),
1465				$enabled,
1466				$db->qstr($manifest->name),
1467				$db->qstr($manifest->description),
1468				$db->qstr($manifest->author),
1469				$db->qstr($manifest->revision),
1470				$db->qstr($manifest->link),
1471				$db->qstr($manifest->dir),
1472				$db->qstr(json_encode($manifest->manifest_cache))
1473			));
1474		}
1475		
1476		// Class Loader
1477		if(isset($plugin->class_loader->file)) {
1478			foreach($plugin->class_loader->file as $eFile) {
1479				@$sFilePath = (string) $eFile['path'];
1480				$manifest->class_loader[$sFilePath] = array();
1481				
1482				if(isset($eFile->class))
1483				foreach($eFile->class as $eClass) {
1484					@$sClassName = (string) $eClass['name'];
1485					$manifest->class_loader[$sFilePath][] = $sClassName;
1486				}
1487			}
1488		}
1489		
1490		// Routing
1491		if(isset($plugin->uri_routing->uri)) {
1492			foreach($plugin->uri_routing->uri as $eUri) {
1493				@$sUriName = (string) $eUri['name'];
1494				@$sController = (string) $eUri['controller'];
1495				$manifest->uri_routing[$sUriName] = $sController;
1496			}
1497		}
1498		
1499		// ACL
1500		if(isset($plugin->acl->priv)) {
1501			foreach($plugin->acl->priv as $ePriv) {
1502				@$sId = (string) $ePriv['id'];
1503				@$sLabel = (string) $ePriv['label'];
1504				
1505				if(empty($sId) || empty($sLabel))
1506					continue;
1507					
1508				$priv = new DevblocksAclPrivilege();
1509				$priv->id = $sId;
1510				$priv->plugin_id = $manifest->id;
1511				$priv->label = $sLabel;
1512				
1513				$manifest->acl_privs[$priv->id] = $priv;
1514			}
1515			asort($manifest->acl_privs);
1516		}
1517		
1518		// Event points
1519		if(isset($plugin->event_points->event)) {
1520		    foreach($plugin->event_points->event as $eEvent) {
1521		        $sId = (string) $eEvent['id'];
1522		        $sName = (string) $eEvent->name;
1523		        
1524		        if(empty($sId) || empty($sName))
1525		            continue;
1526		        
1527		        $point = new DevblocksEventPoint();
1528		        $point->id = $sId;
1529		        $point->plugin_id = $plugin->id;
1530		        $point->name = $sName;
1531		        $point->params = array();
1532		        
1533		        if(isset($eEvent->param)) {
1534		            foreach($eEvent->param as $eParam) {
1535		                $key = (string) $eParam['key']; 
1536		                $val = (string) $eParam['value']; 
1537		                $point->param[$key] = $val;
1538		            }
1539		        }
1540		        
1541		        $manifest->event_points[] = $point;
1542		    }
1543		}
1544		
1545		// Extensions
1546		if(isset($plugin->extensions->extension)) {
1547		    foreach($plugin->extensions->extension as $eExtension) {
1548		        $sId = (string) $eExtension->id;
1549		        $sName = (string) $eExtension->name;
1550		        
1551		        if(empty($sId) || empty($sName))
1552		            continue;
1553		        
1554		        $extension = new DevblocksExtensionManifest();
1555		        
1556		        $extension->id = $sId;
1557		        $extension->plugin_id = $manifest->id;
1558		        $extension->point = (string) $eExtension['point'];
1559		        $extension->name = $sName;
1560		        $extension->file = (string) $eExtension->class->file;
1561		        $extension->class = (string) $eExtension->class->name;
1562		        
1563		        if(isset($eExtension->params->param)) {
1564		            foreach($eExtension->params->param as $eParam) {
1565				$key = (string) $eParam['key'];
1566		                if(isset($eParam->value)) {
1567					// [JSJ]: If there is a child of the param tag named value, then this 
1568					//        param has multiple values and thus we need to grab them all.
1569					foreach($eParam->value as $eValue) {
1570						// [JSJ]: If there is a child named data, then this is a complex structure
1571						if(isset($eValue->data)) {
1572							$value = array();
1573							foreach($eValue->data as $eData) {
1574								$key2 = (string) $eData['key'];
1575								if(isset($eData['value'])) {
1576									$value[$key2] = (string) $eData['value'];
1577								} else {
1578									$value[$key2] = (string) $eData;
1579								}
1580							}
1581						}
1582						else {
1583							// [JSJ]: Else, just grab the value and use it
1584							$value = (string) $eValue;
1585						}
1586						$extension->params[$key][] = $value;
1587						unset($value); // Just to be extra safe
1588					}
1589				}
1590				else {
1591					// [JSJ]: Otherwise, we grab the single value from the params value attribute.
1592					$extension->params[$key] = (string) $eParam['value'];
1593				}
1594		            }
1595		        }
1596		        
1597		        $manifest->extensions[] = $extension;
1598		    }
1599		}
1600
1601		// [JAS]: Extension caching
1602		$new_extensions = array();
1603		if(is_array($manifest->extensions))
1604		foreach($manifest->extensions as $pos => $extension) { /* @var $extension DevblocksExtensionManifest */
1605			$db->Execute(sprintf(
1606				"REPLACE INTO ${prefix}extension (id,plugin_id,point,pos,name,file,class,params) ".
1607				"VALUES (%s,%s,%s,%d,%s,%s,%s,%s)",
1608				$db->qstr($extension->id),
1609				$db->qstr($extension->plugin_id),
1610				$db->qstr($extension->point),
1611				$pos,
1612				$db->qstr($extension->name),
1613				$db->qstr($extension->file),
1614				$db->qstr($extension->class),
1615				$db->qstr(serialize($extension->params))
1616			));
1617			
1618			$new_extensions[$extension->id] = true;
1619		}
1620		
1621		/*
1622		 * Compare our loaded XML manifest to the DB manifest cache and invalidate 
1623		 * the cache for extensions that are no longer in the XML.
1624		 */
1625		$sql = sprintf("SELECT id FROM %sextension WHERE plugin_id = %s",
1626			$prefix,
1627			$db->qstr($plugin->id)
1628		);
1629		$results = $db->GetArray($sql);
1630
1631		foreach($results as $row) {
1632			$plugin_ext_id = $row['id'];
1633			if(!isset($new_extensions[$plugin_ext_id]))
1634				DAO_Platform::deleteExtension($plugin_ext_id);
1635		}
1636		
1637		// Class loader cache
1638		$db->Execute(sprintf("DELETE FROM %sclass_loader WHERE plugin_id = %s",$prefix,$db->qstr($plugin->id)));
1639		if(is_array($manifest->class_loader))
1640		foreach($manifest->class_loader as $file_path => $classes) {
1641			if(is_array($classes) && !empty($classes))
1642			foreach($classes as $class)
1643			$db->Execute(sprintf(
1644				"REPLACE INTO ${prefix}class_loader (class,plugin_id,rel_path) ".
1645				"VALUES (%s,%s,%s)",
1646				$db->qstr($class),
1647				$db->qstr($manifest->id),
1648				$db->qstr($file_path)	
1649			));			
1650		}
1651		
1652		// URI routing cache
1653		$db->Execute(sprintf("DELETE FROM %suri_routing WHERE plugin_id = %s",$prefix,$db->qstr($plugin->id)));
1654		if(is_array($manifest->uri_routing))
1655		foreach($manifest->uri_routing as $uri => $controller_id) {
1656			$db->Execute(sprintf(
1657				"REPLACE INTO ${prefix}uri_routing (uri,plugin_id,controller_id) ".
1658				"VALUES (%s,%s,%s)",
1659				$db->qstr($uri),
1660				$db->qstr($manifest->id),
1661				$db->qstr($controller_id)	
1662			));			
1663		}
1664
1665		// ACL caching
1666		$db->Execute(sprintf("DELETE FROM %sacl WHERE plugin_id = %s",$prefix,$db->qstr($plugin->id)));
1667		if(is_array($manifest->acl_privs))
1668		foreach($manifest->acl_privs as $priv) { /* @var $priv DevblocksAclPrivilege */
1669			$db->Execute(sprintf(
1670				"REPLACE INTO ${prefix}acl (id,plugin_id,label) ".
1671				"VALUES (%s,%s,%s)",
1672				$db->qstr($priv->id),
1673				$db->qstr($priv->plugin_id),
1674				$db->qstr($priv->label)
1675			));			
1676		}
1677		
1678        // [JAS]: Event point caching
1679		if(is_array($manifest->event_points))
1680		foreach($manifest->event_points as $event) { /* @var $event DevblocksEventPoint */
1681			$db->Execute(sprintf(
1682				"REPLACE INTO ${prefix}event_point (id,plugin_id,name,params) ".
1683				"VALUES (%s,%s,%s,%s)",
1684				$db->qstr($event->id),
1685				$db->qstr($event->plugin_id),
1686				$db->qstr($event->name),
1687				$db->qstr(serialize($event->params))	
1688			));
1689		}
1690		
1691		return $manifest;
1692	}
1693	
1694	static function getWebPath() {
1695		$location = "";
1696		
1697		// Read the relative URL into an array
1698		if(isset($_SERVER['HTTP_X_REWRITE_URL'])) { // IIS Rewrite
1699			$location = $_SERVER['HTTP_X_REWRITE_URL'];
1700		} elseif(isset($_SERVER['REQUEST_URI'])) { // Apache
1701			$location = $_SERVER['REQUEST_URI'];
1702		} elseif(isset($_SERVER['REDIRECT_URL'])) { // Apache mod_rewrite (breaks on CGI)
1703			$location = $_SERVER['REDIRECT_URL'];
1704		} elseif(isset($_SERVER['ORIG_PATH_INFO'])) { // IIS + CGI
1705			$location = $_SERVER['ORIG_PATH_INFO'];
1706		}
1707		
1708		return $location;
1709	}
1710	
1711	/**
1712	 * Reads the HTTP Request object.
1713	 * 
1714	 * @return DevblocksHttpRequest
1715	 */
1716	static function readRequest() {
1717		$url = DevblocksPlatform::getUrlService();
1718
1719		$location = self::getWebPath();
1720		
1721		$parts = $url->parseURL($location);
1722		
1723		// Add any query string arguments (?arg=value&arg=value)
1724		@$query = $_SERVER['QUERY_STRING'];
1725		$queryArgs = $url->parseQueryString($query);
1726		
1727		if(empty($parts)) {
1728			// Overrides (Form POST, etc.)
1729
1730			// Controller (GET has precedence over POST)
1731			if(isset($_GET['c'])) {
1732				@$uri = DevblocksPlatform::importGPC($_GET['c']); // extension
1733			} elseif (isset($_POST['c'])) {
1734				@$uri = DevblocksPlatform::importGPC($_POST['c']); // extension
1735			}
1736			if(!empty($uri)) $parts[] = DevblocksPlatform::strAlphaNum($uri);
1737
1738			// Action (GET has precedence over POST)
1739			if(isset($_GET['a'])) {
1740				@$listener = DevblocksPlatform::importGPC($_GET['a']); // listener
1741			} elseif (isset($_POST['a'])) {
1742				@$listener = DevblocksPlatform::importGPC($_POST['a']); // listener
1743			}
1744			if(!empty($listener)) $parts[] = DevblocksPlatform::strAlphaNum($listener);
1745		}
1746		
1747		// Controller XSS security (alphanum only)
1748		if(isset($parts[0])) {
1749			$parts[0] = DevblocksPlatform::strAlphaNum($parts[0]);
1750		}
1751		
1752		// Resource / Proxy
1753	    /*
1754	     * [TODO] Run this code through another audit.  Is it worth a tiny hit per resource 
1755	     * to verify the plugin matches exactly in the DB?  If so, make sure we cache the 
1756	     * resulting file.
1757	     * 
1758	     * [TODO] Make this a controller
1759	     */
1760	    $path = $parts;
1761		switch(array_shift($path)) {
1762		    case "resource":
1763			    $plugin_id = array_shift($path);
1764			    if(null == ($plugin = DevblocksPlatform::getPlugin($plugin_id)))
1765			    	break;
1766			    
1767			    $file = implode(DIRECTORY_SEPARATOR, $path); // combine path
1768		        $dir = APP_PATH . '/' . $plugin->dir . '/' . 'resources';
1769		        if(!is_dir($dir)) die(""); // basedir Security
1770		        $resource = $dir . '/' . $file;
1771		        if(0 != strstr($dir,$resource)) die("");
1772		        $ext = @array_pop(explode('.', $resource));
1773		        if(!is_file($resource) || 'php' == $ext) die(""); // extension security
1774
1775                // Caching
1776                switch($ext) {
1777                	case 'css':
1778                	case 'gif':
1779                	case 'jpg':
1780                	case 'js':
1781                	case 'png':
1782		                header('Cache-control: max-age=604800', true); // 1 wk // , must-revalidate
1783		                header('Expires: ' . gmdate('D, d M Y H:i:s',time()+604800) . ' GMT'); // 1 wk
1784                		break;
1785                }
1786                
1787	            switch($ext) {
1788	            	case 'css':
1789	            		header('Content-type: text/css;');
1790	            		break;
1791	            	case 'gif':
1792	            		header('Content-type: image/gif;');
1793	            		break;
1794	            	case 'jpeg':
1795	            	case 'jpg':
1796	            		header('Content-type: image/jpeg;');
1797	            		break;
1798	            	case 'js':
1799	            		header('Content-type: text/javascript;');
1800	            		break;
1801	            	case 'png':
1802	            		header('Content-type: image/png;');
1803	            		break;
1804	            	case 'xml':
1805	            		header('Content-type: text/xml;');
1806	            		break;
1807	            }
1808	            
1809		        $out = file_get_contents($resource, false);
1810		        
1811                // Pass through
1812                if($out) {
1813                	header('Content-Length: '. strlen($out));
1814                	echo $out;
1815                }
1816		        
1817				exit;
1818    	        break;
1819		        
1820		    default:
1821		        break;
1822	

Large files files are truncated, but you can click here to view the full file