diff --git a/web/includes/utilities.inc.php b/web/includes/utilities.inc.php index 32868a1d63..64f07f14e7 100644 --- a/web/includes/utilities.inc.php +++ b/web/includes/utilities.inc.php @@ -139,3 +139,73 @@ function safe_url($raw_url) // parse_url failed, or the scheme was not hypertext-based. return false; } + +/** + * Given an associative array generate a textual representation + * using JSON markup. + * + * @note json_encode is practically useless for real world data + * in PHP <= 5.3 hence this hand-rolled imitation. + * + * @param array (Array) Array to be interpreted. + * @param flags (Integer) Reserved for future use. + * @param indent_level (Integer) Level of indent to add to the output. + * @return (Mixed) Textual representation in JSON format, else Boolean @c FALSE + */ +function json_encode_clean(&$array, $flags=0, $indent_level=0) +{ + if(!is_array($array)) return FALSE; + + $staged = NULL; + foreach($array as $key => $value) + { + $key = '"'. addslashes($key) .'"'; + + // Format the value: + if(is_array($value)) + { + // Descend to the subobject. + $value = json_encode_clean($value, $flags, $indent_level+1); + } + else if(is_bool($value)) + { + $value = ((boolean)$value)? 'true' : 'false'; + } + else if(!is_numeric($value) || is_string($value)) + { + $value = '"'. addslashes($value) .'"'; + } + + // Time to construct the staging array? + if(is_null($staged)) + { + $staged = array(); + } + + $staged[] = "$key: $value"; + } + + if(is_null($staged)) return ''; + + // Collapse into JSON comma-delimited form. + $indent = ' '; + $glue = ", \n"; + $tmp = ''; + foreach($staged as $item) + { + $tmp .= $indent . $item . $glue; + } + $result = "{\n" . substr($tmp, 0, -strlen($glue)) . "\n}"; + + // Determine scope-level indent depth. + $indent_level = (integer)$indent_level; + if($indent_level < 0) $indent_level = 0; + + // Apply a scope-level indent? + if($indent_level != 0) + { + $result = str_repeat($indent, $indent_level) . $result; + } + + return $result; +} diff --git a/web/plugins/buildrepository/builderproduct.interface.php b/web/plugins/buildrepository/builderproduct.interface.php index 8f908d9f27..f04e130e02 100644 --- a/web/plugins/buildrepository/builderproduct.interface.php +++ b/web/plugins/buildrepository/builderproduct.interface.php @@ -28,6 +28,7 @@ interface iBuilderProduct { public function setBuildUniqueId($id); + public function buildUniqueId(); public function &compileLogUri(); public function compileWarnCount(); public function compileErrorCount(); diff --git a/web/plugins/buildrepository/buildrepository.php b/web/plugins/buildrepository/buildrepository.php index b3d8f2de6f..6d4847dbed 100644 --- a/web/plugins/buildrepository/buildrepository.php +++ b/web/plugins/buildrepository/buildrepository.php @@ -730,7 +730,7 @@ public function InterpretRequest($request) { $this->initPackages(); - // Are we redirecting to the download URI for a specific package? + // Are we redirecting to a specific package? $getPackage = FALSE; if(isset($uriArgs['platform'])) { @@ -738,7 +738,7 @@ public function InterpretRequest($request) $platformId = $this->parsePlatformId($uriArgs['platform']); $unstable = isset($uriArgs['unstable']); - // Default to downloading Doomsday if a pack is not specified. + // Default to Doomsday if a pack is not specified. $packTitle = "Doomsday"; if(isset($uriArgs['pack'])) $packTitle = trim($uriArgs['pack']); @@ -747,7 +747,21 @@ public function InterpretRequest($request) $pack = &$this->choosePackage($platformId, $packTitle, $unstable); if(!($pack instanceof NullPackage)) { - $FrontController->enqueueAction($this, array('getpackage' => $pack)); + $args = array(); + + // Are we retrieving the object graph? + if(isset($uriArgs['graph'])) + { + // Return the object graph. + $args['getgraph'] = $pack; + } + else + { + // Redirect to the package download. + $args['getpackage'] = $pack; + } + + $FrontController->enqueueAction($this, $args); return true; // Eat the request. } } @@ -996,6 +1010,23 @@ private function outputPackageRedirect(&$pack) $FrontController->endPage(); } + private function outputPackageGraph(&$pack) + { + global $FrontController; + + if(!($pack instanceof AbstractPackage)) + throw new Exception('Received invalid Package.'); + + // Generate a graph template for this package. + /// @todo cache the encoded graph! + $template = array(); + $pack->populateGraphTemplate($template); + $json = json_encode_clean($template); + + // Print to the output stream. + print($json); + } + private function countInstallablePackages(&$build) { $count = 0; @@ -1042,6 +1073,11 @@ public function execute($args=NULL) $this->outputPackageRedirect($args['getpackage']); return; } + else if(isset($args['getgraph'])) + { + $this->outputPackageGraph($args['getgraph']); + return; + } // Determine whether we are detailing a single build event or listing all events. $uniqueId = $args['build']; diff --git a/web/plugins/buildrepository/packages/abstractpackage.class.php b/web/plugins/buildrepository/packages/abstractpackage.class.php index 011bf22cc9..b7170c2270 100644 --- a/web/plugins/buildrepository/packages/abstractpackage.class.php +++ b/web/plugins/buildrepository/packages/abstractpackage.class.php @@ -42,6 +42,16 @@ public function __construct($platformId=PID_ANY, $title=NULL, $version=NULL, $do $this->downloadUri = "$downloadUri"; } + // Extends implementation in AbstractPackage. + public function populateGraphTemplate(&$tpl) + { + if(!is_array($tpl)) + throw new Exception('Invalid template argument, array expected'); + + parent::populateGraphTemplate($tpl); + $tpl['download_uri'] = $this->downloadUri(); + } + // Implements iDownloadable public function &downloadUri() { @@ -52,6 +62,7 @@ public function &downloadUri() return $this->downloadUri; } + // Implements iDownloadable public function hasDownloadUri() { return !is_null($this->downloadUri); diff --git a/web/plugins/buildrepository/packages/abstractunstablepackage.class.php b/web/plugins/buildrepository/packages/abstractunstablepackage.class.php index 1edd19f419..240f4a804a 100644 --- a/web/plugins/buildrepository/packages/abstractunstablepackage.class.php +++ b/web/plugins/buildrepository/packages/abstractunstablepackage.class.php @@ -67,12 +67,32 @@ public function composeFullTitle($includeVersion=true, $includeBuildId=true) return $title; } + // Extends implementation in AbstractPackage. + public function populateGraphTemplate(&$tpl) + { + if(!is_array($tpl)) + throw new Exception('Invalid template argument, array expected'); + + parent::populateGraphTemplate($tpl); + $tpl['is_unstable'] = true; + $tpl['build_uniqueid'] = $this->buildUniqueId(); + $tpl['compile_loguri'] = $this->compileLogUri(); + $tpl['compile_errorcount'] = $this->compileErrorCount(); + $tpl['compile_warncount'] = $this->compileWarnCount(); + } + // Implements iBuilderProduct. public function setBuildUniqueId($id) { $this->buildId = intval($id); } + // Implements iBuilderProduct. + public function buildUniqueId() + { + return $this->buildId; + } + // Implements iBuilderProduct. public function &compileLogUri() { diff --git a/web/plugins/buildrepository/packages/basepackage.class.php b/web/plugins/buildrepository/packages/basepackage.class.php index d78d3c1b73..f9c0b240f1 100644 --- a/web/plugins/buildrepository/packages/basepackage.class.php +++ b/web/plugins/buildrepository/packages/basepackage.class.php @@ -75,4 +75,23 @@ public function __toString() $fullTitle = $this->composeFullTitle(); return '('.get_class($this).":$title)"; } + + /** + * Add the object graph properties for this to the specified template. + * + * @param tpl (Array) Array to be filled with graph properties. + */ + public function populateGraphTemplate(&$tpl) + { + if(!is_array($tpl)) + throw new Exception('Invalid template argument, array expected'); + + $plat = &BuildRepositoryPlugin::platform($this->platformId()); + + $tpl['platform_id'] = $this->platformId(); + $tpl['platform_name'] = $plat['nicename']; + $tpl['version'] = $this->version(); + $tpl['title'] = $this->title(); + $tpl['fulltitle'] = $this->composeFullTitle(); + } }