/** * The Symphony constructor initialises the class variables of Symphony. * It will set the DateTime settings, define new date constants and initialise * the correct Language for the currently logged in Author. If magic quotes * are enabled, Symphony will sanitize the `$_SERVER`, `$_COOKIE`, * `$_GET` and `$_POST` arrays. The constructor loads in * the initial Configuration values from the `CONFIG` file */ protected function __construct() { self::$Profiler = Profiler::instance(); if (get_magic_quotes_gpc()) { General::cleanArray($_SERVER); General::cleanArray($_COOKIE); General::cleanArray($_GET); General::cleanArray($_POST); } // Set date format throughout the system define_safe('__SYM_DATE_FORMAT__', self::Configuration()->get('date_format', 'region')); define_safe('__SYM_TIME_FORMAT__', self::Configuration()->get('time_format', 'region')); define_safe('__SYM_DATETIME_FORMAT__', __SYM_DATE_FORMAT__ . self::Configuration()->get('datetime_separator', 'region') . __SYM_TIME_FORMAT__); DateTimeObj::setSettings(self::Configuration()->get('region')); self::initialiseErrorHandler(); // Initialize language management Lang::initialize(); Lang::set(self::$Configuration->get('lang', 'symphony')); self::initialiseCookie(); // If the user is not a logged in Author, turn off the verbose error messages. if (!self::isLoggedIn() && is_null(self::$Author)) { GenericExceptionHandler::$enabled = false; } // Engine is ready. self::$Profiler->sample('Engine Initialisation'); }
public function processDependencies(array $params = array()) { $datasources = $this->getDependencies(); if (!is_array($datasources) || empty($datasources)) { return; } $datasources = array_map(create_function('$a', "return str_replace('\$ds-', '', \$a);"), $datasources); $datasources = array_map(create_function('$a', "return str_replace('-', '_', \$a);"), $datasources); $env = array('today' => DateTimeObj::get('Y-m-d'), 'current-time' => DateTimeObj::get('H:i'), 'this-year' => DateTimeObj::get('Y'), 'this-month' => DateTimeObj::get('m'), 'this-day' => DateTimeObj::get('d'), 'timezone' => DateTimeObj::get('P'), 'enm-newsletter-id' => $this->newsletter_id); $this->_env['param'] = $env; $this->_env['env']['pool'] = $params; $dependencies = array(); foreach ($datasources as $handle) { $profiler = Symphony::Profiler(); $profiler->seed(); $pool[$handle] =& DatasourceManager::create($handle, NULL, false); $dependencies[$handle] = $pool[$handle]->getDependencies(); } $dsOrder = $this->__findDatasourceOrder($dependencies); foreach ($dsOrder as $handle) { $ds = $pool[$handle]; $ds->processParameters($this->_env); $ds->grab($this->_env['env']['pool']); unset($ds); } $this->processParameters($this->_env); }
/** * The constructor for `JSONPage`. This sets the page status to `Page::HTTP_STATUS_OK`, * the default content type to `application/json` and initialises `$this->_Result` * with an `array`. The constructor also starts the Profiler for this * page template. * * @see toolkit.Profiler */ public function __construct() { $this->_Result = array(); $this->setHttpStatus(self::HTTP_STATUS_OK); $this->addHeaderToPage('Content-Type', 'application/json'); Symphony::Profiler()->sample('Page template created', PROFILE_LAP); }
/** * The constructor for `XMLPage`. This sets the page status to `Page::HTTP_STATUS_OK`, * the default content type to `text/xml` and initialises `$this->_Result` * with an `XMLElement`. The constructor also starts the Profiler for this * page template. * * @see toolkit.Profiler */ public function __construct() { $this->_Result = new XMLElement('result'); $this->_Result->setIncludeHeader(true); $this->setHttpStatus(self::HTTP_STATUS_OK); $this->addHeaderToPage('Content-Type', 'text/xml'); Symphony::Profiler()->sample('Page template created', PROFILE_LAP); }
public function __construct() { parent::__construct(); $this->addHeaderToPage('Content-Type', 'text/html; charset=UTF-8'); $this->Html->setElementStyle('html'); $this->Html->setDTD('<!DOCTYPE html>'); $this->Html->setAttribute('lang', Lang::get()); $this->addElementToHead(new XMLElement('meta', null, array('charset' => 'UTF-8')), 0); $this->addElementToHead(new XMLElement('meta', null, array('http-equiv' => 'X-UA-Compatible', 'content' => 'IE=edge,chrome=1')), 1); $this->addElementToHead(new XMLElement('meta', null, array('name' => 'viewport', 'content' => 'width=device-width, initial-scale=1')), 2); $this->addStylesheetToHead(APPLICATION_URL . '/assets/css/symphony.min.css', 'screen', null, false); $this->setTitle(__('%1$s – %2$s', array(__('Login'), Symphony::Configuration()->get('sitename', 'general')))); $this->Body->setAttribute('id', 'login'); Symphony::Profiler()->sample('Page template created', PROFILE_LAP); }
public function __construct() { parent::__construct(); $this->addHeaderToPage('Content-Type', 'text/html; charset=UTF-8'); $this->Html->setElementStyle('html'); $this->Html->setDTD('<!DOCTYPE html>'); //PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd" $this->Html->setAttribute('lang', Lang::get()); $this->addElementToHead(new XMLElement('meta', NULL, array('http-equiv' => 'Content-Type', 'content' => 'text/html; charset=UTF-8')), 0); $this->addStylesheetToHead(SYMPHONY_URL . '/assets/css/symphony.css', 'screen', 30); $this->addStylesheetToHead(SYMPHONY_URL . '/assets/css/symphony.forms.css', 'screen', 31); $this->addStylesheetToHead(SYMPHONY_URL . '/assets/css/symphony.frames.css', 'screen', 32); $this->setTitle(__('%1$s – %2$s', array(__('Login'), __('Symphony')))); $this->Body->setAttribute('id', 'login'); Symphony::Profiler()->sample('Page template created', PROFILE_LAP); }
/** * The Symphony constructor initialises the class variables of Symphony. At present * constructor has a couple of responsibilities: * - Start a profiler instance * - If magic quotes are enabled, clean `$_SERVER`, `$_COOKIE`, `$_GET` and `$_POST` arrays * - Initialise the correct Language for the currently logged in Author. * - Start the session and adjust the error handling if the user is logged in */ protected function __construct() { self::$Profiler = Profiler::instance(); if (get_magic_quotes_gpc()) { General::cleanArray($_SERVER); General::cleanArray($_COOKIE); General::cleanArray($_GET); General::cleanArray($_POST); } // Initialize language management Lang::initialize(); Lang::set(self::$Configuration->get('lang', 'symphony')); self::initialiseCookie(); // If the user is not a logged in Author, turn off the verbose error messages. if (!self::isLoggedIn() && is_null(self::$Author)) { GenericExceptionHandler::$enabled = false; } // Engine is ready. self::$Profiler->sample('Engine Initialisation'); }
/** * Called by index.php, this function is responsible for rendering the current * page on the Frontend. Two delegates are fired, AdminPagePreGenerate and * AdminPagePostGenerate. This function runs the Profiler for the page build * process. * * @uses AdminPagePreGenerate * @uses AdminPagePostGenerate * @see core.Symphony#__buildPage() * @see boot.getCurrentPage() * @param string $page * The result of getCurrentPage, which returns the $_GET['symphony-page'] * variable. * @throws Exception * @throws SymphonyErrorPage * @return string * The HTML of the page to return */ public function display($page) { Symphony::Profiler()->sample('Page build process started'); $this->__buildPage($page); // Add XSRF token to form's in the backend if (self::isXSRFEnabled() && isset($this->Page->Form)) { $this->Page->Form->prependChild(XSRF::formToken()); } /** * Immediately before generating the admin page. Provided with the page object * @delegate AdminPagePreGenerate * @param string $context * '/backend/' * @param HTMLPage $oPage * An instance of the current page to be rendered, this will usually be a class that * extends HTMLPage. The Symphony backend uses a convention of contentPageName * as the class that extends the HTMLPage */ Symphony::ExtensionManager()->notifyMembers('AdminPagePreGenerate', '/backend/', array('oPage' => &$this->Page)); $output = $this->Page->generate(); /** * Immediately after generating the admin page. Provided with string containing page source * @delegate AdminPagePostGenerate * @param string $context * '/backend/' * @param string $output * The resulting backend page HTML as a string, passed by reference */ Symphony::ExtensionManager()->notifyMembers('AdminPagePostGenerate', '/backend/', array('output' => &$output)); Symphony::Profiler()->sample('Page built'); return $output; }
/** * Given an array of all the Datasources for this page, sort them into the * correct execution order and append the Datasource results to the * page XML. If the Datasource provides any parameters, they will be * added to the `$env` pool for use by other Datasources and eventual * inclusion into the page parameters. * * @param string $datasources * A string of Datasource's attached to this page, comma separated. * @param XMLElement $wrapper * The XMLElement to append the Datasource results to. Datasource * results are contained in a root XMLElement that is the handlised * version of their name. * @param array $params * Any params to automatically add to the `$env` pool, by default this * is an empty array. It looks like Symphony does not utilise this parameter * at all */ public function processDatasources($datasources, XMLElement &$wrapper, array $params = array()) { if (trim($datasources) == '') { return; } $datasources = preg_split('/,\\s*/i', $datasources, -1, PREG_SPLIT_NO_EMPTY); $datasources = array_map('trim', $datasources); if (!is_array($datasources) || empty($datasources)) { return; } $this->_env['pool'] = $params; $pool = $params; $dependencies = array(); foreach ($datasources as $handle) { Symphony::Profiler()->seed(); $pool[$handle] =& DatasourceManager::create($handle, array(), false); $dependencies[$handle] = $pool[$handle]->getDependencies(); } $dsOrder = $this->__findDatasourceOrder($dependencies); foreach ($dsOrder as $handle) { Symphony::Profiler()->seed(); $queries = Symphony::Database()->queryCount(); $ds = $pool[$handle]; $ds->processParameters(array('env' => $this->_env, 'param' => $this->_param)); // default to no XML $xml = NULL; /** * Allows extensions to execute the data source themselves (e.g. for caching) * and providing their own output XML instead * * @since Symphony 2.3 * @delegate DataSourcePreExecute * @param string $context * '/frontend/' * @param boolean $datasource * The Datasource object * @param mixed $xml * The XML output of the data source. Can be an XMLElement or string. * @param mixed $paral_pool * The existing param pool including output parameters of any previous data sources */ Symphony::ExtensionManager()->notifyMembers('DataSourcePreExecute', '/frontend/', array('datasource' => &$ds, 'xml' => &$xml, 'param_pool' => &$this->_env['pool'])); // if the XML is still null, an extension has not run the data source, so run normally if (is_null($xml)) { $xml = $ds->grab($this->_env['pool']); } if ($xml) { if (is_object($xml)) { $wrapper->appendChild($xml); } else { $wrapper->setValue($wrapper->getValue() . PHP_EOL . ' ' . trim($xml)); } } $queries = Symphony::Database()->queryCount() - $queries; Symphony::Profiler()->sample($handle, PROFILE_LAP, 'Datasource', $queries); unset($ds); } }
/** * Given an array of all the Datasources for this page, sort them into the * correct execution order and append the Datasource results to the * page XML. If the Datasource provides any parameters, they will be * added to the `$env` pool for use by other Datasources and eventual * inclusion into the page parameters. * * @param string $datasources * A string of Datasource's attached to this page, comma separated. * @param XMLElement $wrapper * The XMLElement to append the Datasource results to. Datasource * results are contained in a root XMLElement that is the handlised * version of their name. * @param array $params * Any params to automatically add to the `$env` pool, by default this * is an empty array. It looks like Symphony does not utilise this parameter * at all */ public function processDatasources($datasources, XMLElement &$wrapper, array $params = array()) { if (trim($datasources) == '') { return; } $datasources = preg_split('/,\\s*/i', $datasources, -1, PREG_SPLIT_NO_EMPTY); $datasources = array_map('trim', $datasources); if (!is_array($datasources) || empty($datasources)) { return; } $this->_env['pool'] = $params; $pool = $params; $dependencies = array(); foreach ($datasources as $handle) { $pool[$handle] = DatasourceManager::create($handle, array(), false); $dependencies[$handle] = $pool[$handle]->getDependencies(); } $dsOrder = $this->__findDatasourceOrder($dependencies); foreach ($dsOrder as $handle) { Symphony::Profiler()->seed(); $queries = Symphony::Database()->queryCount(); // default to no XML $xml = null; $ds = $pool[$handle]; // Handle redirect on empty setting correctly RE: #1539 try { $ds->processParameters(array('env' => $this->_env, 'param' => $this->_param)); } catch (FrontendPageNotFoundException $e) { // Work around. This ensures the 404 page is displayed and // is not picked up by the default catch() statement below FrontendPageNotFoundExceptionHandler::render($e); } /** * Allows extensions to execute the data source themselves (e.g. for caching) * and providing their own output XML instead * * @since Symphony 2.3 * @delegate DataSourcePreExecute * @param string $context * '/frontend/' * @param DataSource $datasource * The Datasource object * @param mixed $xml * The XML output of the data source. Can be an `XMLElement` or string. * @param array $param_pool * The existing param pool including output parameters of any previous data sources */ Symphony::ExtensionManager()->notifyMembers('DataSourcePreExecute', '/frontend/', array('datasource' => &$ds, 'xml' => &$xml, 'param_pool' => &$this->_env['pool'])); // if the XML is still null, an extension has not run the data source, so run normally if (is_null($xml)) { $xml = $ds->grab($this->_env['pool']); } if ($xml) { /** * After the datasource has executed, either by itself or via the * `DataSourcePreExecute` delegate, and if the `$xml` variable is truthy, * this delegate allows extensions to modify the output XML and parameter pool * * @since Symphony 2.3 * @delegate DataSourcePostExecute * @param string $context * '/frontend/' * @param DataSource $datasource * The Datasource object * @param mixed $xml * The XML output of the data source. Can be an `XMLElement` or string. * @param array $param_pool * The existing param pool including output parameters of any previous data sources */ Symphony::ExtensionManager()->notifyMembers('DataSourcePostExecute', '/frontend/', array('datasource' => $ds, 'xml' => &$xml, 'param_pool' => &$this->_env['pool'])); if ($xml instanceof XMLElement) { $wrapper->appendChild($xml); } else { $wrapper->setValue($wrapper->getValue() . PHP_EOL . ' ' . trim($xml)); } } $queries = Symphony::Database()->queryCount() - $queries; Symphony::Profiler()->sample($handle, PROFILE_LAP, 'Datasource', $queries); unset($ds); } }
/** * This function will append the Navigation to the AdministrationPage. * It fires a delegate, NavigationPreRender, to allow extensions to manipulate * the navigation. Extensions should not use this to add their own navigation, * they should provide the navigation through their fetchNavigation function. * Note with the Section navigation groups, if there is only one section in a group * and that section is set to visible, the group will not appear in the navigation. * * @uses NavigationPreRender * @see getNavigationArray() * @see toolkit.Extension#fetchNavigation() */ public function appendNavigation() { $nav = $this->getNavigationArray(); /** * Immediately before displaying the admin navigation. Provided with the * navigation array. Manipulating it will alter the navigation for all pages. * * @delegate NavigationPreRender * @param string $context * '/backend/' * @param array $nav * An associative array of the current navigation, passed by reference */ Symphony::ExtensionManager()->notifyMembers('NavigationPreRender', '/backend/', array('navigation' => &$nav)); $navElement = new XMLElement('nav', NULL, array('id' => 'nav')); $contentNav = new XMLElement('ul', NULL, array('class' => 'content')); $structureNav = new XMLElement('ul', NULL, array('class' => 'structure')); foreach ($nav as $n) { if (isset($n['visible']) && $n['visible'] == 'no') { continue; } $can_access = false; if (!isset($n['limit']) || $n['limit'] == 'author') { $can_access = true; } elseif ($n['limit'] == 'developer' && Administration::instance()->Author->isDeveloper()) { $can_access = true; } elseif ($n['limit'] == 'primary' && Administration::instance()->Author->isPrimaryAccount()) { $can_access = true; } if ($can_access) { $xGroup = new XMLElement('li', $n['name']); if (isset($n['class']) && trim($n['name']) != '') { $xGroup->setAttribute('class', $n['class']); } $hasChildren = false; $xChildren = new XMLElement('ul'); if (is_array($n['children']) && !empty($n['children'])) { foreach ($n['children'] as $c) { if ($c['visible'] == 'no') { continue; } $can_access_child = false; if (!isset($c['limit']) || $c['limit'] == 'author') { $can_access_child = true; } elseif ($c['limit'] == 'developer' && Administration::instance()->Author->isDeveloper()) { $can_access_child = true; } elseif ($c['limit'] == 'primary' && Administration::instance()->Author->isPrimaryAccount()) { $can_access_child = true; } if ($can_access_child) { $xChild = new XMLElement('li'); $xChild->appendChild(Widget::Anchor($c['name'], SYMPHONY_URL . $c['link'])); $xChildren->appendChild($xChild); $hasChildren = true; } } if ($hasChildren) { $xGroup->appendChild($xChildren); if ($n['type'] === 'content') { $contentNav->appendChild($xGroup); } else { if ($n['type'] === 'structure') { $structureNav->prependChild($xGroup); } } } } } } $navElement->appendChild($contentNav); $navElement->appendChild($structureNav); $this->Header->appendChild($navElement); Symphony::Profiler()->sample('Navigation Built', PROFILE_LAP); }
/** * Given a delegate name, notify all extensions that have registered to that * delegate to executing their callbacks with a `$context` array parameter * that contains information about the current Symphony state. * * @param string $delegate * The delegate name * @param string $page * The current page namespace that this delegate operates in * @param array $context * The `$context` param is an associative array that at minimum will contain * the current Administration class, the current page object and the delegate * name. Other context information may be passed to this function when it is * called. eg. * * array( * 'parent' =>& $this->Parent, * 'page' => $page, * 'delegate' => $delegate * ); * @throws Exception * @throws SymphonyErrorPage * @return null|void */ public static function notifyMembers($delegate, $page, array $context = array()) { // Make sure $page is an array if (!is_array($page)) { $page = array($page); } // Support for global delegate subscription if (!in_array('*', $page)) { $page[] = '*'; } $services = array(); if (isset(self::$_subscriptions[$delegate])) { foreach (self::$_subscriptions[$delegate] as $subscription) { if (!in_array($subscription['page'], $page)) { continue; } $services[] = $subscription; } } if (empty($services)) { return null; } $context += array('page' => $page, 'delegate' => $delegate); $profiling = Symphony::Profiler() instanceof Profiler; foreach ($services as $s) { if ($profiling) { // Initial seeding and query count Symphony::Profiler()->seed(); $queries = Symphony::Database()->queryCount(); } // Get instance of extension and execute the callback passing // the `$context` along $obj = self::getInstance($s['name']); if (is_object($obj) && method_exists($obj, $s['callback'])) { $obj->{$s['callback']}($context); } // Complete the Profiling sample if ($profiling) { $queries = Symphony::Database()->queryCount() - $queries; Symphony::Profiler()->sample($delegate . '|' . $s['name'], PROFILE_LAP, 'Delegate', $queries); } } }
/** * This function will append the Navigation to the AdministrationPage. * It fires a delegate, NavigationPreRender, to allow extensions to manipulate * the navigation. Extensions should not use this to add their own navigation, * they should provide the navigation through their fetchNavigation function. * Note with the Section navigation groups, if there is only one section in a group * and that section is set to visible, the group will not appear in the navigation. * * @uses NavigationPreRender * @see getNavigationArray() * @see toolkit.Extension#fetchNavigation() */ public function appendNavigation() { $nav = $this->getNavigationArray(); /** * Immediately before displaying the admin navigation. Provided with the * navigation array. Manipulating it will alter the navigation for all pages. * * @delegate NavigationPreRender * @param string $context * '/backend/' * @param array $nav * An associative array of the current navigation, passed by reference */ Symphony::ExtensionManager()->notifyMembers('NavigationPreRender', '/backend/', array('navigation' => &$nav)); $navElement = new XMLElement('nav', null, array('id' => 'nav', 'role' => 'navigation')); $contentNav = new XMLElement('ul', null, array('class' => 'content', 'role' => 'menubar')); $structureNav = new XMLElement('ul', null, array('class' => 'structure', 'role' => 'menubar')); foreach ($nav as $n) { if (isset($n['visible']) && $n['visible'] === 'no') { continue; } if ($this->doesAuthorHaveAccess($n['limit'])) { $xGroup = new XMLElement('li', $n['name'], array('role' => 'presentation')); if (isset($n['class']) && trim($n['name']) !== '') { $xGroup->setAttribute('class', $n['class']); } $hasChildren = false; $xChildren = new XMLElement('ul', null, array('role' => 'menu')); if (is_array($n['children']) && !empty($n['children'])) { foreach ($n['children'] as $c) { // adapt for Yes and yes if (strtolower($c['visible']) !== 'yes') { continue; } if ($this->doesAuthorHaveAccess($c['limit'])) { $xChild = new XMLElement('li'); $xChild->setAttribute('role', 'menuitem'); $linkChild = Widget::Anchor($c['name'], SYMPHONY_URL . $c['link']); if (isset($c['target'])) { $linkChild->setAttribute('target', $c['target']); } $xChild->appendChild($linkChild); $xChildren->appendChild($xChild); $hasChildren = true; } } if ($hasChildren) { $xGroup->setAttribute('aria-haspopup', 'true'); $xGroup->appendChild($xChildren); if ($n['type'] === 'content') { $contentNav->appendChild($xGroup); } elseif ($n['type'] === 'structure') { $structureNav->prependChild($xGroup); } } } } } $navElement->appendChild($contentNav); $navElement->appendChild($structureNav); $this->Header->appendChild($navElement); Symphony::Profiler()->sample('Navigation Built', PROFILE_LAP); }
public function buildContent(XMLElement $wrapper) { $this->addStylesheetToHead(URL . '/extensions/profiledevkit/assets/devkit.css', 'screen', 9126343); $table = new XMLElement('table'); $table->setAttribute('id', $this->_view); if ($this->_view == 'render-statistics') { $xml_generation = Symphony::Profiler()->retrieveByMessage('XML Generation'); $xsl_transformation = Symphony::Profiler()->retrieveByMessage('XSLT Transformation'); $event_total = 0; foreach ($this->_records['events'] as $data) { $event_total += $data[1]; } $ds_total = 0; foreach ($this->_records['data-sources'] as $data) { $ds_total += $data[1]; } $this->_records = array(array(__('Total Database Queries'), $this->_dbstats['queries'], NULL, NULL, false), array(__('Slow Queries (> 0.09s)'), (string) count($this->_dbstats['slow-queries']), NULL, NULL, false), array(__('Total Time Spent on Queries'), $this->_dbstats['total-query-time']), array(__('Time Triggering All Events'), $event_total), array(__('Time Running All Data Sources'), $ds_total), array(__('XML Generation'), $xml_generation[1]), array(__('XSLT Transformation'), $xsl_transformation[1]), array(__('Output Creation Time'), Symphony::Profiler()->retrieveTotalRunningTime()), array(__('Total Memory Usage'), General::formatFilesize(Symphony::Profiler()->retrieveTotalMemoryUsage()), NULL, NULL, false)); foreach ($this->_records as $data) { if (!isset($data[4]) || $data[4] !== false) { $data[1] = number_format($data[1], 4) . ' s'; } $row = new XMLElement('tr'); $row->appendChild(new XMLElement('th', $data[0])); $row->appendChild(new XMLElement('td', $data[1])); $table->appendChild($row); } } else { if ($this->_view == 'memory-usage') { $items = Symphony::Profiler()->retrieve(); $base = $items[0][5]; $total = 0; $last = 0; foreach ($items as $index => $item) { // Build row display name if (in_array($item[3], array('Datasource', 'Event'))) { $display_value = $item[3] . ': ' . $item[0]; } else { if ($item[3] == 'Delegate') { $display_value = str_replace('|', ': ', $item[0]); } else { $display_value = $item[0]; } } $row = new XMLElement('tr'); $row->appendChild(new XMLElement('th', $display_value)); $row->appendChild(new XMLElement('td', General::formatFilesize(max(0, $item[5] - $base - $last)))); $table->appendChild($row); $last = $item[5] - $base; } } else { if ($this->_view == 'database-queries') { $debug = Symphony::Database()->debug(); if (count($debug) > 0) { $i = 1; foreach ($debug as $query) { $row = new XMLElement('tr'); $row->appendChild(new XMLElement('th', $i)); $row->appendChild(new XMLElement('td', number_format($query['execution_time'], 4))); $row->appendChild(new XMLElement('td', $query['query'])); $table->appendChild($row); $i++; } } } else { if ($this->_view == 'delegates') { $delegates = array(); $debug = Symphony::Database()->debug(); // Build an array of delegate => extensions foreach ($this->_records['delegates'] as $data) { $parts = explode('|', $data[0]); $data[0] = $parts[1]; $delegates[$parts[0]][] = $data; } foreach ($delegates as $delegate => $extensions) { $tt = $tq = 0; $te = array(); $row = new XMLElement('tr'); $row->appendChild(new XMLElement('th', $delegate)); $table->appendChild($row); foreach ($extensions as $extension) { $execution_time = number_format($extension[1], 4); $extension_row = new XMLElement('tr'); // Poor man's grouping. $extension_row->appendChild(new XMLElement('td', ' ')); $extension_row->appendChild(new XMLElement('th', $extension[0])); $extension_row->appendChild(new XMLElement('td', $execution_time . ' s from ' . $extension[4] . ' ' . ($extension[4] == 1 ? 'query' : 'queries'))); $table->appendChild($extension_row); $tt += $execution_time; $tq += is_array($extension[4]) ? count($extension[4]) : $extension[4]; if (!in_array($extension[0], $te)) { $te[] = $extension[0]; } } $row->appendChild(new XMLElement('td', number_format($tt, 4) . ' s from ' . count($te) . ' extensions and ' . $tq . ' ' . ($tq == 1 ? 'query' : 'queries'))); } } else { if ($this->_records = $this->_records[$this->_view]) { $ds_total = 0; foreach ($this->_records as $data) { $row = new XMLElement('tr'); $row->appendChild(new XMLElement('th', $data[0])); if ($this->_view == 'general') { $row->appendChild(new XMLElement('td', number_format($data[1], 4) . ' s')); } elseif ($this->_view == 'slow-queries') { if (!isset($data[4]) || $data[4] !== false) { $data[1] = number_format($data[1], 4) . ' s'; } $row->appendChild(new XMLElement('td', $data[1])); } else { $row->appendChild(new XMLElement('td', number_format($data[1], 4) . ' s from ' . $data[4] . ' ' . ($data[4] == 1 ? 'query' : 'queries'))); } $ds_total += $data[1]; $table->appendChild($row); } } } } } } $wrapper->appendChild($table); }