Browse Source

NEW: Add module debugbar

Laurent Destailleur 6 years ago
parent
commit
c2badb57d7
95 changed files with 11095 additions and 4 deletions
  1. 1 1
      htdocs/core/modules/modDav.class.php
  2. 133 0
      htdocs/core/modules/modDebugBar.class.php
  3. 81 0
      htdocs/debugbar/class/DataCollector/DolConfigCollector.php
  4. 34 0
      htdocs/debugbar/class/DataCollector/DolExceptionsCollector.php
  5. 182 0
      htdocs/debugbar/class/DataCollector/DolLogsCollector.php
  6. 28 0
      htdocs/debugbar/class/DataCollector/DolMemoryCollector.php
  7. 35 0
      htdocs/debugbar/class/DataCollector/DolMessagesCollector.php
  8. 117 0
      htdocs/debugbar/class/DataCollector/DolQueryCollector.php
  9. 28 0
      htdocs/debugbar/class/DataCollector/DolRequestDataCollector.php
  10. 34 0
      htdocs/debugbar/class/DataCollector/DolTimeDataCollector.php
  11. 140 0
      htdocs/debugbar/class/DataCollector/DolibarrCollector.php
  12. 55 0
      htdocs/debugbar/class/DebugBar.php
  13. 679 0
      htdocs/debugbar/class/TraceableDB.php
  14. 112 0
      htdocs/debugbar/class/actions_debugbar.class.php
  15. 19 0
      htdocs/debugbar/class/autoloader.php
  16. 75 0
      htdocs/debugbar/js/widgets.js
  17. 62 0
      htdocs/includes/DebugBar/Bridge/CacheCacheCollector.php
  18. 98 0
      htdocs/includes/DebugBar/Bridge/DoctrineCollector.php
  19. 103 0
      htdocs/includes/DebugBar/Bridge/MonologCollector.php
  20. 253 0
      htdocs/includes/DebugBar/Bridge/PropelCollector.php
  21. 66 0
      htdocs/includes/DebugBar/Bridge/SlimCollector.php
  22. 44 0
      htdocs/includes/DebugBar/Bridge/SwiftMailer/SwiftLogCollector.php
  23. 92 0
      htdocs/includes/DebugBar/Bridge/SwiftMailer/SwiftMailCollector.php
  24. 417 0
      htdocs/includes/DebugBar/Bridge/Twig/TraceableTwigEnvironment.php
  25. 131 0
      htdocs/includes/DebugBar/Bridge/Twig/TraceableTwigTemplate.php
  26. 87 0
      htdocs/includes/DebugBar/Bridge/Twig/TwigCollector.php
  27. 166 0
      htdocs/includes/DebugBar/DataCollector/AggregatedCollector.php
  28. 28 0
      htdocs/includes/DebugBar/DataCollector/AssetProvider.php
  29. 71 0
      htdocs/includes/DebugBar/DataCollector/ConfigCollector.php
  30. 90 0
      htdocs/includes/DebugBar/DataCollector/DataCollector.php
  31. 31 0
      htdocs/includes/DebugBar/DataCollector/DataCollectorInterface.php
  32. 111 0
      htdocs/includes/DebugBar/DataCollector/ExceptionsCollector.php
  33. 64 0
      htdocs/includes/DebugBar/DataCollector/LocalizationCollector.php
  34. 63 0
      htdocs/includes/DebugBar/DataCollector/MemoryCollector.php
  35. 21 0
      htdocs/includes/DebugBar/DataCollector/MessagesAggregateInterface.php
  36. 152 0
      htdocs/includes/DebugBar/DataCollector/MessagesCollector.php
  37. 182 0
      htdocs/includes/DebugBar/DataCollector/PDO/PDOCollector.php
  38. 196 0
      htdocs/includes/DebugBar/DataCollector/PDO/TraceablePDO.php
  39. 74 0
      htdocs/includes/DebugBar/DataCollector/PDO/TraceablePDOStatement.php
  40. 240 0
      htdocs/includes/DebugBar/DataCollector/PDO/TracedStatement.php
  41. 51 0
      htdocs/includes/DebugBar/DataCollector/PhpInfoCollector.php
  42. 25 0
      htdocs/includes/DebugBar/DataCollector/Renderable.php
  43. 48 0
      htdocs/includes/DebugBar/DataCollector/RequestDataCollector.php
  44. 228 0
      htdocs/includes/DebugBar/DataCollector/TimeDataCollector.php
  45. 169 0
      htdocs/includes/DebugBar/DataFormatter/DataFormatter.php
  46. 42 0
      htdocs/includes/DebugBar/DataFormatter/DataFormatterInterface.php
  47. 468 0
      htdocs/includes/DebugBar/DebugBar.php
  48. 16 0
      htdocs/includes/DebugBar/DebugBarException.php
  49. 63 0
      htdocs/includes/DebugBar/HttpDriverInterface.php
  50. 960 0
      htdocs/includes/DebugBar/JavascriptRenderer.php
  51. 107 0
      htdocs/includes/DebugBar/OpenHandler.php
  52. 49 0
      htdocs/includes/DebugBar/PhpHttpDriver.php
  53. 22 0
      htdocs/includes/DebugBar/RequestIdGenerator.php
  54. 21 0
      htdocs/includes/DebugBar/RequestIdGeneratorInterface.php
  55. 289 0
      htdocs/includes/DebugBar/Resources/debugbar.css
  56. 1161 0
      htdocs/includes/DebugBar/Resources/debugbar.js
  57. 32 0
      htdocs/includes/DebugBar/Resources/openhandler.css
  58. 202 0
      htdocs/includes/DebugBar/Resources/openhandler.js
  59. 3 0
      htdocs/includes/DebugBar/Resources/vendor/font-awesome/css/font-awesome.min.css
  60. BIN
      htdocs/includes/DebugBar/Resources/vendor/font-awesome/fonts/FontAwesome.otf
  61. BIN
      htdocs/includes/DebugBar/Resources/vendor/font-awesome/fonts/fontawesome-webfont.eot
  62. 196 0
      htdocs/includes/DebugBar/Resources/vendor/font-awesome/fonts/fontawesome-webfont.svg
  63. BIN
      htdocs/includes/DebugBar/Resources/vendor/font-awesome/fonts/fontawesome-webfont.ttf
  64. BIN
      htdocs/includes/DebugBar/Resources/vendor/font-awesome/fonts/fontawesome-webfont.woff
  65. 0 0
      htdocs/includes/DebugBar/Resources/vendor/highlightjs/highlight.pack.js
  66. 125 0
      htdocs/includes/DebugBar/Resources/vendor/highlightjs/styles/github.css
  67. 1 0
      htdocs/includes/DebugBar/Resources/vendor/jquery/dist/jquery.min.js
  68. 243 0
      htdocs/includes/DebugBar/Resources/widgets.css
  69. 499 0
      htdocs/includes/DebugBar/Resources/widgets.js
  70. 12 0
      htdocs/includes/DebugBar/Resources/widgets/mails/widget.css
  71. 40 0
      htdocs/includes/DebugBar/Resources/widgets/mails/widget.js
  72. 82 0
      htdocs/includes/DebugBar/Resources/widgets/sqlqueries/widget.css
  73. 96 0
      htdocs/includes/DebugBar/Resources/widgets/sqlqueries/widget.js
  74. 60 0
      htdocs/includes/DebugBar/Resources/widgets/templates/widget.css
  75. 69 0
      htdocs/includes/DebugBar/Resources/widgets/templates/widget.js
  76. 34 0
      htdocs/includes/DebugBar/StandardDebugBar.php
  77. 108 0
      htdocs/includes/DebugBar/Storage/FileStorage.php
  78. 91 0
      htdocs/includes/DebugBar/Storage/MemcachedStorage.php
  79. 117 0
      htdocs/includes/DebugBar/Storage/PdoStorage.php
  80. 74 0
      htdocs/includes/DebugBar/Storage/RedisStorage.php
  81. 45 0
      htdocs/includes/DebugBar/Storage/StorageInterface.php
  82. 16 0
      htdocs/includes/DebugBar/Storage/pdo_storage_schema.sql
  83. 128 0
      htdocs/includes/Psr/Log/AbstractLogger.php
  84. 7 0
      htdocs/includes/Psr/Log/InvalidArgumentException.php
  85. 18 0
      htdocs/includes/Psr/Log/LogLevel.php
  86. 18 0
      htdocs/includes/Psr/Log/LoggerAwareInterface.php
  87. 26 0
      htdocs/includes/Psr/Log/LoggerAwareTrait.php
  88. 123 0
      htdocs/includes/Psr/Log/LoggerInterface.php
  89. 140 0
      htdocs/includes/Psr/Log/LoggerTrait.php
  90. 28 0
      htdocs/includes/Psr/Log/NullLogger.php
  91. 140 0
      htdocs/includes/Psr/Log/Test/LoggerInterfaceTest.php
  92. 6 1
      htdocs/langs/en_US/admin.lang
  93. 2 2
      htdocs/modulebuilder/template/core/modules/modMyModule.class.php
  94. BIN
      htdocs/theme/eldy/img/object_debugbar.png
  95. BIN
      htdocs/theme/md/img/object_debugbar.png

+ 1 - 1
htdocs/core/modules/modDav.class.php

@@ -19,7 +19,7 @@
  * 	\defgroup   dav     Module dav
  *  \brief      dav module descriptor.
  *
- *  \file       htdocs/dav/core/modules/modDav.class.php
+ *  \file       htdocs/core/modules/modDav.class.php
  *  \ingroup    dav
  *  \brief      Description and activation file for module dav
  */

+ 133 - 0
htdocs/core/modules/modDebugBar.class.php

@@ -0,0 +1,133 @@
+<?php
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * 	\defgroup   debugbar     Debug bar
+ *  \brief      debugbar module descriptor.
+ *
+ *  \file       htdocs/core/modules/modDebugBar.class.php
+ *  \ingroup    debugbar
+ *  \brief      Description and activation file for module debugbar
+ */
+include_once DOL_DOCUMENT_ROOT .'/core/modules/DolibarrModules.class.php';
+
+
+/**
+ *	Class to describe and enable module
+ */
+class modDebugBar extends DolibarrModules
+{
+
+    /**
+     *   Constructor. Define names, constants, directories, boxes, permissions
+     *
+     *   @param      DoliDB		$db      Database handler
+     */
+    public function __construct($db)
+    {
+        $this->db = $db;
+
+        $this->numero = 43;
+
+        $this->rights_class = 'debugbar';
+
+        $this->family = "base";
+        $this->module_position = '75';
+
+        // Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module)
+        $this->name = preg_replace('/^mod/i', '', get_class($this));
+        $this->description = "Debug bar";
+        // Possible values for version are: 'development', 'experimental', 'dolibarr' or version
+        $this->version = 'dolibarr';
+        $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
+        $this->picto='technic';
+
+        $this->module_parts = array(
+            // Set here all hooks context managed by module. To find available hook context, make a "grep -r '>initHooks(' *" on source code. You can also set hook context 'all'
+            'hooks' => array(
+                'data' => array(
+                    'main',
+                    'login',
+                ),
+                'entity' => '0',
+            ),
+            // Set this to 1 if feature of module are opened to external users
+            'moduleforexternal' => 0,
+        );
+
+        // Data directories to create when module is enabled
+        $this->dirs = array();
+
+        // Dependencies
+        $this->depends = array();        // May be used for product or service or third party module
+        $this->requiredby = array();
+
+        // Config pages
+        $this->config_page_url = array();
+
+        // Constants
+        // Example: $this->const=array(0=>array('MYMODULE_MYNEWCONST1','chaine','myvalue','This is a constant to add',0),
+        //							  1=>array('MYMODULE_MYNEWCONST2','chaine','myvalue','This is another constant to add',0) );
+        $this->const = array(
+            0 => array('DEBUGBAR_LOGS_LINES_NUMBER', 'chaine', '100', 'Number of log lines to show in debug bar', 1)
+        );
+
+        // Boxes
+        $this->boxes = array();
+
+        // Permissions
+        $this->rights = array();
+
+        $this->rights[1][0] = 430; // id de la permission
+        $this->rights[1][1] = 'Use Debug Bar'; // libelle de la permission
+        $this->rights[1][2] = 'u'; // type de la permission (deprecie a ce jour)
+        $this->rights[1][3] = 1; // La permission est-elle une permission par defaut
+        $this->rights[1][4] = 'read';
+    }
+
+
+    /**
+     *      Function called when module is enabled.
+     *      The init function add constants, boxes, permissions and menus (defined in constructor) into Dolibarr database.
+     *      It also creates data directories.
+     *
+     *      @param      string	$options    Options when enabling module ('', 'noboxes')
+     *      @return     int             	1 if OK, 0 if KO
+     */
+    public function init($options = '')
+    {
+        // Permissions
+        $this->remove($options);
+
+        $sql = array(
+        );
+
+        return $this->_init($sql, $options);
+    }
+
+	/**
+	 * Function called after module configuration.
+	 *
+	 */
+	public function loadSettings()
+	{
+		$this->addPermission("use", "UseDebugBar", "u");
+
+		$this->enableHooks(array(
+			'main',
+			'login'
+		));
+	}
+}

+ 81 - 0
htdocs/debugbar/class/DataCollector/DolConfigCollector.php

@@ -0,0 +1,81 @@
+<?php
+
+use \DebugBar\DataCollector\ConfigCollector;
+
+/**
+ * DolConfigCollector class
+ */
+
+class DolConfigCollector extends ConfigCollector
+{
+	/**
+	 *	Return widget settings
+	 *
+	 */
+	public function getWidgets()
+	{
+		global $langs;
+
+		return array(
+			$langs->transnoentities('Config') => array(
+				"icon" => "gear",
+				"widget" => "PhpDebugBar.Widgets.VariableListWidget",
+				"map" => $this->getName(),
+				"default" => "{}"
+			)
+		);
+	}
+
+	/**
+	 *	Return collected data
+	 *
+	 */
+	public function collect()
+	{
+		$this->data = $this->getConfig();
+
+		return parent::collect();
+	}
+
+	/**
+	 * Returns an array with config data
+	 *
+	 */
+	protected function getConfig()
+	{
+		global $conf, $user;
+
+		// Get constants
+		$const = get_defined_constants(true);
+
+		$config = array(
+			'Dolibarr' => array(
+				'const' => $const['user'],
+				'$conf' => $this->object_to_array($conf),
+				'$user' => $this->object_to_array($user)
+			),
+			'PHP' => array(
+				'version'   => PHP_VERSION,
+				'interface' => PHP_SAPI,
+				'os'        => PHP_OS
+			)
+		);
+
+		return $config;
+	}
+
+	/**
+	 * Convert an object to array
+	 *
+	 */
+	protected function object_to_array($obj)
+	{
+		$_arr = is_object($obj) ? get_object_vars($obj) : $obj;
+		foreach ($_arr as $key => $val) {
+			$val = (is_array($val) || is_object($val)) ? $this->object_to_array($val) : $val;
+			$arr[$key] = $val;
+		}
+
+		return $arr;
+	}
+}

+ 34 - 0
htdocs/debugbar/class/DataCollector/DolExceptionsCollector.php

@@ -0,0 +1,34 @@
+<?php
+
+use \DebugBar\DataCollector\ExceptionsCollector;
+
+/**
+ * DolExceptionsCollector class
+ */
+
+class DolExceptionsCollector extends ExceptionsCollector
+{
+	/**
+	 *	Return widget settings
+	 *
+	 */
+	public function getWidgets()
+	{
+		global $langs;
+
+		$title = $langs->transnoentities('Exceptions');
+
+		return array(
+			"$title" => array(
+				'icon' => 'bug',
+				'widget' => 'PhpDebugBar.Widgets.ExceptionsWidget',
+				'map' => 'exceptions.exceptions',
+				'default' => '[]'
+			),
+			"$title:badge" => array(
+				'map' => 'exceptions.count',
+				'default' => 'null'
+			)
+		);
+	}
+}

+ 182 - 0
htdocs/debugbar/class/DataCollector/DolLogsCollector.php

@@ -0,0 +1,182 @@
+<?php
+
+use DebugBar\DataCollector\MessagesCollector;
+use Psr\Log\LogLevel;
+//use ReflectionClass;
+
+/**
+ * DolLogsCollector class
+ */
+
+class DolLogsCollector extends MessagesCollector
+{
+	/**
+	 * @var string default logs file path
+	 */
+	protected $path;
+	/**
+	 * @var int number of lines to show
+	 */
+	protected $lines;
+
+	/**
+	 * Constructor
+	 *
+	 * @param string $path
+	 * @param string $name
+	 */
+	public function __construct($path = null, $name = 'logs')
+	{
+		global $conf;
+
+		parent::__construct($name);
+
+		$this->path = $path ?: $this->getLogsFile();
+		$this->lines = $conf->global->DEBUGBAR_LOGS_LINES_NUMBER ? $conf->global->DEBUGBAR_LOGS_LINES_NUMBER : 100;
+	}
+
+	/**
+	 *	Return widget settings
+	 *
+	 */
+	public function getWidgets()
+	{
+		global $langs;
+
+		$title = $langs->transnoentities('Logs');
+		$name = $this->getName();
+
+		return array(
+			"$title" => array(
+				"icon" => "list-alt",
+				"widget" => "PhpDebugBar.Widgets.MessagesWidget",
+				"map" => "$name.messages",
+				"default" => "[]"
+			),
+			"$title:badge" => array(
+				"map" => "$name.count",
+				"default" => "null"
+			)
+		);
+	}
+
+	/**
+	 *	Return collected data
+	 *
+	 */
+	public function collect()
+	{
+		$this->getStorageLogs($this->path);
+
+		return parent::collect();
+	}
+
+	/**
+	 * Get the path to the logs file
+	 *
+	 * @return string
+	 */
+	public function getLogsFile()
+	{
+		// default dolibarr log file
+		$path = DOL_DATA_ROOT . '/dolibarr.log';
+
+		return $path;
+	}
+
+	/**
+	 * Get logs
+	 *
+	 * @param string $path
+	 * @return array
+	 */
+	public function getStorageLogs($path)
+	{
+		if (! file_exists($path)) {
+			return;
+		}
+
+		// Load the latest lines
+		$file = implode("", $this->tailFile($path, $this->lines));
+
+		foreach ($this->getLogs($file) as $log) {
+			$this->addMessage($log['line'], $log['level'], false);
+		}
+	}
+
+	/**
+	 * Get latest file lines
+	 *
+	 * @param string $file
+	 * @param int $lines
+	 * @return array
+	 */
+	protected function tailFile($file, $lines)
+	{
+		$handle = fopen($file, "r");
+		$linecounter = $lines;
+		$pos = -2;
+		$beginning = false;
+		$text = [];
+		while ($linecounter > 0) {
+			$t = " ";
+			while ($t != "\n") {
+				if (fseek($handle, $pos, SEEK_END) == -1) {
+					$beginning = true;
+					break;
+				}
+				$t = fgetc($handle);
+				$pos--;
+			}
+			$linecounter--;
+			if ($beginning) {
+				rewind($handle);
+			}
+			$text[$lines - $linecounter - 1] = fgets($handle);
+			if ($beginning) {
+				break;
+			}
+		}
+		fclose($handle);
+		return array_reverse($text);
+	}
+
+	/**
+	 * Search a string for log entries
+	 *
+	 * @param $file
+	 * @return array
+	 */
+	public function getLogs($file)
+	{
+		$pattern = "/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.*/";
+		$log_levels = $this->getLevels();
+		preg_match_all($pattern, $file, $matches);
+		$log = [];
+		foreach ($matches as $lines) {
+			foreach ($lines as $line) {
+				foreach ($log_levels as $level_key => $level) {
+					if (strpos(strtolower($line), strtolower($level_key)) == 20) {
+						$log[] = ['level' => $level, 'line' => $line];
+					}
+				}
+			}
+		}
+		$log = array_reverse($log);
+		return $log;
+	}
+
+	/**
+	 * Get the log levels from psr/log.
+	 *
+	 * @return array
+	 */
+	public function getLevels()
+	{
+		$class = new ReflectionClass(new LogLevel());
+		$levels = $class->getConstants();
+		$levels['ERR'] = 'error';
+
+		return $levels;
+	}
+}

+ 28 - 0
htdocs/debugbar/class/DataCollector/DolMemoryCollector.php

@@ -0,0 +1,28 @@
+<?php
+
+use \DebugBar\DataCollector\MemoryCollector;
+
+/**
+ * DolMemoryCollector class
+ */
+
+class DolMemoryCollector extends MemoryCollector
+{
+	/**
+	 *	Return widget settings
+	 *
+	 */
+	public function getWidgets()
+	{
+		global $langs;
+
+		return array(
+			"memory" => array(
+				"icon" => "cogs",
+				"tooltip" => $langs->transnoentities('MemoryUsage'),
+				"map" => "memory.peak_usage_str",
+				"default" => "'0B'"
+			)
+		);
+	}
+}

+ 35 - 0
htdocs/debugbar/class/DataCollector/DolMessagesCollector.php

@@ -0,0 +1,35 @@
+<?php
+
+use \DebugBar\DataCollector\MessagesCollector;
+
+/**
+ * DolMessagesCollector class
+ */
+
+class DolMessagesCollector extends MessagesCollector
+{
+	/**
+	 *	Return widget settings
+	 *
+	 */
+	public function getWidgets()
+	{
+		global $langs;
+
+		$title = $langs->transnoentities('Messages');
+		$name = $this->getName();
+
+		return array(
+			"$title" => array(
+				"icon" => "list-alt",
+				"widget" => "PhpDebugBar.Widgets.MessagesWidget",
+				"map" => "$name.messages",
+				"default" => "[]"
+			),
+			"$title:badge" => array(
+				"map" => "$name.count",
+				"default" => "null"
+			)
+		);
+	}
+}

+ 117 - 0
htdocs/debugbar/class/DataCollector/DolQueryCollector.php

@@ -0,0 +1,117 @@
+<?php
+
+use DebugBar\DataCollector\AssetProvider;
+use DebugBar\DataCollector\DataCollector;
+use DebugBar\DataCollector\Renderable;
+use DebugBar\DebugBarException;
+
+dol_include_once('/debugbar/class/TraceableDB.php');
+
+/**
+ * DolQueryCollector class
+ */
+
+class DolQueryCollector extends DataCollector implements Renderable, AssetProvider
+{
+	/**
+	 * @var object Database handler
+	 */
+	protected $db;
+
+	/**
+	 * Constructor
+	 *
+	 */
+	public function __construct()
+	{
+		global $db;
+
+		$db = new TraceableDB($db);
+
+		$this->db = $db;
+	}
+
+	/**
+	 * Return collected data
+	 *
+	 */
+	public function collect()
+	{
+		$queries = array();
+		$totalExecTime = 0;
+		$totalMemoryUsage = 0;
+		$totalFailed = 0;
+		foreach ($this->db->queries as $query) {
+			$queries[] = array(
+				'sql' => $query['sql'],
+				'duration' => $query['duration'],
+				'duration_str' => $this->formatDuration($query['duration']),
+				'memory' => $query['memory_usage'],
+				'memory_str' => $this->formatBytes($query['memory_usage']),
+				'is_success' => $query['is_success'],
+				'error_code' => $query['error_code'],
+				'error_message' => $query['error_message']
+			);
+			$totalExecTime += $query['duration'];
+			$totalMemoryUsage += $query['memory_usage'];
+			if (! $query['is_success']) {
+				$totalFailed += 1;
+			}
+		}
+
+		return array(
+			'nb_statements' => count($queries),
+			'nb_failed_statements' => $totalFailed,
+			'accumulated_duration' => $totalExecTime,
+			'accumulated_duration_str' => $this->formatDuration($totalExecTime),
+			'memory_usage' => $totalMemoryUsage,
+			'memory_usage_str' => $this->formatBytes($totalMemoryUsage),
+			'statements' => $queries
+		);
+	}
+
+	/**
+	 *	Return collector name
+	 *
+	 */
+	public function getName()
+	{
+		return 'query';
+	}
+
+	/**
+	 *	Return widget settings
+	 *
+	 */
+	public function getWidgets()
+	{
+		global $langs;
+
+		$title = $langs->transnoentities('Database');
+
+		return array(
+			"$title" => array(
+				"icon" => "arrow-right",
+				"widget" => "PhpDebugBar.Widgets.SQLQueriesWidget",
+				"map" => "query",
+				"default" => "[]"
+			),
+			"$title:badge" => array(
+				"map" => "query.nb_statements",
+				"default" => 0
+			)
+		);
+	}
+
+	/**
+	 *	Return assets
+	 *
+	 */
+	public function getAssets()
+	{
+		return array(
+			'css' => 'widgets/sqlqueries/widget.css',
+			'js' => 'widgets/sqlqueries/widget.js'
+		);
+	}
+}

+ 28 - 0
htdocs/debugbar/class/DataCollector/DolRequestDataCollector.php

@@ -0,0 +1,28 @@
+<?php
+
+use \DebugBar\DataCollector\RequestDataCollector;
+
+/**
+ * DolRequestDataCollector class
+ */
+
+class DolRequestDataCollector extends RequestDataCollector
+{
+	/**
+	 *	Return widget settings
+	 *
+	 */
+	public function getWidgets()
+	{
+		global $langs;
+
+		return array(
+			$langs->transnoentities('Request') => array(
+				"icon" => "tags",
+				"widget" => "PhpDebugBar.Widgets.VariableListWidget",
+				"map" => "request",
+				"default" => "{}"
+			)
+		);
+	}
+}

+ 34 - 0
htdocs/debugbar/class/DataCollector/DolTimeDataCollector.php

@@ -0,0 +1,34 @@
+<?php
+
+use \DebugBar\DataCollector\TimeDataCollector;
+
+/**
+ * DolTimeDataCollector class
+ */
+
+class DolTimeDataCollector extends TimeDataCollector
+{
+	/**
+	 *	Return widget settings
+	 *
+	 */
+	public function getWidgets()
+	{
+		global $langs;
+
+		return array(
+			"time" => array(
+				"icon" => "clock-o",
+				"tooltip" => $langs->transnoentities('RequestDuration'),
+				"map" => "time.duration_str",
+				"default" => "'0ms'"
+			),
+			$langs->transnoentities('Timeline') => array(
+				"icon" => "tasks",
+				"widget" => "PhpDebugBar.Widgets.TimelineWidget",
+				"map" => "time",
+				"default" => "{}"
+			)
+		);
+	}
+}

+ 140 - 0
htdocs/debugbar/class/DataCollector/DolibarrCollector.php

@@ -0,0 +1,140 @@
+<?php
+
+use DebugBar\DataCollector\AssetProvider;
+use DebugBar\DataCollector\DataCollector;
+use DebugBar\DataCollector\Renderable;
+use DebugBar\DebugBarException;
+
+/**
+ * DolibarrCollector class
+ */
+
+class DolibarrCollector extends DataCollector implements Renderable, AssetProvider
+{
+	/**
+	 *	Return collector name
+	 *
+	 */
+	public function getName()
+	{
+		return 'dolibarr';
+	}
+
+	/**
+	 *	Return collected data
+	 *
+	 */
+	public function collect()
+	{
+		return array();
+	}
+
+	/**
+	 *	Return database info as an HTML string
+	 *
+	 */
+	protected function getDatabaseInfo()
+	{
+		global $conf, $langs;
+
+		$info  = $langs->trans('Host') . ': <strong>' . $conf->db->host . '</strong><br>';
+		$info .= $langs->trans('Port') . ': <strong>' . $conf->db->port . '</strong><br>';
+		$info .= $langs->trans('Name') . ': <strong>' . $conf->db->name . '</strong><br>';
+		$info .= $langs->trans('User') . ': <strong>' . $conf->db->user . '</strong><br>';
+		$info .= $langs->trans('Type') . ': <strong>' . $conf->db->type . '</strong><br>';
+		$info .= $langs->trans('Prefix') . ': <strong>' . $conf->db->prefix . '</strong><br>';
+		$info .= $langs->trans('Charset') . ': <strong>' . $conf->db->character_set . '</strong>';
+
+		return $info;
+	}
+
+	/**
+	 *	Return dolibarr info as an HTML string
+	 *
+	 */
+	protected function getDolibarrInfo()
+	{
+		global $conf, $langs;
+
+		$info  = $langs->trans('Version') . ': <strong>' . DOL_VERSION . '</strong><br>';
+		$info .= $langs->trans('Theme') . ': <strong>' . $conf->theme . '</strong><br>';
+		$info .= $langs->trans('Locale') . ': <strong>' . $conf->global->MAIN_LANG_DEFAULT . '</strong><br>';
+		$info .= $langs->trans('Currency') . ': <strong>' . $conf->currency . '</strong><br>';
+		$info .= $langs->trans('DolEntity') . ': <strong>' . $conf->entity . '</strong><br>';
+		$info .= $langs->trans('ListLimit') . ': <strong>' . ($conf->liste_limit ?: $conf->global->MAIN_SIZE_LISTE_LIMIT) . '</strong><br>';
+		$info .= $langs->trans('UploadSize') . ': <strong>' . $conf->global->MAIN_UPLOAD_DOC . '</strong>';
+
+		return $info;
+	}
+
+	/**
+	 *	Return mail info as an HTML string
+	 *
+	 */
+	protected function getMailInfo()
+	{
+		global $conf, $langs;
+
+		$info  = $langs->trans('Method') . ': <strong>' . $conf->global->MAIN_MAIL_SENDMODE . '</strong><br>';
+		$info .= $langs->trans('Server') . ': <strong>' . $conf->global->MAIN_MAIL_SMTP_SERVER . '</strong><br>';
+		$info .= $langs->trans('Port') . ': <strong>' . $conf->global->MAIN_MAIL_SMTP_PORT . '</strong><br>';
+		$info .= $langs->trans('ID') . ': <strong>' . $conf->global->MAIN_MAIL_SMTPS_ID . '</strong><br>';
+		$info .= $langs->trans('Pwd') . ': <strong>' . $conf->global->MAIN_MAIL_SMTPS_PW . '</strong><br>';
+		$info .= $langs->trans('TLS/STARTTLS') . ': <strong>' . $conf->global->MAIN_MAIL_EMAIL_TLS . '</strong> / <strong>' . $conf->global->MAIN_MAIL_EMAIL_STARTTLS . '</strong><br>';
+		$info .= $langs->trans('Status') . ': <strong>' . ($conf->global->MAIN_DISABLE_ALL_MAILS ? $langs->trans('StatusDisabled') : $langs->trans('StatusEnabled')) . '</strong>';
+
+		return $info;
+	}
+
+	/**
+	 *	Return widget settings
+	 *
+	 */
+	public function getWidgets()
+	{
+		return array(
+			"database_info" => array(
+				"icon" => "database",
+				"indicator" => "PhpDebugBar.DebugBar.TooltipIndicator",
+				"tooltip" => array(
+					"html" => $this->getDatabaseInfo(),
+					"class" => "tooltip-wide"
+				),
+				"map" => "",
+				"default" => ""
+			),
+			"dolibarr_info" => array(
+				"icon" => "desktop",
+				"indicator" => "PhpDebugBar.DebugBar.TooltipIndicator",
+				"tooltip" => array(
+					"html" => $this->getDolibarrInfo(),
+					"class" => "tooltip-wide"
+				),
+				"map" => "",
+				"default" => ""
+			),
+			"mail_info" => array(
+				"icon" => "envelope",
+				"indicator" => "PhpDebugBar.DebugBar.TooltipIndicator",
+				"tooltip" => array(
+					"html" => $this->getMailInfo(),
+					"class" => "tooltip-extra-wide"
+				),
+				"map" => "",
+				"default" => ""
+			)
+		);
+	}
+
+	/**
+	 *	Return collector assests
+	 *
+	 */
+	public function getAssets()
+	{
+		return array(
+			'base_url' => dol_buildpath('/debugbar', 1),
+			'js' => 'js/widgets.js'
+		);
+	}
+}

+ 55 - 0
htdocs/debugbar/class/DebugBar.php

@@ -0,0 +1,55 @@
+<?php
+
+dol_include_once('/debugbar/class/autoloader.php');
+
+use \DebugBar\DebugBar;
+use \DebugBar\DataCollector\PhpInfoCollector;
+dol_include_once('/debugbar/class/DataCollector/DolMessagesCollector.php');
+dol_include_once('/debugbar/class/DataCollector/DolRequestDataCollector.php');
+dol_include_once('/debugbar/class/DataCollector/DolConfigCollector.php');
+dol_include_once('/debugbar/class/DataCollector/DolTimeDataCollector.php');
+dol_include_once('/debugbar/class/DataCollector/DolMemoryCollector.php');
+dol_include_once('/debugbar/class/DataCollector/DolExceptionsCollector.php');
+dol_include_once('/debugbar/class/DataCollector/DolQueryCollector.php');
+dol_include_once('/debugbar/class/DataCollector/DolibarrCollector.php');
+dol_include_once('/debugbar/class/DataCollector/DolLogsCollector.php');
+
+/**
+ * DolibarrDebugBar class
+ *
+ * @see http://phpdebugbar.com/docs/base-collectors.html#base-collectors
+ */
+
+class DolibarrDebugBar extends DebugBar
+{
+	/**
+	 * Constructor
+	 *
+	 */
+	public function __construct()
+	{
+		global $conf;
+
+		//$this->addCollector(new PhpInfoCollector());
+		$this->addCollector(new DolMessagesCollector());
+		$this->addCollector(new DolRequestDataCollector());
+		$this->addCollector(new DolConfigCollector());
+		$this->addCollector(new DolTimeDataCollector());
+		$this->addCollector(new DolMemoryCollector());
+		$this->addCollector(new DolExceptionsCollector());
+		$this->addCollector(new DolQueryCollector());
+		$this->addCollector(new DolibarrCollector());
+		if ($conf->syslog->enabled) {
+			$this->addCollector(new DolLogsCollector());
+		}
+	}
+
+	/**
+	 * Returns a JavascriptRenderer for this instance
+	 *
+	 */
+	public function getRenderer()
+	{
+	    return parent::getJavascriptRenderer(DOL_URL_ROOT.'/includes/DebugBar/Resources');
+	}
+}

+ 679 - 0
htdocs/debugbar/class/TraceableDB.php

@@ -0,0 +1,679 @@
+<?php
+
+require_once DOL_DOCUMENT_ROOT .'/core/db/DoliDB.class.php';
+
+/**
+ * TraceableDB class
+ *
+ * Used to log queries into DebugBar
+ */
+
+class TraceableDB extends DoliDB
+{
+	/**
+	 * @var DoliDb Database handler
+	 */
+	public $db; // cannot be protected because of parent declaration
+	/**
+	 * @var array Queries array
+	 */
+	public $queries;
+	/**
+	 * @var int Request start time
+	 */
+	protected $startTime;
+	/**
+	 * @var int Request start memory
+	 */
+	protected $startMemory;
+	/**
+	 * @var Database type
+	 */
+	public $type;
+	/**
+	 * @const Database label
+	 */
+	const LABEL = ''; // TODO: the right value should be $this->db::LABEL (but this is a constant? o_O)
+	/**
+	 * @const Version min database
+	 */
+	const VERSIONMIN = ''; // TODO: the same thing here, $this->db::VERSIONMIN is the right value
+
+	/**
+	 * Constructor
+	 *
+	 * @param		DoliDB		$db      Database handler
+	 */
+	public function __construct($db)
+	{
+		$this->db      = $db;
+		$this->type    = $db->type;
+		$this->queries = array();
+	}
+
+	/**
+	 * Format a SQL IF
+	 *
+	 * @param   string $test Test string (example: 'cd.statut=0', 'field IS NULL')
+	 * @param   string $resok resultat si test egal
+	 * @param   string $resko resultat si test non egal
+	 * @return	string                SQL string
+	 */
+	public function ifsql($test, $resok, $resko)
+	{
+		return $this->db->ifsql($test, $resok, $resko);
+	}
+
+	/**
+	 * Return datas as an array
+	 *
+	 * @param   resource $resultset Resultset of request
+	 * @return  array                    Array
+	 */
+	public function fetch_row($resultset)
+	{
+		return $this->db->fetch_row($resultset);
+	}
+
+	/**
+	 * Convert (by PHP) a GM Timestamp date into a string date with PHP server TZ to insert into a date field.
+	 * Function to use to build INSERT, UPDATE or WHERE predica
+	 *
+	 * @param   int		$param 		Date TMS to convert
+	 * @return  string            	Date in a string YYYYMMDDHHMMSS
+	 */
+	public function idate($param)
+	{
+		return $this->db->idate($param);
+	}
+
+	/**
+	 * Return last error code
+	 *
+	 * @return  string    lasterrno
+	 */
+	public function lasterrno()
+	{
+		return $this->db->lasterrno();
+	}
+
+	/**
+	 * Start transaction
+	 *
+	 * @return  int         1 if transaction successfuly opened or already opened, 0 if error
+	 */
+	public function begin()
+	{
+		return $this->db->begin();
+	}
+
+	/**
+	 * Create a new database
+	 * Do not use function xxx_create_db (xxx=mysql, ...) as they are deprecated
+	 * We force to create database with charset this->forcecharset and collate this->forcecollate
+	 *
+	 * @param   string 		$database 		Database name to create
+	 * @param   string 		$charset 		Charset used to store data
+	 * @param   string 		$collation 		Charset used to sort data
+	 * @param   string 		$owner 			Username of database owner
+	 * @return  resource                	resource defined if OK, null if KO
+	 */
+	public function DDLCreateDb($database, $charset = '', $collation = '', $owner = '')
+	{
+		return $this->db->DDLCreateDb($database, $charset, $collation, $owner);
+	}
+
+	/**
+	 * Return version of database server into an array
+	 *
+	 * @return	array        Version array
+	 */
+	public function getVersionArray()
+	{
+		return $this->db->getVersionArray();
+	}
+
+	/**
+	 *  Convert a SQL request in Mysql syntax to native syntax
+	 *
+	 * @param   string $line SQL request line to convert
+	 * @param   string $type Type of SQL order ('ddl' for insert, update, select, delete or 'dml' for create, alter...)
+	 * @return  string        SQL request line converted
+	 */
+	public static function convertSQLFromMysql($line, $type = 'ddl')
+	{
+		return $this->db->convertSQLFromMysql($line);
+	}
+
+	/**
+	 * Renvoie le nombre de lignes dans le resultat d'une requete INSERT, DELETE ou UPDATE
+	 *
+	 * @param   resource $resultset Curseur de la requete voulue
+	 * @return 	int            Nombre de lignes
+	 * @see    	num_rows
+	 */
+	public function affected_rows($resultset)
+	{
+		return $this->db->affected_rows($resultset);
+	}
+
+	/**
+	 * Return description of last error
+	 *
+	 * @return  string        Error text
+	 */
+	public function error()
+	{
+		return $this->db->error();
+	}
+
+	/**
+	 *  List tables into a database
+	 *
+	 *  @param	string		$database	Name of database
+	 *  @param	string		$table		Nmae of table filter ('xxx%')
+	 *  @return	array					List of tables in an array
+	 */
+	public function DDLListTables($database, $table = '')
+	{
+		return $this->db->DDLListTables($database, $table);
+	}
+
+	/**
+	 * Return last request executed with query()
+	 *
+	 * @return	string                    Last query
+	 */
+	public function lastquery()
+	{
+		return $this->db->lastquery();
+	}
+
+	/**
+	 * Define sort criteria of request
+	 *
+	 * @param   string $sortfield List of sort fields
+	 * @param   string $sortorder Sort order
+	 * @return  string            String to provide syntax of a sort sql string
+	 */
+	public function order($sortfield = null, $sortorder = null)
+	{
+		return $this->db->order($sortfield, $sortorder);
+	}
+
+	/**
+	 * Decrypt sensitive data in database
+	 *
+	 * @param    string $value Value to decrypt
+	 * @return   string                    Decrypted value if used
+	 */
+	public function decrypt($value)
+	{
+		return $this->db->decrypt($value);
+	}
+
+	/**
+	 *    Return datas as an array
+	 *
+	 * @param   resource $resultset Resultset of request
+	 * @return  array                    Array
+	 */
+	public function fetch_array($resultset)
+	{
+		return $this->db->fetch_array($resultset);
+	}
+
+	/**
+	 * Return last error label
+	 *
+	 * @return	string    lasterror
+	 */
+	public function lasterror()
+	{
+		return $this->db->lasterror();
+	}
+
+	/**
+	 * Escape a string to insert data
+	 *
+	 * @param   string $stringtoencode String to escape
+	 * @return  string                        String escaped
+	 */
+	public function escape($stringtoencode)
+	{
+		return $this->db->escape($stringtoencode);
+	}
+
+	/**
+	 * Get last ID after an insert INSERT
+	 *
+	 * @param	string 	$tab 		Table name concerned by insert. Ne sert pas sous MySql mais requis pour compatibilite avec Postgresql
+	 * @param   string 	$fieldid 	Field name
+	 * @return  int                	Id of row
+	 */
+	public function last_insert_id($tab, $fieldid = 'rowid')
+	{
+		return $this->db->last_insert_id($tab, $fieldid);
+	}
+
+	/**
+	 *    Return full path of restore program
+	 *
+	 * @return        string        Full path of restore program
+	 */
+	public function getPathOfRestore()
+	{
+		return $this->db->getPathOfRestore();
+	}
+
+	/**
+	 *    Annulation d'une transaction et retour aux anciennes valeurs
+	 *
+	 * @param	string $log Add more log to default log line
+	 * @return  int                1 si annulation ok ou transaction non ouverte, 0 en cas d'erreur
+	 */
+	public function rollback($log = '')
+	{
+		return $this->db->rollback($log);
+	}
+
+	/**
+	 * Execute a SQL request and return the resultset
+	 *
+	 * @param   string $query SQL query string
+	 * @param   int $usesavepoint 0=Default mode, 1=Run a savepoint before and a rollback to savepoint if error (this allow to have some request with errors inside global transactions).
+	 *                                    Note that with Mysql, this parameter is not used as Myssql can already commit a transaction even if one request is in error, without using savepoints.
+	 * @param   string $type Type of SQL order ('ddl' for insert, update, select, delete or 'dml' for create, alter...)
+	 * @return  resource                Resultset of answer
+	 */
+	public function query($query, $usesavepoint = 0, $type = 'auto')
+	{
+		$this->startTracing();
+
+		$resql = $this->db->query($query, $usesavepoint, $type);
+
+		$this->endTracing($query, $resql);
+
+		return $resql;
+	}
+
+	/**
+	 * Start query tracing
+	 */
+	protected function startTracing()
+	{
+		$this->startTime   = microtime(true);
+		$this->startMemory = memory_get_usage(true);
+	}
+
+	/**
+	 * End query tracing
+	 *
+	 * @param     $sql       query string
+	 * @param     $resql     query result
+	 */
+	protected function endTracing($sql, $resql)
+	{
+		$endTime     = microtime(true);
+		$duration    = $endTime - $this->startTime;
+		$endMemory   = memory_get_usage(true);
+		$memoryDelta = $endMemory - $this->startMemory;
+
+		$this->queries[] = array(
+			'sql'           => $sql,
+			'duration'      => $duration,
+			'memory_usage'  => $memoryDelta,
+			'is_success'    => $resql,
+			'error_code'    => ! $resql ? $this->db->lasterrno() : null,
+			'error_message' => ! $resql ? $this->db->lasterror() : null
+		);
+	}
+
+	/**
+	 *    Connexion to server
+	 *
+	 * @param   string $host database server host
+	 * @param   string $login login
+	 * @param   string $passwd password
+	 * @param   string $name name of database (not used for mysql, used for pgsql)
+	 * @param   int    $port Port of database server
+	 * @return  resource            Database access handler
+	 * @see     close
+	 */
+	public function connect($host, $login, $passwd, $name, $port = 0)
+	{
+		return $this->db->connect($host, $login, $passwd, $name, $port);
+	}
+
+	/**
+	 *    Define limits and offset of request
+	 *
+	 * @param   int $limit Maximum number of lines returned (-1=conf->liste_limit, 0=no limit)
+	 * @param   int $offset Numero of line from where starting fetch
+	 * @return  string            String with SQL syntax to add a limit and offset
+	 */
+	public function plimit($limit = 0, $offset = 0)
+	{
+		return $this->db->plimit($limit, $offset);
+	}
+
+	/**
+	 * Return value of server parameters
+	 *
+	 * @param   string	$filter		Filter list on a particular value
+	 * @return  array				Array of key-values (key=>value)
+	 */
+	public function getServerParametersValues($filter = '')
+	{
+		return $this->db->getServerParametersValues($filter);
+	}
+
+	/**
+	 * Return value of server status
+	 *
+	 * @param   string $filter 		Filter list on a particular value
+	 * @return  array				Array of key-values (key=>value)
+	 */
+	public function getServerStatusValues($filter = '')
+	{
+		return $this->db->getServerStatusValues($filter);
+	}
+
+	/**
+	 * Return collation used in database
+	 *
+	 * @return  string        Collation value
+	 */
+	public function getDefaultCollationDatabase()
+	{
+		return $this->db->getDefaultCollationDatabase();
+	}
+
+	/**
+	 * Return number of lines for result of a SELECT
+	 *
+	 * @param   resource $resultset Resulset of requests
+	 * @return 	int                        Nb of lines
+	 * @see    	affected_rows
+	 */
+	public function num_rows($resultset)
+	{
+		return $this->db->num_rows($resultset);
+	}
+
+	/**
+	 * Return full path of dump program
+	 *
+	 * @return        string        Full path of dump program
+	 */
+	public function getPathOfDump()
+	{
+		return $this->db->getPathOfDump();
+	}
+
+	/**
+	 * Return version of database client driver
+	 *
+	 * @return            string      Version string
+	 */
+	public function getDriverInfo()
+	{
+		return $this->db->getDriverInfo();
+	}
+
+	/**
+	 * Return generic error code of last operation.
+	 *
+	 * @return    string        Error code (Exemples: DB_ERROR_TABLE_ALREADY_EXISTS, DB_ERROR_RECORD_ALREADY_EXISTS...)
+	 */
+	public function errno()
+	{
+		return $this->db->errno();
+	}
+
+	/**
+	 * Create a table into database
+	 *
+	 * @param        string $table 			Name of table
+	 * @param        array 	$fields 		Tableau associatif [nom champ][tableau des descriptions]
+	 * @param        string $primary_key 	Nom du champ qui sera la clef primaire
+	 * @param        string $type 			Type de la table
+	 * @param        array 	$unique_keys 	Tableau associatifs Nom de champs qui seront clef unique => valeur
+	 * @param        array 	$fulltext_keys 	Tableau des Nom de champs qui seront indexes en fulltext
+	 * @param        array $keys 			Tableau des champs cles noms => valeur
+	 * @return       int                    <0 if KO, >=0 if OK
+	 */
+	public function DDLCreateTable($table, $fields, $primary_key, $type, $unique_keys = null, $fulltext_keys = null, $keys = null)
+	{
+		return $this->db->DDLCreateTable($table, $fields, $primary_key, $type, $unique_keys, $fulltext_keys, $keys);
+	}
+
+	/**
+	 * Drop a table into database
+	 *
+	 * @param        string $table 			Name of table
+	 * @return       int                    <0 if KO, >=0 if OK
+	 */
+	public function DDLDropTable($table)
+	{
+		return $this->db->DDLDropTable($table);
+	}
+
+	/**
+	 * Return list of available charset that can be used to store data in database
+	 *
+	 * @return        array        List of Charset
+	 */
+	public function getListOfCharacterSet()
+	{
+		return $this->db->getListOfCharacterSet();
+	}
+
+	/**
+	 * Create a new field into table
+	 *
+	 * @param    string $table 				Name of table
+	 * @param    string $field_name 		Name of field to add
+	 * @param    string $field_desc 		Tableau associatif de description du champ a inserer[nom du parametre][valeur du parametre]
+	 * @param    string $field_position 	Optionnel ex.: "after champtruc"
+	 * @return   int                        <0 if KO, >0 if OK
+	 */
+	public function DDLAddField($table, $field_name, $field_desc, $field_position = "")
+	{
+		return $this->db->DDLAddField($table, $field_name, $field_desc, $field_position);
+	}
+
+	/**
+	 * Drop a field from table
+	 *
+	 * @param    string $table 				Name of table
+	 * @param    string $field_name 		Name of field to drop
+	 * @return   int                        <0 if KO, >0 if OK
+	 */
+	public function DDLDropField($table, $field_name)
+	{
+		return $this->db->DDLDropField($table, $field_name);
+	}
+
+	/**
+	 * Update format of a field into a table
+	 *
+	 * @param    string 	$table 			Name of table
+	 * @param    string 	$field_name 	Name of field to modify
+	 * @param    string 	$field_desc 	Array with description of field format
+	 * @return   int                        <0 if KO, >0 if OK
+	 */
+	public function DDLUpdateField($table, $field_name, $field_desc)
+	{
+		return $this->db->DDLUpdateField($table, $field_name, $field_desc);
+	}
+
+	/**
+	 * Return list of available collation that can be used for database
+	 *
+	 * @return        array        			List of Collation
+	 */
+	public function getListOfCollation()
+	{
+		return $this->db->getListOfCollation();
+	}
+
+	/**
+	 * Return a pointer of line with description of a table or field
+	 *
+	 * @param    string 	$table 			Name of table
+	 * @param    string 	$field 			Optionnel : Name of field if we want description of field
+	 * @return   resource            		Resource
+	 */
+	public function DDLDescTable($table, $field = "")
+	{
+		return $this->db->DDLDescTable($table, $field);
+	}
+
+	/**
+	 * Return version of database server
+	 *
+	 * @return            string      		Version string
+	 */
+	public function getVersion()
+	{
+		return $this->db->getVersion();
+	}
+
+	/**
+	 * Return charset used to store data in database
+	 *
+	 * @return        string        		Charset
+	 */
+	public function getDefaultCharacterSetDatabase()
+	{
+		return $this->db->getDefaultCharacterSetDatabase();
+	}
+
+	/**
+	 * Create a user and privileges to connect to database (even if database does not exists yet)
+	 *
+	 * @param    string $dolibarr_main_db_host 	Ip serveur
+	 * @param    string $dolibarr_main_db_user 	Nom user a creer
+	 * @param    string $dolibarr_main_db_pass 	Mot de passe user a creer
+	 * @param    string $dolibarr_main_db_name 	Database name where user must be granted
+	 * @return   int                            <0 if KO, >=0 if OK
+	 */
+	public function DDLCreateUser($dolibarr_main_db_host, $dolibarr_main_db_user, $dolibarr_main_db_pass, $dolibarr_main_db_name)
+	{
+		return $this->db->DDLCreateUser($dolibarr_main_db_host, $dolibarr_main_db_user, $dolibarr_main_db_pass, $dolibarr_main_db_name);
+	}
+
+	/**
+	 * Convert (by PHP) a PHP server TZ string date into a Timestamps date (GMT if gm=true)
+	 * 19700101020000 -> 3600 with TZ+1 and gmt=0
+	 * 19700101020000 -> 7200 whaterver is TZ if gmt=1
+	 *
+	 * @param	string			$string		Date in a string (YYYYMMDDHHMMSS, YYYYMMDD, YYYY-MM-DD HH:MM:SS)
+	 * @param	bool			$gm			1=Input informations are GMT values, otherwise local to server TZ
+	 * @return	int|string					Date TMS or ''
+	 */
+	public function jdate($string, $gm=false)
+	{
+		return $this->db->jdate($string, $gm);
+	}
+
+	/**
+	 * Encrypt sensitive data in database
+	 * Warning: This function includes the escape, so it must use direct value
+	 *
+	 * @param   string 			$fieldorvalue 	Field name or value to encrypt
+	 * @param  	int 			$withQuotes 	Return string with quotes
+	 * @return 	string                     		XXX(field) or XXX('value') or field or 'value'
+	 */
+	public function encrypt($fieldorvalue, $withQuotes = 0)
+	{
+		return $this->db->encrypt($fieldorvalue, $withQuotes);
+	}
+
+	/**
+	 * Validate a database transaction
+	 *
+	 * @param   string 			$log 			Add more log to default log line
+	 * @return	int                				1 if validation is OK or transaction level no started, 0 if ERROR
+	 */
+	public function commit($log = '')
+	{
+		return $this->db->commit($log);
+	}
+
+	/**
+	 * List information of columns into a table.
+	 *
+	 * @param   string 			$table 			Name of table
+	 * @return  array                			Array with inforation on table
+	 */
+	public function DDLInfoTable($table)
+	{
+		return $this->db->DDLInfoTable($table);
+	}
+
+	/**
+	 * Free last resultset used.
+	 *
+	 * @param  	resource 		$resultset 		Fre cursor
+	 * @return  void
+	 */
+	public function free($resultset = null)
+	{
+		return $this->db->free($resultset);
+	}
+
+	/**
+	 * Close database connexion
+	 *
+	 * @return  boolean     					True if disconnect successfull, false otherwise
+	 * @see     connect
+	 */
+	public function close()
+	{
+		return $this->db->close();
+	}
+
+	/**
+	 * Return last query in error
+	 *
+	 * @return  string    lastqueryerror
+	 */
+	public function lastqueryerror()
+	{
+		return $this->db->lastqueryerror();
+	}
+
+	/**
+	 * Return connexion ID
+	 *
+	 * @return  string      Id connexion
+	 */
+	public function DDLGetConnectId()
+	{
+		return $this->db->DDLGetConnectId();
+	}
+
+	/**
+	 * Renvoie la ligne courante (comme un objet) pour le curseur resultset
+	 *
+	 * @param   resource $resultset Curseur de la requete voulue
+	 * @return  Object                    Object result line or false if KO or end of cursor
+	 */
+	public function fetch_object($resultset)
+	{
+		return $this->db->fetch_object($resultset);
+	}
+
+	/**
+	 * Select a database
+	 *
+	 * @param	string $database Name of database
+	 * @return  boolean            true if OK, false if KO
+	 */
+	public function select_db($database)
+	{
+		return $this->db->select_db($database);
+	}
+}

+ 112 - 0
htdocs/debugbar/class/actions_debugbar.class.php

@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * ActionsDebugBar class
+ */
+
+class ActionsDebugBar
+{
+	/**
+	 * Load Debug bar
+	 *
+	 */
+	protected function loadDebugBar()
+	{
+		global $conf, $debugbar;
+
+		dol_include_once('/debugbar/class/DebugBar.php');
+		$debugbar = new DolibarrDebugBar();
+		$renderer = $debugbar->getRenderer();
+		$conf->global->MAIN_HTML_HEADER .= $renderer->renderHead();
+	}
+
+	/**
+	 * Overloading the afterLogin function
+	 *
+	 * @param   array()         $parameters     Hook metadatas (context, etc...)
+	 * @param   CommonObject    &$object        The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...)
+	 * @param   string          &$action        Current action (if set). Generally create or edit or null
+	 * @param   HookManager     $hookmanager    Hook manager propagated to allow calling another hook
+	 * @return  int                             < 0 on error, 0 on success, 1 to replace standard code
+	 */
+	public function afterLogin($parameters, &$object, &$action, $hookmanager)
+	{
+		$error = 0; // Error counter
+
+		if (in_array('login', explode(':', $parameters['context'])))
+		{
+			$this->loadDebugBar();
+		}
+
+		if (! $error)
+		{
+			return 0; // or return 1 to replace standard code
+		}
+		else
+		{
+			return -1;
+		}
+	}
+
+	/**
+	 * Overloading the updateSession function
+	 *
+	 * @param   array()         $parameters     Hook metadatas (context, etc...)
+	 * @param   CommonObject    &$object        The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...)
+	 * @param   string          &$action        Current action (if set). Generally create or edit or null
+	 * @param   HookManager     $hookmanager    Hook manager propagated to allow calling another hook
+	 * @return  int                             < 0 on error, 0 on success, 1 to replace standard code
+	 */
+	public function updateSession($parameters, &$object, &$action, $hookmanager)
+	{
+		$error = 0; // Error counter
+
+		if (in_array('main', explode(':', $parameters['context'])))
+		{
+			$this->loadDebugBar();
+		}
+
+		if (! $error)
+		{
+			return 0; // or return 1 to replace standard code
+		}
+		else
+		{
+			return -1;
+		}
+	}
+
+	/**
+	 * Overloading the printCommonFooter function
+	 *
+	 * @param   array()         $parameters     Hook metadatas (context, etc...)
+	 * @param   CommonObject    &$object        The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...)
+	 * @param   string          &$action        Current action (if set). Generally create or edit or null
+	 * @param   HookManager     $hookmanager    Hook manager propagated to allow calling another hook
+	 * @return  int                             < 0 on error, 0 on success, 1 to replace standard code
+	 */
+	public function printCommonFooter($parameters, &$object, &$action, $hookmanager)
+	{
+		global $user, $debugbar, $langs;
+
+		$error = 0; // Error counter
+		$context = explode(':', $parameters['context']);
+
+		if (in_array('main', $context) || in_array('login', $context))
+		{
+			if ($user->rights->debugbar->read && is_object($debugbar)) {
+				$renderer = $debugbar->getRenderer();
+				echo $renderer->render();
+			}
+		}
+
+		if (! $error)
+		{
+			return 0; // or return 1 to replace standard code
+		}
+		else
+		{
+			return -1;
+		}
+	}
+}

+ 19 - 0
htdocs/debugbar/class/autoloader.php

@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * Simple autoloader, so we don't need Composer just for this.
+ */
+
+spl_autoload_register(function ($class) {
+    if (preg_match('/^DebugBar/', $class) || preg_match('/^'.preg_quote('Psr\Log','/').'/', $class))
+    {
+        $file = DOL_DOCUMENT_ROOT.'/includes/'.str_replace('\\', DIRECTORY_SEPARATOR, $class).'.php';
+        //var_dump($class.' - '.file_exists($file).' - '.$file);
+        if (file_exists($file)) {
+    		require_once $file;
+    		return true;
+    	}
+    	return false;
+    }
+    return true;
+});

+ 75 - 0
htdocs/debugbar/js/widgets.js

@@ -0,0 +1,75 @@
+(function ($) {
+
+    var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-');
+
+    /**
+     * TooltipIndicator
+     *
+     * A customised indicator class that will provide a better tooltip.
+     *
+     * Options:
+     *  - icon
+     *  - title
+     *  - tooltip: array('html' => '', 'class' => '')
+     *  - data: alias of title
+     */
+    var TooltipIndicator = PhpDebugBar.DebugBar.TooltipIndicator = PhpDebugBar.DebugBar.Indicator.extend({
+
+        render: function() {
+            this.$icon = $('<i />').appendTo(this.$el);
+            this.bindAttr('icon', function(icon) {
+                if (icon) {
+                    this.$icon.attr('class', 'fa fa-' + icon);
+                } else {
+                    this.$icon.attr('class', '');
+                }
+            });
+
+            this.bindAttr(['title', 'data'], $('<span />').addClass(csscls('text')).appendTo(this.$el));
+
+            this.$tooltip = $('<span />').addClass(csscls('tooltip disabled')).appendTo(this.$el);
+            this.bindAttr('tooltip', function(tooltip) {
+                if (tooltip['html']) {
+                    tooltipHTML = $('<span />').html(tooltip['html']).addClass(csscls('tooltip-html'));
+                    this.$tooltip.html(tooltipHTML).removeClass(csscls('disabled'));
+                    if (tooltip['class']) {
+                        this.$tooltip.addClass(csscls(tooltip['class']));
+                    }
+                } else {
+                    this.$tooltip.addClass(csscls('disabled'));
+                }
+            });
+        }
+
+    });
+
+    /**
+     * LinkIndicator
+     *
+     * A customised indicator class that will allow "click" behaviour.
+     *
+     * Options:
+     *  - icon
+     *  - title
+     *  - tooltip
+     *  - data: alias of title
+     *  - href
+     *  - target
+     */
+    var LinkIndicator = PhpDebugBar.DebugBar.LinkIndicator = PhpDebugBar.DebugBar.Indicator.extend({
+
+        tagName: 'a',
+
+        render: function() {
+            LinkIndicator.__super__.render.apply(this);
+            this.bindAttr('href', function(href) {
+                this.$el.attr('href', href);
+            });
+            this.bindAttr('target', function(target) {
+                this.$el.attr('target', target);
+            });
+        }
+
+    });
+
+})(PhpDebugBar.$);

+ 62 - 0
htdocs/includes/DebugBar/Bridge/CacheCacheCollector.php

@@ -0,0 +1,62 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Bridge;
+
+use CacheCache\Cache;
+use CacheCache\LoggingBackend;
+use Monolog\Logger;
+
+/**
+ * Collects CacheCache operations
+ *
+ * http://maximebf.github.io/CacheCache/
+ *
+ * Example:
+ * <code>
+ * $debugbar->addCollector(new CacheCacheCollector(CacheManager::get('default')));
+ * // or
+ * $debugbar->addCollector(new CacheCacheCollector());
+ * $debugbar['cache']->addCache(CacheManager::get('default'));
+ * </code>
+ */
+class CacheCacheCollector extends MonologCollector
+{
+    protected $logger;
+
+    public function __construct(Cache $cache = null, Logger $logger = null, $level = Logger::DEBUG, $bubble = true)
+    {
+        parent::__construct(null, $level, $bubble);
+
+        if ($logger === null) {
+            $logger = new Logger('Cache');
+        }
+        $this->logger = $logger;
+
+        if ($cache !== null) {
+            $this->addCache($cache);
+        }
+    }
+
+    public function addCache(Cache $cache)
+    {
+        $backend = $cache->getBackend();
+        if (!($backend instanceof LoggingBackend)) {
+            $backend = new LoggingBackend($backend, $this->logger);
+        }
+        $cache->setBackend($backend);
+        $this->addLogger($backend->getLogger());
+    }
+
+    public function getName()
+    {
+        return 'cache';
+    }
+}

+ 98 - 0
htdocs/includes/DebugBar/Bridge/DoctrineCollector.php

@@ -0,0 +1,98 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Bridge;
+
+use DebugBar\DataCollector\AssetProvider;
+use DebugBar\DataCollector\DataCollector;
+use DebugBar\DataCollector\Renderable;
+use DebugBar\DebugBarException;
+use Doctrine\DBAL\Logging\DebugStack;
+use Doctrine\ORM\EntityManager;
+
+/**
+ * Collects Doctrine queries
+ *
+ * http://doctrine-project.org
+ *
+ * Uses the DebugStack logger to collects data about queries
+ *
+ * <code>
+ * $debugStack = new Doctrine\DBAL\Logging\DebugStack();
+ * $entityManager->getConnection()->getConfiguration()->setSQLLogger($debugStack);
+ * $debugbar->addCollector(new DoctrineCollector($debugStack));
+ * </code>
+ */
+class DoctrineCollector extends DataCollector implements Renderable, AssetProvider
+{
+    protected $debugStack;
+
+    public function __construct($debugStackOrEntityManager)
+    {
+        if ($debugStackOrEntityManager instanceof EntityManager) {
+            $debugStackOrEntityManager = $debugStackOrEntityManager->getConnection()->getConfiguration()->getSQLLogger();
+        }
+        if (!($debugStackOrEntityManager instanceof DebugStack)) {
+            throw new DebugBarException("'DoctrineCollector' requires an 'EntityManager' or 'DebugStack' object");
+        }
+        $this->debugStack = $debugStackOrEntityManager;
+    }
+
+    public function collect()
+    {
+        $queries = array();
+        $totalExecTime = 0;
+        foreach ($this->debugStack->queries as $q) {
+            $queries[] = array(
+                'sql' => $q['sql'],
+                'params' => (object) $q['params'],
+                'duration' => $q['executionMS'],
+                'duration_str' => $this->formatDuration($q['executionMS'])
+            );
+            $totalExecTime += $q['executionMS'];
+        }
+
+        return array(
+            'nb_statements' => count($queries),
+            'accumulated_duration' => $totalExecTime,
+            'accumulated_duration_str' => $this->formatDuration($totalExecTime),
+            'statements' => $queries
+        );
+    }
+
+    public function getName()
+    {
+        return 'doctrine';
+    }
+
+    public function getWidgets()
+    {
+        return array(
+            "database" => array(
+                "icon" => "arrow-right",
+                "widget" => "PhpDebugBar.Widgets.SQLQueriesWidget",
+                "map" => "doctrine",
+                "default" => "[]"
+            ),
+            "database:badge" => array(
+                "map" => "doctrine.nb_statements",
+                "default" => 0
+            )
+        );
+    }
+
+    public function getAssets()
+    {
+        return array(
+            'css' => 'widgets/sqlqueries/widget.css',
+            'js' => 'widgets/sqlqueries/widget.js'
+        );
+    }
+}

+ 103 - 0
htdocs/includes/DebugBar/Bridge/MonologCollector.php

@@ -0,0 +1,103 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Bridge;
+
+use DebugBar\DataCollector\DataCollectorInterface;
+use DebugBar\DataCollector\MessagesAggregateInterface;
+use DebugBar\DataCollector\Renderable;
+use Monolog\Handler\AbstractProcessingHandler;
+use Monolog\Logger;
+
+/**
+ * A monolog handler as well as a data collector
+ *
+ * https://github.com/Seldaek/monolog
+ *
+ * <code>
+ * $debugbar->addCollector(new MonologCollector($logger));
+ * </code>
+ */
+class MonologCollector extends AbstractProcessingHandler implements DataCollectorInterface, Renderable, MessagesAggregateInterface
+{
+    protected $name;
+
+    protected $records = array();
+
+    /**
+     * @param Logger $logger
+     * @param int $level
+     * @param boolean $bubble
+     * @param string $name
+     */
+    public function __construct(Logger $logger = null, $level = Logger::DEBUG, $bubble = true, $name = 'monolog')
+    {
+        parent::__construct($level, $bubble);
+        $this->name = $name;
+        if ($logger !== null) {
+            $this->addLogger($logger);
+        }
+    }
+
+    /**
+     * Adds logger which messages you want to log
+     *
+     * @param Logger $logger
+     */
+    public function addLogger(Logger $logger)
+    {
+        $logger->pushHandler($this);
+    }
+
+    protected function write(array $record)
+    {
+        $this->records[] = array(
+            'message' => $record['formatted'],
+            'is_string' => true,
+            'label' => strtolower($record['level_name']),
+            'time' => $record['datetime']->format('U')
+        );
+    }
+
+    public function getMessages()
+    {
+        return $this->records;
+    }
+
+    public function collect()
+    {
+        return array(
+            'count' => count($this->records),
+            'records' => $this->records
+        );
+    }
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function getWidgets()
+    {
+        $name = $this->getName();
+        return array(
+            $name => array(
+                "icon" => "suitcase",
+                "widget" => "PhpDebugBar.Widgets.MessagesWidget",
+                "map" => "$name.records",
+                "default" => "[]"
+            ),
+            "$name:badge" => array(
+                "map" => "$name.count",
+                "default" => "null"
+            )
+        );
+    }
+}

+ 253 - 0
htdocs/includes/DebugBar/Bridge/PropelCollector.php

@@ -0,0 +1,253 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Bridge;
+
+use BasicLogger;
+use DebugBar\DataCollector\AssetProvider;
+use DebugBar\DataCollector\DataCollector;
+use DebugBar\DataCollector\Renderable;
+use Propel;
+use PropelConfiguration;
+use PropelPDO;
+use Psr\Log\LogLevel;
+use Psr\Log\LoggerInterface;
+
+/**
+ * A Propel logger which acts as a data collector
+ *
+ * http://propelorm.org/
+ *
+ * Will log queries and display them using the SQLQueries widget.
+ * You can provide a LoggerInterface object to forward non-query related message to.
+ *
+ * Example:
+ * <code>
+ * $debugbar->addCollector(new PropelCollector($debugbar['messages']));
+ * PropelCollector::enablePropelProfiling();
+ * </code>
+ */
+class PropelCollector extends DataCollector implements BasicLogger, Renderable, AssetProvider
+{
+    protected $logger;
+
+    protected $statements = array();
+
+    protected $accumulatedTime = 0;
+
+    protected $peakMemory = 0;
+
+    /**
+     * Sets the needed configuration option in propel to enable query logging
+     *
+     * @param PropelConfiguration $config Apply profiling on a specific config
+     */
+    public static function enablePropelProfiling(PropelConfiguration $config = null)
+    {
+        if ($config === null) {
+            $config = Propel::getConfiguration(PropelConfiguration::TYPE_OBJECT);
+        }
+        $config->setParameter('debugpdo.logging.details.method.enabled', true);
+        $config->setParameter('debugpdo.logging.details.time.enabled', true);
+        $config->setParameter('debugpdo.logging.details.mem.enabled', true);
+        $allMethods = array(
+            'PropelPDO::__construct',       // logs connection opening
+            'PropelPDO::__destruct',        // logs connection close
+            'PropelPDO::exec',              // logs a query
+            'PropelPDO::query',             // logs a query
+            'PropelPDO::beginTransaction',  // logs a transaction begin
+            'PropelPDO::commit',            // logs a transaction commit
+            'PropelPDO::rollBack',          // logs a transaction rollBack (watch out for the capital 'B')
+            'DebugPDOStatement::execute',   // logs a query from a prepared statement
+        );
+        $config->setParameter('debugpdo.logging.methods', $allMethods, false);
+    }
+
+    /**
+     * @param LoggerInterface $logger A logger to forward non-query log lines to
+     * @param PropelPDO $conn Bound this collector to a connection only
+     */
+    public function __construct(LoggerInterface $logger = null, PropelPDO $conn = null)
+    {
+        if ($conn) {
+            $conn->setLogger($this);
+        } else {
+            Propel::setLogger($this);
+        }
+        $this->logger = $logger;
+        $this->logQueriesToLogger = false;
+    }
+
+    public function setLogQueriesToLogger($enable = true)
+    {
+        $this->logQueriesToLogger = $enable;
+        return $this;
+    }
+
+    public function isLogQueriesToLogger()
+    {
+        return $this->logQueriesToLogger;
+    }
+
+    public function emergency($m)
+    {
+        $this->log($m, Propel::LOG_EMERG);
+    }
+
+    public function alert($m)
+    {
+        $this->log($m, Propel::LOG_ALERT);
+    }
+
+    public function crit($m)
+    {
+        $this->log($m, Propel::LOG_CRIT);
+    }
+
+    public function err($m)
+    {
+        $this->log($m, Propel::LOG_ERR);
+    }
+
+    public function warning($m)
+    {
+        $this->log($m, Propel::LOG_WARNING);
+    }
+
+    public function notice($m)
+    {
+        $this->log($m, Propel::LOG_NOTICE);
+    }
+
+    public function info($m)
+    {
+        $this->log($m, Propel::LOG_INFO);
+    }
+
+    public function debug($m)
+    {
+        $this->log($m, Propel::LOG_DEBUG);
+    }
+
+    public function log($message, $severity = null)
+    {
+        if (strpos($message, 'DebugPDOStatement::execute') !== false) {
+            list($sql, $duration_str) = $this->parseAndLogSqlQuery($message);
+            if (!$this->logQueriesToLogger) {
+                return;
+            }
+            $message = "$sql ($duration_str)";
+        }
+
+        if ($this->logger !== null) {
+            $this->logger->log($this->convertLogLevel($severity), $message);
+        }
+    }
+
+    /**
+     * Converts Propel log levels to PSR log levels
+     *
+     * @param int $level
+     * @return string
+     */
+    protected function convertLogLevel($level)
+    {
+        $map = array(
+            Propel::LOG_EMERG => LogLevel::EMERGENCY,
+            Propel::LOG_ALERT => LogLevel::ALERT,
+            Propel::LOG_CRIT => LogLevel::CRITICAL,
+            Propel::LOG_ERR => LogLevel::ERROR,
+            Propel::LOG_WARNING => LogLevel::WARNING,
+            Propel::LOG_NOTICE => LogLevel::NOTICE,
+            Propel::LOG_DEBUG => LogLevel::DEBUG
+        );
+        return $map[$level];
+    }
+
+    /**
+     * Parse a log line to extract query information
+     *
+     * @param string $message
+     */
+    protected function parseAndLogSqlQuery($message)
+    {
+        $parts = explode('|', $message, 4);
+        $sql = trim($parts[3]);
+
+        $duration = 0;
+        if (preg_match('/([0-9]+\.[0-9]+)/', $parts[1], $matches)) {
+            $duration = (float) $matches[1];
+        }
+
+        $memory = 0;
+        if (preg_match('/([0-9]+\.[0-9]+) ([A-Z]{1,2})/', $parts[2], $matches)) {
+            $memory = (float) $matches[1];
+            if ($matches[2] == 'KB') {
+                $memory *= 1024;
+            } elseif ($matches[2] == 'MB') {
+                $memory *= 1024 * 1024;
+            }
+        }
+
+        $this->statements[] = array(
+            'sql' => $sql,
+            'is_success' => true,
+            'duration' => $duration,
+            'duration_str' => $this->formatDuration($duration),
+            'memory' => $memory,
+            'memory_str' => $this->formatBytes($memory)
+        );
+        $this->accumulatedTime += $duration;
+        $this->peakMemory = max($this->peakMemory, $memory);
+        return array($sql, $this->formatDuration($duration));
+    }
+
+    public function collect()
+    {
+        return array(
+            'nb_statements' => count($this->statements),
+            'nb_failed_statements' => 0,
+            'accumulated_duration' => $this->accumulatedTime,
+            'accumulated_duration_str' => $this->formatDuration($this->accumulatedTime),
+            'peak_memory_usage' => $this->peakMemory,
+            'peak_memory_usage_str' => $this->formatBytes($this->peakMemory),
+            'statements' => $this->statements
+        );
+    }
+
+    public function getName()
+    {
+        return 'propel';
+    }
+
+    public function getWidgets()
+    {
+        return array(
+            "propel" => array(
+                "icon" => "bolt",
+                "widget" => "PhpDebugBar.Widgets.SQLQueriesWidget",
+                "map" => "propel",
+                "default" => "[]"
+            ),
+            "propel:badge" => array(
+                "map" => "propel.nb_statements",
+                "default" => 0
+            )
+        );
+    }
+
+    public function getAssets()
+    {
+        return array(
+            'css' => 'widgets/sqlqueries/widget.css',
+            'js' => 'widgets/sqlqueries/widget.js'
+        );
+    }
+}

+ 66 - 0
htdocs/includes/DebugBar/Bridge/SlimCollector.php

@@ -0,0 +1,66 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Bridge;
+
+use DebugBar\DataCollector\MessagesCollector;
+use Psr\Log\LogLevel;
+use Slim\Log;
+use Slim\Slim;
+
+/**
+ * Collects messages from a Slim logger
+ *
+ * http://slimframework.com
+ */
+class SlimCollector extends MessagesCollector
+{
+    protected $slim;
+
+    protected $originalLogWriter;
+
+    public function __construct(Slim $slim)
+    {
+        $this->slim = $slim;
+        if ($log = $slim->getLog()) {
+            $this->originalLogWriter = $log->getWriter();
+            $log->setWriter($this);
+            $log->setEnabled(true);
+        }
+    }
+
+    public function write($message, $level)
+    {
+        if ($this->originalLogWriter) {
+            $this->originalLogWriter->write($message, $level);
+        }
+        $this->addMessage($message, $this->getLevelName($level));
+    }
+
+    protected function getLevelName($level)
+    {
+        $map = array(
+            Log::EMERGENCY => LogLevel::EMERGENCY,
+            Log::ALERT => LogLevel::ALERT,
+            Log::CRITICAL => LogLevel::CRITICAL,
+            Log::ERROR => LogLevel::ERROR,
+            Log::WARN => LogLevel::WARNING,
+            Log::NOTICE => LogLevel::NOTICE,
+            Log::INFO => LogLevel::INFO,
+            Log::DEBUG => LogLevel::DEBUG
+        );
+        return $map[$level];
+    }
+
+    public function getName()
+    {
+        return 'slim';
+    }
+}

+ 44 - 0
htdocs/includes/DebugBar/Bridge/SwiftMailer/SwiftLogCollector.php

@@ -0,0 +1,44 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Bridge\SwiftMailer;
+
+use DebugBar\DataCollector\MessagesCollector;
+use Swift_Mailer;
+use Swift_Plugins_Logger;
+use Swift_Plugins_LoggerPlugin;
+
+/**
+ * Collects log messages
+ *
+ * http://swiftmailer.org/
+ */
+class SwiftLogCollector extends MessagesCollector implements Swift_Plugins_Logger
+{
+    public function __construct(Swift_Mailer $mailer)
+    {
+        $mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($this));
+    }
+
+    public function add($entry)
+    {
+        $this->addMessage($entry);
+    }
+
+    public function dump()
+    {
+        return implode(PHP_EOL, $this->_log);
+    }
+
+    public function getName()
+    {
+        return 'swiftmailer_logs';
+    }
+}

+ 92 - 0
htdocs/includes/DebugBar/Bridge/SwiftMailer/SwiftMailCollector.php

@@ -0,0 +1,92 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Bridge\SwiftMailer;
+
+use DebugBar\DataCollector\AssetProvider;
+use DebugBar\DataCollector\DataCollector;
+use DebugBar\DataCollector\Renderable;
+use Swift_Mailer;
+use Swift_Plugins_MessageLogger;
+
+/**
+ * Collects data about sent mails
+ *
+ * http://swiftmailer.org/
+ */
+class SwiftMailCollector extends DataCollector implements Renderable, AssetProvider
+{
+    protected $messagesLogger;
+
+    public function __construct(Swift_Mailer $mailer)
+    {
+        $this->messagesLogger = new Swift_Plugins_MessageLogger();
+        $mailer->registerPlugin($this->messagesLogger);
+    }
+
+    public function collect()
+    {
+        $mails = array();
+        foreach ($this->messagesLogger->getMessages() as $msg) {
+            $mails[] = array(
+                'to' => $this->formatTo($msg->getTo()),
+                'subject' => $msg->getSubject(),
+                'headers' => $msg->getHeaders()->toString()
+            );
+        }
+        return array(
+            'count' => count($mails),
+            'mails' => $mails
+        );
+    }
+
+    protected function formatTo($to)
+    {
+        if (!$to) {
+            return '';
+        }
+
+        $f = array();
+        foreach ($to as $k => $v) {
+            $f[] = (empty($v) ? '' : "$v ") . "<$k>";
+        }
+        return implode(', ', $f);
+    }
+
+    public function getName()
+    {
+        return 'swiftmailer_mails';
+    }
+
+    public function getWidgets()
+    {
+        return array(
+            'emails' => array(
+                'icon' => 'inbox',
+                'widget' => 'PhpDebugBar.Widgets.MailsWidget',
+                'map' => 'swiftmailer_mails.mails',
+                'default' => '[]',
+                'title' => 'Mails'
+            ),
+            'emails:badge' => array(
+                'map' => 'swiftmailer_mails.count',
+                'default' => 'null'
+            )
+        );
+    }
+
+    public function getAssets()
+    {
+        return array(
+            'css' => 'widgets/mails/widget.css',
+            'js' => 'widgets/mails/widget.js'
+        );
+    }
+}

+ 417 - 0
htdocs/includes/DebugBar/Bridge/Twig/TraceableTwigEnvironment.php

@@ -0,0 +1,417 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Bridge\Twig;
+
+use DebugBar\DataCollector\TimeDataCollector;
+use Twig_CompilerInterface;
+use Twig_Environment;
+use Twig_ExtensionInterface;
+use Twig_LexerInterface;
+use Twig_LoaderInterface;
+use Twig_NodeInterface;
+use Twig_NodeVisitorInterface;
+use Twig_ParserInterface;
+use Twig_TokenParserInterface;
+use Twig_TokenStream;
+
+/**
+ * Wrapped a Twig Environment to provide profiling features
+ */
+class TraceableTwigEnvironment extends Twig_Environment
+{
+    protected $twig;
+
+    protected $renderedTemplates = array();
+
+    protected $timeDataCollector;
+
+    /**
+     * @param Twig_Environment $twig
+     * @param TimeDataCollector $timeDataCollector
+     */
+    public function __construct(Twig_Environment $twig, TimeDataCollector $timeDataCollector = null)
+    {
+        $this->twig = $twig;
+        $this->timeDataCollector = $timeDataCollector;
+    }
+
+    public function __call($name, $arguments)
+    {
+        return call_user_func_array(array($this->twig, $name), $arguments);
+    }
+
+    public function getRenderedTemplates()
+    {
+        return $this->renderedTemplates;
+    }
+
+    public function addRenderedTemplate(array $info)
+    {
+        $this->renderedTemplates[] = $info;
+    }
+
+    public function getTimeDataCollector()
+    {
+        return $this->timeDataCollector;
+    }
+
+    public function getBaseTemplateClass()
+    {
+        return $this->twig->getBaseTemplateClass();
+    }
+
+    public function setBaseTemplateClass($class)
+    {
+        $this->twig->setBaseTemplateClass($class);
+    }
+
+    public function enableDebug()
+    {
+        $this->twig->enableDebug();
+    }
+
+    public function disableDebug()
+    {
+        $this->twig->disableDebug();
+    }
+
+    public function isDebug()
+    {
+        return $this->twig->isDebug();
+    }
+
+    public function enableAutoReload()
+    {
+        $this->twig->enableAutoReload();
+    }
+
+    public function disableAutoReload()
+    {
+        $this->twig->disableAutoReload();
+    }
+
+    public function isAutoReload()
+    {
+        return $this->twig->isAutoReload();
+    }
+
+    public function enableStrictVariables()
+    {
+        $this->twig->enableStrictVariables();
+    }
+
+    public function disableStrictVariables()
+    {
+        $this->twig->disableStrictVariables();
+    }
+
+    public function isStrictVariables()
+    {
+        return $this->twig->isStrictVariables();
+    }
+
+    public function getCache()
+    {
+        return $this->twig->getCache();
+    }
+
+    public function setCache($cache)
+    {
+        $this->twig->setCache($cache);
+    }
+
+    public function getCacheFilename($name)
+    {
+        return $this->twig->getCacheFilename($name);
+    }
+
+    public function getTemplateClass($name, $index = null)
+    {
+        return $this->twig->getTemplateClass($name, $index);
+    }
+
+    public function getTemplateClassPrefix()
+    {
+        return $this->twig->getTemplateClassPrefix();
+    }
+
+    public function render($name, array $context = array())
+    {
+        return $this->loadTemplate($name)->render($context);
+    }
+
+    public function display($name, array $context = array())
+    {
+        $this->loadTemplate($name)->display($context);
+    }
+
+    public function loadTemplate($name, $index = null)
+    {
+        $cls = $this->twig->getTemplateClass($name, $index);
+
+        if (isset($this->twig->loadedTemplates[$cls])) {
+            return $this->twig->loadedTemplates[$cls];
+        }
+
+        if (!class_exists($cls, false)) {
+            if (false === $cache = $this->getCacheFilename($name)) {
+                eval('?>'.$this->compileSource($this->getLoader()->getSource($name), $name));
+            } else {
+                if (!is_file($cache) || ($this->isAutoReload() && !$this->isTemplateFresh($name, filemtime($cache)))) {
+                    $this->writeCacheFile($cache, $this->compileSource($this->getLoader()->getSource($name), $name));
+                }
+
+                require_once $cache;
+            }
+        }
+
+        if (!$this->twig->runtimeInitialized) {
+            $this->initRuntime();
+        }
+
+        return $this->twig->loadedTemplates[$cls] = new TraceableTwigTemplate($this, new $cls($this));
+    }
+
+    public function isTemplateFresh($name, $time)
+    {
+        return $this->twig->isTemplateFresh($name, $time);
+    }
+
+    public function resolveTemplate($names)
+    {
+        return $this->twig->resolveTemplate($names);
+    }
+
+    public function clearTemplateCache()
+    {
+        $this->twig->clearTemplateCache();
+    }
+
+    public function clearCacheFiles()
+    {
+        $this->twig->clearCacheFiles();
+    }
+
+    public function getLexer()
+    {
+        return $this->twig->getLexer();
+    }
+
+    public function setLexer(Twig_LexerInterface $lexer)
+    {
+        $this->twig->setLexer($lexer);
+    }
+
+    public function tokenize($source, $name = null)
+    {
+        return $this->twig->tokenize($source, $name);
+    }
+
+    public function getParser()
+    {
+        return $this->twig->getParser();
+    }
+
+    public function setParser(Twig_ParserInterface $parser)
+    {
+        $this->twig->setParser($parser);
+    }
+
+    public function parse(Twig_TokenStream $tokens)
+    {
+        return $this->twig->parse($tokens);
+    }
+
+    public function getCompiler()
+    {
+        return $this->twig->getCompiler();
+    }
+
+    public function setCompiler(Twig_CompilerInterface $compiler)
+    {
+        $this->twig->setCompiler($compiler);
+    }
+
+    public function compile(Twig_NodeInterface $node)
+    {
+        return $this->twig->compile($node);
+    }
+
+    public function compileSource($source, $name = null)
+    {
+        return $this->twig->compileSource($source, $name);
+    }
+
+    public function setLoader(Twig_LoaderInterface $loader)
+    {
+        $this->twig->setLoader($loader);
+    }
+
+    public function getLoader()
+    {
+        return $this->twig->getLoader();
+    }
+
+    public function setCharset($charset)
+    {
+        $this->twig->setCharset($charset);
+    }
+
+    public function getCharset()
+    {
+        return $this->twig->getCharset();
+    }
+
+    public function initRuntime()
+    {
+        $this->twig->initRuntime();
+    }
+
+    public function hasExtension($name)
+    {
+        return $this->twig->hasExtension($name);
+    }
+
+    public function getExtension($name)
+    {
+        return $this->twig->getExtension($name);
+    }
+
+    public function addExtension(Twig_ExtensionInterface $extension)
+    {
+        $this->twig->addExtension($extension);
+    }
+
+    public function removeExtension($name)
+    {
+        $this->twig->removeExtension($name);
+    }
+
+    public function setExtensions(array $extensions)
+    {
+        $this->twig->setExtensions($extensions);
+    }
+
+    public function getExtensions()
+    {
+        return $this->twig->getExtensions();
+    }
+
+    public function addTokenParser(Twig_TokenParserInterface $parser)
+    {
+        $this->twig->addTokenParser($parser);
+    }
+
+    public function getTokenParsers()
+    {
+        return $this->twig->getTokenParsers();
+    }
+
+    public function getTags()
+    {
+        return $this->twig->getTags();
+    }
+
+    public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
+    {
+        $this->twig->addNodeVisitor($visitor);
+    }
+
+    public function getNodeVisitors()
+    {
+        return $this->twig->getNodeVisitors();
+    }
+
+    public function addFilter($name, $filter = null)
+    {
+        $this->twig->addFilter($name, $filter);
+    }
+
+    public function getFilter($name)
+    {
+        return $this->twig->getFilter($name);
+    }
+
+    public function registerUndefinedFilterCallback($callable)
+    {
+        $this->twig->registerUndefinedFilterCallback($callable);
+    }
+
+    public function getFilters()
+    {
+        return $this->twig->getFilters();
+    }
+
+    public function addTest($name, $test = null)
+    {
+        $this->twig->addTest($name, $test);
+    }
+
+    public function getTests()
+    {
+        return $this->twig->getTests();
+    }
+
+    public function getTest($name)
+    {
+        return $this->twig->getTest($name);
+    }
+
+    public function addFunction($name, $function = null)
+    {
+        $this->twig->addFunction($name, $function);
+    }
+
+    public function getFunction($name)
+    {
+        return $this->twig->getFunction($name);
+    }
+
+    public function registerUndefinedFunctionCallback($callable)
+    {
+        $this->twig->registerUndefinedFunctionCallback($callable);
+    }
+
+    public function getFunctions()
+    {
+        return $this->twig->getFunctions();
+    }
+
+    public function addGlobal($name, $value)
+    {
+        $this->twig->addGlobal($name, $value);
+    }
+
+    public function getGlobals()
+    {
+        return $this->twig->getGlobals();
+    }
+
+    public function mergeGlobals(array $context)
+    {
+        return $this->twig->mergeGlobals($context);
+    }
+
+    public function getUnaryOperators()
+    {
+        return $this->twig->getUnaryOperators();
+    }
+
+    public function getBinaryOperators()
+    {
+        return $this->twig->getBinaryOperators();
+    }
+
+    public function computeAlternatives($name, $items)
+    {
+        return $this->twig->computeAlternatives($name, $items);
+    }
+}

+ 131 - 0
htdocs/includes/DebugBar/Bridge/Twig/TraceableTwigTemplate.php

@@ -0,0 +1,131 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Bridge\Twig;
+
+use Twig_Template;
+use Twig_TemplateInterface;
+
+/**
+ * Wraps a Twig_Template to add profiling features
+ */
+class TraceableTwigTemplate implements Twig_TemplateInterface
+{
+    protected $template;
+
+    /**
+     * @param TraceableTwigEnvironment $env
+     * @param Twig_Template $template
+     */
+    public function __construct(TraceableTwigEnvironment $env, Twig_Template $template)
+    {
+        $this->env = $env;
+        $this->template = $template;
+    }
+
+    public function __call($name, $arguments)
+    {
+        return call_user_func_array(array($this->template, $name), $arguments);
+    }
+
+    public function getTemplateName()
+    {
+        return $this->template->getTemplateName();
+    }
+
+    public function getEnvironment()
+    {
+        return $this->template->getEnvironment();
+    }
+
+    public function getParent(array $context)
+    {
+        return $this->template->getParent($context);
+    }
+
+    public function isTraitable()
+    {
+        return $this->template->isTraitable();
+    }
+
+    public function displayParentBlock($name, array $context, array $blocks = array())
+    {
+        $this->template->displayParentBlock($name, $context, $blocks);
+    }
+
+    public function displayBlock($name, array $context, array $blocks = array(), $useBlocks = true)
+    {
+        $this->template->displayBlock($name, $context, $blocks, $useBlocks);
+    }
+
+    public function renderParentBlock($name, array $context, array $blocks = array())
+    {
+        return $this->template->renderParentBlock($name, $context, $blocks);
+    }
+
+    public function renderBlock($name, array $context, array $blocks = array(), $useBlocks = true)
+    {
+        return $this->template->renderBlock($name, $context, $blocks, $useBlocks);
+    }
+
+    public function hasBlock($name)
+    {
+        return $this->template->hasBlock($name);
+    }
+
+    public function getBlockNames()
+    {
+        return $this->template->getBlockNames();
+    }
+
+    public function getBlocks()
+    {
+        return $this->template->getBlocks();
+    }
+
+    public function display(array $context, array $blocks = array())
+    {
+        $start = microtime(true);
+        $this->template->display($context, $blocks);
+        $end = microtime(true);
+
+        if ($timeDataCollector = $this->env->getTimeDataCollector()) {
+            $name = sprintf("twig.render(%s)", $this->template->getTemplateName());
+            $timeDataCollector->addMeasure($name, $start, $end);
+        }
+
+        $this->env->addRenderedTemplate(array(
+            'name' => $this->template->getTemplateName(),
+            'render_time' => $end - $start
+        ));
+    }
+
+    public function render(array $context)
+    {
+        $level = ob_get_level();
+        ob_start();
+        try {
+            $this->display($context);
+        } catch (Exception $e) {
+            while (ob_get_level() > $level) {
+                ob_end_clean();
+            }
+
+            throw $e;
+        }
+
+        return ob_get_clean();
+    }
+
+    public static function clearCache()
+    {
+        Twig_Template::clearCache();
+    }
+}

+ 87 - 0
htdocs/includes/DebugBar/Bridge/Twig/TwigCollector.php

@@ -0,0 +1,87 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Bridge\Twig;
+
+use DebugBar\DataCollector\AssetProvider;
+use DebugBar\DataCollector\DataCollector;
+use DebugBar\DataCollector\Renderable;
+
+/**
+ * Collects data about rendered templates
+ *
+ * http://twig.sensiolabs.org/
+ *
+ * Your Twig_Environment object needs to be wrapped in a
+ * TraceableTwigEnvironment object
+ *
+ * <code>
+ * $env = new TraceableTwigEnvironment(new Twig_Environment($loader));
+ * $debugbar->addCollector(new TwigCollector($env));
+ * </code>
+ */
+class TwigCollector extends DataCollector implements Renderable, AssetProvider
+{
+    public function __construct(TraceableTwigEnvironment $twig)
+    {
+        $this->twig = $twig;
+    }
+
+    public function collect()
+    {
+        $templates = array();
+        $accuRenderTime = 0;
+
+        foreach ($this->twig->getRenderedTemplates() as $tpl) {
+            $accuRenderTime += $tpl['render_time'];
+            $templates[] = array(
+                'name' => $tpl['name'],
+                'render_time' => $tpl['render_time'],
+                'render_time_str' => $this->formatDuration($tpl['render_time'])
+            );
+        }
+
+        return array(
+            'nb_templates' => count($templates),
+            'templates' => $templates,
+            'accumulated_render_time' => $accuRenderTime,
+            'accumulated_render_time_str' => $this->formatDuration($accuRenderTime)
+        );
+    }
+
+    public function getName()
+    {
+        return 'twig';
+    }
+
+    public function getWidgets()
+    {
+        return array(
+            'twig' => array(
+                'icon' => 'leaf',
+                'widget' => 'PhpDebugBar.Widgets.TemplatesWidget',
+                'map' => 'twig',
+                'default' => '[]'
+            ),
+            'twig:badge' => array(
+                'map' => 'twig.nb_templates',
+                'default' => 0
+            )
+        );
+    }
+
+    public function getAssets()
+    {
+        return array(
+            'css' => 'widgets/templates/widget.css',
+            'js' => 'widgets/templates/widget.js'
+        );
+    }
+}

+ 166 - 0
htdocs/includes/DebugBar/DataCollector/AggregatedCollector.php

@@ -0,0 +1,166 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataCollector;
+
+use ArrayAccess;
+use DebugBar\DebugBarException;
+
+/**
+ * Aggregates data from multiple collectors
+ *
+ * <code>
+ * $aggcollector = new AggregateCollector('foobar');
+ * $aggcollector->addCollector(new MessagesCollector('msg1'));
+ * $aggcollector->addCollector(new MessagesCollector('msg2'));
+ * $aggcollector['msg1']->addMessage('hello world');
+ * </code>
+ */
+class AggregatedCollector implements DataCollectorInterface, ArrayAccess
+{
+    protected $name;
+
+    protected $mergeProperty;
+
+    protected $sort;
+
+    protected $collectors = array();
+
+    /**
+     * @param string $name
+     * @param string $mergeProperty
+     * @param boolean $sort
+     */
+    public function __construct($name, $mergeProperty = null, $sort = false)
+    {
+        $this->name = $name;
+        $this->mergeProperty = $mergeProperty;
+        $this->sort = $sort;
+    }
+
+    /**
+     * @param DataCollectorInterface $collector
+     */
+    public function addCollector(DataCollectorInterface $collector)
+    {
+        $this->collectors[$collector->getName()] = $collector;
+    }
+
+    /**
+     * @return array
+     */
+    public function getCollectors()
+    {
+        return $this->collectors;
+    }
+
+    /**
+     * Merge data from one of the key/value pair of the collected data
+     *
+     * @param string $property
+     */
+    public function setMergeProperty($property)
+    {
+        $this->mergeProperty = $property;
+    }
+
+    /**
+     * @return string
+     */
+    public function getMergeProperty()
+    {
+        return $this->mergeProperty;
+    }
+
+    /**
+     * Sorts the collected data
+     *
+     * If true, sorts using sort()
+     * If it is a string, sorts the data using the value from a key/value pair of the array
+     *
+     * @param bool|string $sort
+     */
+    public function setSort($sort)
+    {
+        $this->sort = $sort;
+    }
+
+    /**
+     * @return bool|string
+     */
+    public function getSort()
+    {
+        return $this->sort;
+    }
+
+    public function collect()
+    {
+        $aggregate = array();
+        foreach ($this->collectors as $collector) {
+            $data = $collector->collect();
+            if ($this->mergeProperty !== null) {
+                $data = $data[$this->mergeProperty];
+            }
+            $aggregate = array_merge($aggregate, $data);
+        }
+
+        return $this->sort($aggregate);
+    }
+
+    /**
+     * Sorts the collected data
+     *
+     * @param array $data
+     * @return array
+     */
+    protected function sort($data)
+    {
+        if (is_string($this->sort)) {
+            $p = $this->sort;
+            usort($data, function ($a, $b) use ($p) {
+                if ($a[$p] == $b[$p]) {
+                    return 0;
+                }
+                return $a[$p] < $b[$p] ? -1 : 1;
+            });
+        } elseif ($this->sort === true) {
+            sort($data);
+        }
+        return $data;
+    }
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    // --------------------------------------------
+    // ArrayAccess implementation
+
+    public function offsetSet($key, $value)
+    {
+        throw new DebugBarException("AggregatedCollector[] is read-only");
+    }
+
+    public function offsetGet($key)
+    {
+        return $this->collectors[$key];
+    }
+
+    public function offsetExists($key)
+    {
+        return isset($this->collectors[$key]);
+    }
+
+    public function offsetUnset($key)
+    {
+        throw new DebugBarException("AggregatedCollector[] is read-only");
+    }
+}

+ 28 - 0
htdocs/includes/DebugBar/DataCollector/AssetProvider.php

@@ -0,0 +1,28 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataCollector;
+
+/**
+ * Indicates that a DataCollector provides some assets
+ */
+interface AssetProvider
+{
+    /**
+     * Returns an array with the following keys:
+     *  - base_path
+     *  - base_url
+     *  - css: an array of filenames
+     *  - js: an array of filenames
+     *
+     * @return array
+     */
+    function getAssets();
+}

+ 71 - 0
htdocs/includes/DebugBar/DataCollector/ConfigCollector.php

@@ -0,0 +1,71 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataCollector;
+
+/**
+ * Collects array data
+ */
+class ConfigCollector extends DataCollector implements Renderable
+{
+    protected $name;
+
+    protected $data;
+
+    /**
+     * @param array  $data
+     * @param string $name
+     */
+    public function __construct(array $data = array(), $name = 'config')
+    {
+        $this->name = $name;
+        $this->data = $data;
+    }
+
+    /**
+     * Sets the data
+     *
+     * @param array $data
+     */
+    public function setData(array $data)
+    {
+        $this->data = $data;
+    }
+
+    public function collect()
+    {
+        $data = array();
+        foreach ($this->data as $k => $v) {
+            if (!is_string($v)) {
+                $v = $this->getDataFormatter()->formatVar($v);
+            }
+            $data[$k] = $v;
+        }
+        return $data;
+    }
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function getWidgets()
+    {
+        $name = $this->getName();
+        return array(
+            "$name" => array(
+                "icon" => "gear",
+                "widget" => "PhpDebugBar.Widgets.VariableListWidget",
+                "map" => "$name",
+                "default" => "{}"
+            )
+        );
+    }
+}

+ 90 - 0
htdocs/includes/DebugBar/DataCollector/DataCollector.php

@@ -0,0 +1,90 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataCollector;
+
+use DebugBar\DataFormatter\DataFormatter;
+use DebugBar\DataFormatter\DataFormatterInterface;
+
+/**
+ * Abstract class for data collectors
+ */
+abstract class DataCollector implements DataCollectorInterface
+{
+    private static $defaultDataFormatter;
+
+    protected $dataFormater;
+
+    /**
+     * Sets the default data formater instance used by all collectors subclassing this class
+     *
+     * @param DataFormatterInterface $formater
+     */
+    public static function setDefaultDataFormatter(DataFormatterInterface $formater)
+    {
+        self::$defaultDataFormatter = $formater;
+    }
+
+    /**
+     * Returns the default data formater
+     *
+     * @return DataFormatterInterface
+     */
+    public static function getDefaultDataFormatter()
+    {
+        if (self::$defaultDataFormatter === null) {
+            self::$defaultDataFormatter = new DataFormatter();
+        }
+        return self::$defaultDataFormatter;
+    }
+
+    /**
+     * Sets the data formater instance used by this collector
+     *
+     * @param DataFormatterInterface $formater
+     */
+    public function setDataFormatter(DataFormatterInterface $formater)
+    {
+        $this->dataFormater = $formater;
+        return $this;
+    }
+
+    public function getDataFormatter()
+    {
+        if ($this->dataFormater === null) {
+            $this->dataFormater = self::getDefaultDataFormatter();
+        }
+        return $this->dataFormater;
+    }
+
+    /**
+     * @deprecated
+     */
+    public function formatVar($var)
+    {
+        return $this->getDataFormatter()->formatVar($var);
+    }
+
+    /**
+     * @deprecated
+     */
+    public function formatDuration($seconds)
+    {
+        return $this->getDataFormatter()->formatDuration($seconds);
+    }
+
+    /**
+     * @deprecated
+     */
+    public function formatBytes($size, $precision = 2)
+    {
+        return $this->getDataFormatter()->formatBytes($size, $precision);
+    }
+}

+ 31 - 0
htdocs/includes/DebugBar/DataCollector/DataCollectorInterface.php

@@ -0,0 +1,31 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataCollector;
+
+/**
+ * DataCollector Interface
+ */
+interface DataCollectorInterface
+{
+    /**
+     * Called by the DebugBar when data needs to be collected
+     *
+     * @return array Collected data
+     */
+    function collect();
+
+    /**
+     * Returns the unique name of the collector
+     *
+     * @return string
+     */
+    function getName();
+}

+ 111 - 0
htdocs/includes/DebugBar/DataCollector/ExceptionsCollector.php

@@ -0,0 +1,111 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataCollector;
+
+use Exception;
+
+/**
+ * Collects info about exceptions
+ */
+class ExceptionsCollector extends DataCollector implements Renderable
+{
+    protected $exceptions = array();
+    protected $chainExceptions = false;
+
+    /**
+     * Adds an exception to be profiled in the debug bar
+     *
+     * @param Exception $e
+     */
+    public function addException(Exception $e)
+    {
+        $this->exceptions[] = $e;
+        if ($this->chainExceptions && $previous = $e->getPrevious()) {
+            $this->addException($previous);
+        }
+    }
+
+    /**
+     * Configure whether or not all chained exceptions should be shown.
+     *
+     * @param bool $chainExceptions
+     */
+    public function setChainExceptions($chainExceptions = true)
+    {
+        $this->chainExceptions = $chainExceptions;
+    }
+
+    /**
+     * Returns the list of exceptions being profiled
+     *
+     * @return array[Exception]
+     */
+    public function getExceptions()
+    {
+        return $this->exceptions;
+    }
+
+    public function collect()
+    {
+        return array(
+            'count' => count($this->exceptions),
+            'exceptions' => array_map(array($this, 'formatExceptionData'), $this->exceptions)
+        );
+    }
+
+    /**
+     * Returns exception data as an array
+     *
+     * @param Exception $e
+     * @return array
+     */
+    public function formatExceptionData(Exception $e)
+    {
+        $filePath = $e->getFile();
+        if ($filePath && file_exists($filePath)) {
+            $lines = file($filePath);
+            $start = $e->getLine() - 4;
+            $lines = array_slice($lines, $start < 0 ? 0 : $start, 7);
+        } else {
+            $lines = array("Cannot open the file ($filePath) in which the exception occurred ");
+        }
+
+        return array(
+            'type' => get_class($e),
+            'message' => $e->getMessage(),
+            'code' => $e->getCode(),
+            'file' => $filePath,
+            'line' => $e->getLine(),
+            'surrounding_lines' => $lines
+        );
+    }
+
+    public function getName()
+    {
+        return 'exceptions';
+    }
+
+    public function getWidgets()
+    {
+        return array(
+            'exceptions' => array(
+                'icon' => 'bug',
+                'widget' => 'PhpDebugBar.Widgets.ExceptionsWidget',
+                'map' => 'exceptions.exceptions',
+                'default' => '[]'
+            ),
+            'exceptions:badge' => array(
+                'map' => 'exceptions.count',
+                'default' => 'null'
+            )
+        );
+    }
+}

+ 64 - 0
htdocs/includes/DebugBar/DataCollector/LocalizationCollector.php

@@ -0,0 +1,64 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataCollector;
+
+/**
+ * Collects info about the current localization state
+ */
+class LocalizationCollector extends DataCollector implements Renderable
+{
+    /**
+     * Get the current locale
+     *
+     * @return string
+     */
+    public function getLocale()
+    {
+        return setlocale(LC_ALL, 0);
+    }
+
+    /**
+     * Get the current translations domain
+     *
+     * @return string
+     */
+    public function getDomain()
+    {
+        return textdomain();
+    }
+
+    public function collect()
+    {
+        return array(
+          'locale' => $this->getLocale(),
+          'domain' => $this->getDomain(),
+        );
+    }
+
+    public function getName()
+    {
+        return 'localization';
+    }
+
+    public function getWidgets()
+    {
+        return array(
+            'domain' => array(
+                'icon' => 'bookmark',
+                'map'  => 'localization.domain',
+            ),
+            'locale' => array(
+                'icon' => 'flag',
+                'map'  => 'localization.locale',
+            )
+        );
+    }
+}

+ 63 - 0
htdocs/includes/DebugBar/DataCollector/MemoryCollector.php

@@ -0,0 +1,63 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataCollector;
+
+/**
+ * Collects info about memory usage
+ */
+class MemoryCollector extends DataCollector implements Renderable
+{
+    protected $peakUsage = 0;
+
+    /**
+     * Returns the peak memory usage
+     *
+     * @return integer
+     */
+    public function getPeakUsage()
+    {
+        return $this->peakUsage;
+    }
+
+    /**
+     * Updates the peak memory usage value
+     */
+    public function updatePeakUsage()
+    {
+        $this->peakUsage = memory_get_peak_usage(true);
+    }
+
+    public function collect()
+    {
+        $this->updatePeakUsage();
+        return array(
+            'peak_usage' => $this->peakUsage,
+            'peak_usage_str' => $this->getDataFormatter()->formatBytes($this->peakUsage)
+        );
+    }
+
+    public function getName()
+    {
+        return 'memory';
+    }
+
+    public function getWidgets()
+    {
+        return array(
+            "memory" => array(
+                "icon" => "cogs",
+                "tooltip" => "Memory Usage",
+                "map" => "memory.peak_usage_str",
+                "default" => "'0B'"
+            )
+        );
+    }
+}

+ 21 - 0
htdocs/includes/DebugBar/DataCollector/MessagesAggregateInterface.php

@@ -0,0 +1,21 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataCollector;
+
+interface MessagesAggregateInterface
+{
+    /**
+     * Returns collected messages
+     *
+     * @return array
+     */
+    public function getMessages();
+}

+ 152 - 0
htdocs/includes/DebugBar/DataCollector/MessagesCollector.php

@@ -0,0 +1,152 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataCollector;
+
+use Psr\Log\AbstractLogger;
+
+/**
+ * Provides a way to log messages
+ */
+class MessagesCollector extends AbstractLogger implements DataCollectorInterface, MessagesAggregateInterface, Renderable
+{
+    protected $name;
+
+    protected $messages = array();
+
+    protected $aggregates = array();
+
+    protected $dataFormater;
+
+    /**
+     * @param string $name
+     */
+    public function __construct($name = 'messages')
+    {
+        $this->name = $name;
+    }
+
+    /**
+     * Sets the data formater instance used by this collector
+     *
+     * @param DataFormatterInterface $formater
+     */
+    public function setDataFormatter(DataFormatterInterface $formater)
+    {
+        $this->dataFormater = $formater;
+        return $this;
+    }
+
+    public function getDataFormatter()
+    {
+        if ($this->dataFormater === null) {
+            $this->dataFormater = DataCollector::getDefaultDataFormatter();
+        }
+        return $this->dataFormater;
+    }
+
+    /**
+     * Adds a message
+     *
+     * A message can be anything from an object to a string
+     *
+     * @param mixed $message
+     * @param string $label
+     */
+    public function addMessage($message, $label = 'info', $isString = true)
+    {
+        if (!is_string($message)) {
+            $message = $this->getDataFormatter()->formatVar($message);
+            $isString = false;
+        }
+        $this->messages[] = array(
+            'message' => $message,
+            'is_string' => $isString,
+            'label' => $label,
+            'time' => microtime(true)
+        );
+    }
+
+    /**
+     * Aggregates messages from other collectors
+     *
+     * @param MessagesAggregateInterface $messages
+     */
+    public function aggregate(MessagesAggregateInterface $messages)
+    {
+        $this->aggregates[] = $messages;
+    }
+
+    public function getMessages()
+    {
+        $messages = $this->messages;
+        foreach ($this->aggregates as $collector) {
+            $msgs = array_map(function ($m) use ($collector) {
+                $m['collector'] = $collector->getName();
+                return $m;
+            }, $collector->getMessages());
+            $messages = array_merge($messages, $msgs);
+        }
+
+        // sort messages by their timestamp
+        usort($messages, function ($a, $b) {
+            if ($a['time'] === $b['time']) {
+                return 0;
+            }
+            return $a['time'] < $b['time'] ? -1 : 1;
+        });
+
+        return $messages;
+    }
+
+    public function log($level, $message, array $context = array())
+    {
+        $this->addMessage($message, $level);
+    }
+
+    /**
+     * Deletes all messages
+     */
+    public function clear()
+    {
+        $this->messages = array();
+    }
+
+    public function collect()
+    {
+        $messages = $this->getMessages();
+        return array(
+            'count' => count($messages),
+            'messages' => $messages
+        );
+    }
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function getWidgets()
+    {
+        $name = $this->getName();
+        return array(
+            "$name" => array(
+                'icon' => 'list-alt',
+                "widget" => "PhpDebugBar.Widgets.MessagesWidget",
+                "map" => "$name.messages",
+                "default" => "[]"
+            ),
+            "$name:badge" => array(
+                "map" => "$name.count",
+                "default" => "null"
+            )
+        );
+    }
+}

+ 182 - 0
htdocs/includes/DebugBar/DataCollector/PDO/PDOCollector.php

@@ -0,0 +1,182 @@
+<?php
+
+namespace DebugBar\DataCollector\PDO;
+
+use DebugBar\DataCollector\AssetProvider;
+use DebugBar\DataCollector\DataCollector;
+use DebugBar\DataCollector\Renderable;
+use DebugBar\DataCollector\TimeDataCollector;
+
+/**
+ * Collects data about SQL statements executed with PDO
+ */
+class PDOCollector extends DataCollector implements Renderable, AssetProvider
+{
+    protected $connections = array();
+
+    protected $timeCollector;
+
+    protected $renderSqlWithParams = false;
+
+    protected $sqlQuotationChar = '<>';
+
+    /**
+     * @param TraceablePDO $pdo
+     * @param TimeDataCollector $timeCollector
+     */
+    public function __construct(TraceablePDO $pdo = null, TimeDataCollector $timeCollector = null)
+    {
+        $this->timeCollector = $timeCollector;
+        if ($pdo !== null) {
+            $this->addConnection($pdo, 'default');
+        }
+    }
+
+    /**
+     * Renders the SQL of traced statements with params embeded
+     *
+     * @param boolean $enabled
+     */
+    public function setRenderSqlWithParams($enabled = true, $quotationChar = '<>')
+    {
+        $this->renderSqlWithParams = $enabled;
+        $this->sqlQuotationChar = $quotationChar;
+    }
+
+    public function isSqlRenderedWithParams()
+    {
+        return $this->renderSqlWithParams;
+    }
+
+    public function getSqlQuotationChar()
+    {
+        return $this->sqlQuotationChar;
+    }
+
+    /**
+     * Adds a new PDO instance to be collector
+     *
+     * @param TraceablePDO $pdo
+     * @param string $name Optional connection name
+     */
+    public function addConnection(TraceablePDO $pdo, $name = null)
+    {
+        if ($name === null) {
+            $name = spl_object_hash($pdo);
+        }
+        $this->connections[$name] = $pdo;
+    }
+
+    /**
+     * Returns PDO instances to be collected
+     *
+     * @return array
+     */
+    public function getConnections()
+    {
+        return $this->connections;
+    }
+
+    public function collect()
+    {
+        $data = array(
+            'nb_statements' => 0,
+            'nb_failed_statements' => 0,
+            'accumulated_duration' => 0,
+            'memory_usage' => 0,
+            'peak_memory_usage' => 0,
+            'statements' => array()
+        );
+
+        foreach ($this->connections as $name => $pdo) {
+            $pdodata = $this->collectPDO($pdo, $this->timeCollector);
+            $data['nb_statements'] += $pdodata['nb_statements'];
+            $data['nb_failed_statements'] += $pdodata['nb_failed_statements'];
+            $data['accumulated_duration'] += $pdodata['accumulated_duration'];
+            $data['memory_usage'] += $pdodata['memory_usage'];
+            $data['peak_memory_usage'] = max($data['peak_memory_usage'], $pdodata['peak_memory_usage']);
+            $data['statements'] = array_merge($data['statements'],
+                array_map(function ($s) use ($name) { $s['connection'] = $name; return $s; }, $pdodata['statements']));
+        }
+
+        $data['accumulated_duration_str'] = $this->getDataFormatter()->formatDuration($data['accumulated_duration']);
+        $data['memory_usage_str'] = $this->getDataFormatter()->formatBytes($data['memory_usage']);
+        $data['peak_memory_usage_str'] = $this->getDataFormatter()->formatBytes($data['peak_memory_usage']);
+
+        return $data;
+    }
+
+    /**
+     * Collects data from a single TraceablePDO instance
+     *
+     * @param TraceablePDO $pdo
+     * @param TimeDataCollector $timeCollector
+     * @return array
+     */
+    protected function collectPDO(TraceablePDO $pdo, TimeDataCollector $timeCollector = null)
+    {
+        $stmts = array();
+        foreach ($pdo->getExecutedStatements() as $stmt) {
+            $stmts[] = array(
+                'sql' => $this->renderSqlWithParams ? $stmt->getSqlWithParams($this->sqlQuotationChar) : $stmt->getSql(),
+                'row_count' => $stmt->getRowCount(),
+                'stmt_id' => $stmt->getPreparedId(),
+                'prepared_stmt' => $stmt->getSql(),
+                'params' => (object) $stmt->getParameters(),
+                'duration' => $stmt->getDuration(),
+                'duration_str' => $this->getDataFormatter()->formatDuration($stmt->getDuration()),
+                'memory' => $stmt->getMemoryUsage(),
+                'memory_str' => $this->getDataFormatter()->formatBytes($stmt->getMemoryUsage()),
+                'end_memory' => $stmt->getEndMemory(),
+                'end_memory_str' => $this->getDataFormatter()->formatBytes($stmt->getEndMemory()),
+                'is_success' => $stmt->isSuccess(),
+                'error_code' => $stmt->getErrorCode(),
+                'error_message' => $stmt->getErrorMessage()
+            );
+            if ($timeCollector !== null) {
+                $timeCollector->addMeasure($stmt->getSql(), $stmt->getStartTime(), $stmt->getEndTime());
+            }
+        }
+
+        return array(
+            'nb_statements' => count($stmts),
+            'nb_failed_statements' => count($pdo->getFailedExecutedStatements()),
+            'accumulated_duration' => $pdo->getAccumulatedStatementsDuration(),
+            'accumulated_duration_str' => $this->getDataFormatter()->formatDuration($pdo->getAccumulatedStatementsDuration()),
+            'memory_usage' => $pdo->getMemoryUsage(),
+            'memory_usage_str' => $this->getDataFormatter()->formatBytes($pdo->getPeakMemoryUsage()),
+            'peak_memory_usage' => $pdo->getPeakMemoryUsage(),
+            'peak_memory_usage_str' => $this->getDataFormatter()->formatBytes($pdo->getPeakMemoryUsage()),
+            'statements' => $stmts
+        );
+    }
+
+    public function getName()
+    {
+        return 'pdo';
+    }
+
+    public function getWidgets()
+    {
+        return array(
+            "database" => array(
+                "icon" => "inbox",
+                "widget" => "PhpDebugBar.Widgets.SQLQueriesWidget",
+                "map" => "pdo",
+                "default" => "[]"
+            ),
+            "database:badge" => array(
+                "map" => "pdo.nb_statements",
+                "default" => 0
+            )
+        );
+    }
+
+    public function getAssets()
+    {
+        return array(
+            'css' => 'widgets/sqlqueries/widget.css',
+            'js' => 'widgets/sqlqueries/widget.js'
+        );
+    }
+}

+ 196 - 0
htdocs/includes/DebugBar/DataCollector/PDO/TraceablePDO.php

@@ -0,0 +1,196 @@
+<?php
+
+namespace DebugBar\DataCollector\PDO;
+
+use PDO;
+use PDOException;
+
+/**
+ * A PDO proxy which traces statements
+ */
+class TraceablePDO extends PDO
+{
+    protected $pdo;
+
+    protected $executedStatements = array();
+
+    public function __construct(PDO $pdo)
+    {
+        $this->pdo = $pdo;
+        $this->pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('DebugBar\DataCollector\PDO\TraceablePDOStatement', array($this)));
+    }
+
+    public function beginTransaction()
+    {
+        return $this->pdo->beginTransaction();
+    }
+
+    public function commit()
+    {
+        return $this->pdo->commit();
+    }
+
+    public function errorCode()
+    {
+        return $this->pdo->errorCode();
+    }
+
+    public function errorInfo()
+    {
+        return $this->pdo->errorInfo();
+    }
+
+    public function exec($sql)
+    {
+        return $this->profileCall('exec', $sql, func_get_args());
+    }
+
+    public function getAttribute($attr)
+    {
+        return $this->pdo->getAttribute($attr);
+    }
+
+    public function inTransaction()
+    {
+        return $this->pdo->inTransaction();
+    }
+
+    public function lastInsertId($name = null)
+    {
+        return $this->pdo->lastInsertId($name);
+    }
+
+    public function prepare($sql, $driver_options = array())
+    {
+        return $this->pdo->prepare($sql, $driver_options);
+    }
+
+    public function query($sql)
+    {
+        return $this->profileCall('query', $sql, func_get_args());
+    }
+
+    public function quote($expr, $parameter_type = PDO::PARAM_STR)
+    {
+        return $this->pdo->quote($expr, $parameter_type);
+    }
+
+    public function rollBack()
+    {
+        return $this->pdo->rollBack();
+    }
+
+    public function setAttribute($attr, $value)
+    {
+        return $this->pdo->setAttribute($attr, $value);
+    }
+
+    /**
+     * Profiles a call to a PDO method
+     *
+     * @param string $method
+     * @param string $sql
+     * @param array $args
+     * @return mixed The result of the call
+     */
+    protected function profileCall($method, $sql, array $args)
+    {
+        $trace = new TracedStatement($sql);
+        $trace->start();
+
+        $ex = null;
+        try {
+            $result = call_user_func_array(array($this->pdo, $method), $args);
+        } catch (PDOException $e) {
+            $ex = $e;
+        }
+
+        if ($this->pdo->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_EXCEPTION && $result === false) {
+            $error = $this->pdo->errorInfo();
+            $ex = new PDOException($error[2], $error[0]);
+        }
+
+        $trace->end($ex);
+        $this->addExecutedStatement($trace);
+
+        if ($this->pdo->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION && $ex !== null) {
+            throw $ex;
+        }
+        return $result;
+    }
+
+    /**
+     * Adds an executed TracedStatement
+     *
+     * @param TracedStatement $stmt
+     */
+    public function addExecutedStatement(TracedStatement $stmt)
+    {
+        $this->executedStatements[] = $stmt;
+    }
+
+    /**
+     * Returns the accumulated execution time of statements
+     *
+     * @return int
+     */
+    public function getAccumulatedStatementsDuration()
+    {
+        return array_reduce($this->executedStatements, function ($v, $s) { return $v + $s->getDuration(); });
+    }
+
+    /**
+     * Returns the peak memory usage while performing statements
+     *
+     * @return int
+     */
+    public function getMemoryUsage()
+    {
+        return array_reduce($this->executedStatements, function ($v, $s) { return $v + $s->getMemoryUsage(); });
+    }
+
+    /**
+     * Returns the peak memory usage while performing statements
+     *
+     * @return int
+     */
+    public function getPeakMemoryUsage()
+    {
+        return array_reduce($this->executedStatements, function ($v, $s) { $m = $s->getEndMemory(); return $m > $v ? $m : $v; });
+    }
+
+    /**
+     * Returns the list of executed statements as TracedStatement objects
+     *
+     * @return array
+     */
+    public function getExecutedStatements()
+    {
+        return $this->executedStatements;
+    }
+
+    /**
+     * Returns the list of failed statements
+     *
+     * @return array
+     */
+    public function getFailedExecutedStatements()
+    {
+        return array_filter($this->executedStatements, function ($s) { return !$s->isSuccess(); });
+    }
+
+    public function __get($name)
+    {
+        return $this->pdo->$name;
+    }
+
+    public function __set($name, $value)
+    {
+        $this->pdo->$name = $value;
+    }
+
+    public function __call($name, $args)
+    {
+        return call_user_func_array(array($this->pdo, $name), $args);
+    }
+}

+ 74 - 0
htdocs/includes/DebugBar/DataCollector/PDO/TraceablePDOStatement.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace DebugBar\DataCollector\PDO;
+
+use PDO;
+use PDOException;
+use PDOStatement;
+
+/**
+ * A traceable PDO statement to use with Traceablepdo
+ */
+class TraceablePDOStatement extends PDOStatement
+{
+    protected $pdo;
+
+    protected $boundParameters = array();
+
+    protected function __construct(TraceablePDO $pdo)
+    {
+        $this->pdo = $pdo;
+    }
+
+    public function bindColumn($column, &$param, $type = null, $maxlen = null, $driverdata = null)
+    {
+        $this->boundParameters[$column] = $param;
+        $args = array_merge(array($column, &$param), array_slice(func_get_args(), 2));
+        return call_user_func_array(array("parent", 'bindColumn'), $args);
+    }
+
+    public function bindParam($param, &$var, $data_type = PDO::PARAM_STR, $length = null, $driver_options = null)
+    {
+        $this->boundParameters[$param] = $var;
+        $args = array_merge(array($param, &$var), array_slice(func_get_args(), 2));
+        return call_user_func_array(array("parent", 'bindParam'), $args);
+    }
+
+    public function bindValue($param, $value, $data_type = PDO::PARAM_STR)
+    {
+        $this->boundParameters[$param] = $value;
+        return call_user_func_array(array("parent", 'bindValue'), func_get_args());
+    }
+
+    public function execute($params = null)
+    {
+        $preparedId = spl_object_hash($this);
+        $boundParameters = $this->boundParameters;
+        if (is_array($params)) {
+            $boundParameters = array_merge($boundParameters, $params);
+        }
+
+        $trace = new TracedStatement($this->queryString, $boundParameters, $preparedId);
+        $trace->start();
+
+        $ex = null;
+        try {
+            $result = parent::execute($params);
+        } catch (PDOException $e) {
+            $ex = $e;
+        }
+
+        if ($this->pdo->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_EXCEPTION && $result === false) {
+            $error = $this->errorInfo();
+            $ex = new PDOException($error[2], $error[0]);
+        }
+
+        $trace->end($ex, $this->rowCount());
+        $this->pdo->addExecutedStatement($trace);
+
+        if ($this->pdo->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION && $ex !== null) {
+            throw $ex;
+        }
+        return $result;
+    }
+}

+ 240 - 0
htdocs/includes/DebugBar/DataCollector/PDO/TracedStatement.php

@@ -0,0 +1,240 @@
+<?php
+
+namespace DebugBar\DataCollector\PDO;
+
+/**
+ * Holds information about a statement
+ */
+class TracedStatement
+{
+    protected $sql;
+
+    protected $rowCount;
+
+    protected $parameters;
+
+    protected $startTime;
+
+    protected $endTime;
+
+    protected $duration;
+
+    protected $startMemory;
+
+    protected $endMemory;
+
+    protected $memoryDelta;
+
+    protected $exception;
+
+    /**
+     * @param string $sql
+     * @param array $params
+     * @param string $preparedId
+     * @param integer $rowCount
+     * @param integer $startTime
+     * @param integer $endTime
+     * @param integer $memoryUsage
+     * @param \Exception $e
+     */
+    public function __construct($sql, array $params = array(), $preparedId = null)
+    {
+        $this->sql = $sql;
+        $this->parameters = $this->checkParameters($params);
+        $this->preparedId = $preparedId;
+    }
+
+    public function start($startTime = null, $startMemory = null)
+    {
+        $this->startTime = $startTime ?: microtime(true);
+        $this->startMemory = $startMemory ?: memory_get_usage(true);
+    }
+
+    public function end(\Exception $exception = null, $rowCount = 0, $endTime = null, $endMemory = null)
+    {
+        $this->endTime = $endTime ?: microtime(true);
+        $this->duration = $this->endTime - $this->startTime;
+        $this->endMemory = $endMemory ?: memory_get_usage(true);
+        $this->memoryDelta = $this->endMemory - $this->startMemory;
+        $this->exception = $exception;
+        $this->rowCount = $rowCount;
+    }
+
+    /**
+     * Check parameters for illegal (non UTF-8) strings, like Binary data.
+     *
+     * @param $params
+     * @return mixed
+     */
+    public function checkParameters($params)
+    {
+        foreach ($params as &$param) {
+            if (!mb_check_encoding($param, 'UTF-8')) {
+                $param = '[BINARY DATA]';
+            }
+        }
+        return $params;
+    }
+
+    /**
+     * Returns the SQL string used for the query
+     *
+     * @return string
+     */
+    public function getSql()
+    {
+        return $this->sql;
+    }
+
+    /**
+     * Returns the SQL string with any parameters used embedded
+     *
+     * @param string $quotationChar
+     * @return string
+     */
+    public function getSqlWithParams($quotationChar = '<>')
+    {
+        if (($l = strlen($quotationChar)) > 1) {
+            $quoteLeft = substr($quotationChar, 0, $l / 2);
+            $quoteRight = substr($quotationChar, $l / 2);
+        } else {
+            $quoteLeft = $quoteRight = $quotationChar;
+        }
+
+        $sql = $this->sql;
+        foreach ($this->parameters as $k => $v) {
+            $v = "$quoteLeft$v$quoteRight";
+            if (!is_numeric($k)) {
+                $sql = str_replace($k, $v, $sql);
+            } else {
+                $p = strpos($sql, '?');
+                $sql = substr($sql, 0, $p) . $v. substr($sql, $p + 1);
+            }
+        }
+        return $sql;
+    }
+
+    /**
+     * Returns the number of rows affected/returned
+     *
+     * @return int
+     */
+    public function getRowCount()
+    {
+        return $this->rowCount;
+    }
+
+    /**
+     * Returns an array of parameters used with the query
+     *
+     * @return array
+     */
+    public function getParameters()
+    {
+        $params = array();
+        foreach ($this->parameters as $param) {
+            $params[] = htmlentities($param, ENT_QUOTES, 'UTF-8', false);
+        }
+        return $params;
+    }
+
+    /**
+     * Returns the prepared statement id
+     *
+     * @return string
+     */
+    public function getPreparedId()
+    {
+        return $this->preparedId;
+    }
+
+    /**
+     * Checks if this is a prepared statement
+     *
+     * @return boolean
+     */
+    public function isPrepared()
+    {
+        return $this->preparedId !== null;
+    }
+
+    public function getStartTime()
+    {
+        return $this->startTime;
+    }
+
+    public function getEndTime()
+    {
+        return $this->endTime;
+    }
+
+    /**
+     * Returns the duration in seconds of the execution
+     *
+     * @return int
+     */
+    public function getDuration()
+    {
+        return $this->duration;
+    }
+
+    public function getStartMemory()
+    {
+        return $this->startMemory;
+    }
+
+    public function getEndMemory()
+    {
+        return $this->endMemory;
+    }
+
+    /**
+     * Returns the memory usage during the execution
+     *
+     * @return int
+     */
+    public function getMemoryUsage()
+    {
+        return $this->memoryDelta;
+    }
+
+    /**
+     * Checks if the statement was successful
+     *
+     * @return boolean
+     */
+    public function isSuccess()
+    {
+        return $this->exception === null;
+    }
+
+    /**
+     * Returns the exception triggered
+     *
+     * @return \Exception
+     */
+    public function getException()
+    {
+        return $this->exception;
+    }
+
+    /**
+     * Returns the exception's code
+     *
+     * @return string
+     */
+    public function getErrorCode()
+    {
+        return $this->exception !== null ? $this->exception->getCode() : 0;
+    }
+
+    /**
+     * Returns the exception's message
+     *
+     * @return string
+     */
+    public function getErrorMessage()
+    {
+        return $this->exception !== null ? $this->exception->getMessage() : '';
+    }
+}

+ 51 - 0
htdocs/includes/DebugBar/DataCollector/PhpInfoCollector.php

@@ -0,0 +1,51 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataCollector;
+
+/**
+ * Collects info about PHP
+ */
+class PhpInfoCollector extends DataCollector implements Renderable
+{
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return 'php';
+    }
+
+    /**
+     * @return array
+     */
+    public function collect()
+    {
+        return array(
+            'version' => PHP_VERSION,
+            'interface' => PHP_SAPI
+        );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getWidgets()
+    {
+        return array(
+            "php_version" => array(
+                "icon" => "code",
+                "tooltip" => "Version",
+                "map" => "php.version",
+                "default" => ""
+            ),
+        );
+    }
+}

+ 25 - 0
htdocs/includes/DebugBar/DataCollector/Renderable.php

@@ -0,0 +1,25 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataCollector;
+
+/**
+ * Indicates that a DataCollector is renderable using JavascriptRenderer
+ */
+interface Renderable
+{
+    /**
+     * Returns a hash where keys are control names and their values
+     * an array of options as defined in {@see DebugBar\JavascriptRenderer::addControl()}
+     *
+     * @return array
+     */
+    function getWidgets();
+}

+ 48 - 0
htdocs/includes/DebugBar/DataCollector/RequestDataCollector.php

@@ -0,0 +1,48 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataCollector;
+
+/**
+ * Collects info about the current request
+ */
+class RequestDataCollector extends DataCollector implements Renderable
+{
+    public function collect()
+    {
+        $vars = array('_GET', '_POST', '_SESSION', '_COOKIE', '_SERVER');
+        $data = array();
+
+        foreach ($vars as $var) {
+            if (isset($GLOBALS[$var])) {
+                $data["$" . $var] = $this->getDataFormatter()->formatVar($GLOBALS[$var]);
+            }
+        }
+
+        return $data;
+    }
+
+    public function getName()
+    {
+        return 'request';
+    }
+
+    public function getWidgets()
+    {
+        return array(
+            "request" => array(
+                "icon" => "tags",
+                "widget" => "PhpDebugBar.Widgets.VariableListWidget",
+                "map" => "request",
+                "default" => "{}"
+            )
+        );
+    }
+}

+ 228 - 0
htdocs/includes/DebugBar/DataCollector/TimeDataCollector.php

@@ -0,0 +1,228 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataCollector;
+
+use DebugBar\DebugBarException;
+
+/**
+ * Collects info about the request duration as well as providing
+ * a way to log duration of any operations
+ */
+class TimeDataCollector extends DataCollector implements Renderable
+{
+    /**
+     * @var float
+     */
+    protected $requestStartTime;
+
+    /**
+     * @var float
+     */
+    protected $requestEndTime;
+
+    /**
+     * @var array
+     */
+    protected $startedMeasures = array();
+
+    /**
+     * @var array
+     */
+    protected $measures = array();
+
+    /**
+     * @param float $requestStartTime
+     */
+    public function __construct($requestStartTime = null)
+    {
+        if ($requestStartTime === null) {
+            if (isset($_SERVER['REQUEST_TIME_FLOAT'])) {
+                $requestStartTime = $_SERVER['REQUEST_TIME_FLOAT'];
+            } else {
+                $requestStartTime = microtime(true);
+            }
+        }
+        $this->requestStartTime = $requestStartTime;
+    }
+
+    /**
+     * Starts a measure
+     *
+     * @param string $name Internal name, used to stop the measure
+     * @param string|null $label Public name
+     * @param string|null $collector The source of the collector
+     */
+    public function startMeasure($name, $label = null, $collector = null)
+    {
+        $start = microtime(true);
+        $this->startedMeasures[$name] = array(
+            'label' => $label ?: $name,
+            'start' => $start,
+            'collector' => $collector
+        );
+    }
+
+    /**
+     * Check a measure exists
+     *
+     * @param string $name
+     * @return bool
+     */
+    public function hasStartedMeasure($name)
+    {
+        return isset($this->startedMeasures[$name]);
+    }
+
+    /**
+     * Stops a measure
+     *
+     * @param string $name
+     * @param array $params
+     * @throws DebugBarException
+     */
+    public function stopMeasure($name, $params = array())
+    {
+        $end = microtime(true);
+        if (!$this->hasStartedMeasure($name)) {
+            throw new DebugBarException("Failed stopping measure '$name' because it hasn't been started");
+        }
+        $this->addMeasure(
+            $this->startedMeasures[$name]['label'],
+            $this->startedMeasures[$name]['start'],
+            $end,
+            $params,
+            $this->startedMeasures[$name]['collector']
+        );
+        unset($this->startedMeasures[$name]);
+    }
+
+    /**
+     * Adds a measure
+     *
+     * @param string $label
+     * @param float $start
+     * @param float $end
+     * @param array $params
+     * @param string|null $collector
+     */
+    public function addMeasure($label, $start, $end, $params = array(), $collector = null)
+    {
+        $this->measures[] = array(
+            'label' => $label,
+            'start' => $start,
+            'relative_start' => $start - $this->requestStartTime,
+            'end' => $end,
+            'relative_end' => $end - $this->requestEndTime,
+            'duration' => $end - $start,
+            'duration_str' => $this->getDataFormatter()->formatDuration($end - $start),
+            'params' => $params,
+            'collector' => $collector
+        );
+    }
+
+    /**
+     * Utility function to measure the execution of a Closure
+     *
+     * @param string $label
+     * @param \Closure $closure
+     * @param string|null $collector
+     */
+    public function measure($label, \Closure $closure, $collector = null)
+    {
+        $name = spl_object_hash($closure);
+        $this->startMeasure($name, $label, $collector);
+        $result = $closure();
+        $params = is_array($result) ? $result : array();
+        $this->stopMeasure($name, $params);
+    }
+
+    /**
+     * Returns an array of all measures
+     *
+     * @return array
+     */
+    public function getMeasures()
+    {
+        return $this->measures;
+    }
+
+    /**
+     * Returns the request start time
+     *
+     * @return float
+     */
+    public function getRequestStartTime()
+    {
+        return $this->requestStartTime;
+    }
+
+    /**
+     * Returns the request end time
+     *
+     * @return float
+     */
+    public function getRequestEndTime()
+    {
+        return $this->requestEndTime;
+    }
+
+    /**
+     * Returns the duration of a request
+     *
+     * @return float
+     */
+    public function getRequestDuration()
+    {
+        if ($this->requestEndTime !== null) {
+            return $this->requestEndTime - $this->requestStartTime;
+        }
+        return microtime(true) - $this->requestStartTime;
+    }
+
+    public function collect()
+    {
+        $this->requestEndTime = microtime(true);
+        foreach (array_keys($this->startedMeasures) as $name) {
+            $this->stopMeasure($name);
+        }
+
+        return array(
+            'start' => $this->requestStartTime,
+            'end' => $this->requestEndTime,
+            'duration' => $this->getRequestDuration(),
+            'duration_str' => $this->getDataFormatter()->formatDuration($this->getRequestDuration()),
+            'measures' => array_values($this->measures)
+        );
+    }
+
+    public function getName()
+    {
+        return 'time';
+    }
+
+    public function getWidgets()
+    {
+        return array(
+            "time" => array(
+                "icon" => "clock-o",
+                "tooltip" => "Request Duration",
+                "map" => "time.duration_str",
+                "default" => "'0ms'"
+            ),
+            "timeline" => array(
+                "icon" => "tasks",
+                "widget" => "PhpDebugBar.Widgets.TimelineWidget",
+                "map" => "time",
+                "default" => "{}"
+            )
+        );
+    }
+}

+ 169 - 0
htdocs/includes/DebugBar/DataFormatter/DataFormatter.php

@@ -0,0 +1,169 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataFormatter;
+
+class DataFormatter implements DataFormatterInterface
+{
+    public function formatVar($data)
+    {
+        return $this->kintLite($data);
+    }
+
+    public function formatDuration($seconds)
+    {
+        if ($seconds < 0.001) {
+            return round($seconds * 1000000) . 'μs';
+        } elseif ($seconds < 1) {
+            return round($seconds * 1000, 2) . 'ms';
+        }
+        return round($seconds, 2) . 's';
+    }
+
+    public function formatBytes($size, $precision = 2)
+    {
+        if ($size === 0 || $size === null) {
+            return "0B";
+        }
+        $base = log($size) / log(1024);
+        $suffixes = array('B', 'KB', 'MB', 'GB', 'TB');
+        return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
+    }
+
+     /**
+     * lightweight version of Kint::dump(). Uses whitespace for formatting instead of html
+     * sadly not DRY yet
+     *
+     * Extracted from Kint.class.php in raveren/kint, https://github.com/raveren/kint
+     * Copyright (c) 2013 Rokas Šleinius (raveren@gmail.com)
+     *
+     * @param mixed $var
+     * @param int $level
+     *
+     * @return string
+     */
+    protected function kintLite(&$var, $level = 0)
+    {
+        // initialize function names into variables for prettier string output (html and implode are also DRY)
+        $html     = "htmlspecialchars";
+        $implode  = "implode";
+        $strlen   = "strlen";
+        $count    = "count";
+        $getClass = "get_class";
+
+        if ( $var === null ) {
+            return 'NULL';
+        } elseif ( is_bool( $var ) ) {
+            return 'bool ' . ( $var ? 'TRUE' : 'FALSE' );
+        } elseif ( is_float( $var ) ) {
+            return 'float ' . $var;
+        } elseif ( is_int( $var ) ) {
+            return 'integer ' . $var;
+        } elseif ( is_resource( $var ) ) {
+            if ( ( $type = get_resource_type( $var ) ) === 'stream' and $meta = stream_get_meta_data( $var ) ) {
+                if ( isset( $meta['uri'] ) ) {
+                    $file = $meta['uri'];
+
+                    return "resource ({$type}) {$html( $file, 0 )}";
+                } else {
+                    return "resource ({$type})";
+                }
+            } else {
+                return "resource ({$type})";
+            }
+        } elseif ( is_string( $var ) ) {
+            return "string ({$strlen( $var )}) \"{$html( $var )}\"";
+        } elseif ( is_array( $var ) ) {
+            $output = array();
+            $space  = str_repeat( $s = '    ', $level );
+
+            static $marker;
+
+            if ( $marker === null ) {
+                // Make a unique marker
+                $marker = uniqid( "\x00" );
+            }
+
+            if ( empty( $var ) ) {
+                return "array()";
+            } elseif ( isset( $var[$marker] ) ) {
+                $output[] = "[\n$space$s*RECURSION*\n$space]";
+            } elseif ( $level < 7 ) {
+                $isSeq = array_keys( $var ) === range( 0, count( $var ) - 1 );
+
+                $output[] = "[";
+
+                $var[$marker] = true;
+
+                foreach ( $var as $key => &$val ) {
+                    if ( $key === $marker ) {
+                        continue;
+                    }
+
+                    $key = $space . $s . ( $isSeq ? "" : "'{$html( $key, 0 )}' => " );
+
+                    $dump     = $this->kintLite( $val, $level + 1 );
+                    $output[] = "{$key}{$dump}";
+                }
+
+                unset( $var[$marker] );
+                $output[] = "$space]";
+            } else {
+                $output[] = "[\n$space$s*depth too great*\n$space]";
+            }
+            return "array({$count( $var )}) {$implode( "\n", $output )}";
+        } elseif ( is_object( $var ) ) {
+            if ( $var instanceof SplFileInfo ) {
+                return "object SplFileInfo " . $var->getRealPath();
+            }
+
+            // Copy the object as an array
+            $array = (array) $var;
+
+            $output = array();
+            $space  = str_repeat( $s = '    ', $level );
+
+            $hash = spl_object_hash( $var );
+
+            // Objects that are being dumped
+            static $objects = array();
+
+            if ( empty( $array ) ) {
+                return "object {$getClass( $var )} {}";
+            } elseif ( isset( $objects[$hash] ) ) {
+                $output[] = "{\n$space$s*RECURSION*\n$space}";
+            } elseif ( $level < 7 ) {
+                $output[]       = "{";
+                $objects[$hash] = true;
+
+                foreach ( $array as $key => & $val ) {
+                    if ( $key[0] === "\x00" ) {
+                        $access = $key[1] === "*" ? "protected" : "private";
+
+                        // Remove the access level from the variable name
+                        $key = substr( $key, strrpos( $key, "\x00" ) + 1 );
+                    } else {
+                        $access = "public";
+                    }
+
+                    $output[] = "$space$s$access $key -> " . $this->kintLite( $val, $level + 1 );
+                }
+                unset( $objects[$hash] );
+                $output[] = "$space}";
+            } else {
+                $output[] = "{\n$space$s*depth too great*\n$space}";
+            }
+
+            return "object {$getClass( $var )} ({$count( $array )}) {$implode( "\n", $output )}";
+        } else {
+            return gettype( $var ) . htmlspecialchars( var_export( $var, true ), ENT_NOQUOTES );
+        }
+    }
+}

+ 42 - 0
htdocs/includes/DebugBar/DataFormatter/DataFormatterInterface.php

@@ -0,0 +1,42 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\DataFormatter;
+
+/**
+ * Formats data to be outputed as string
+ */
+interface DataFormatterInterface
+{
+    /**
+     * Transforms a PHP variable to a string representation
+     *
+     * @param mixed $var
+     * @return string
+     */
+    function formatVar($data);
+
+    /**
+     * Transforms a duration in seconds in a readable string
+     *
+     * @param float $seconds
+     * @return string
+     */
+    function formatDuration($seconds);
+
+    /**
+     * Transforms a size in bytes to a human readable string
+     *
+     * @param string $size
+     * @param integer $precision
+     * @return string
+     */
+    function formatBytes($size, $precision = 2);
+}

+ 468 - 0
htdocs/includes/DebugBar/DebugBar.php

@@ -0,0 +1,468 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar;
+
+use ArrayAccess;
+use DebugBar\DataCollector\DataCollectorInterface;
+use DebugBar\Storage\StorageInterface;
+
+/**
+ * Main DebugBar object
+ *
+ * Manages data collectors. DebugBar provides an array-like access
+ * to collectors by name.
+ *
+ * <code>
+ *     $debugbar = new DebugBar();
+ *     $debugbar->addCollector(new DataCollector\MessagesCollector());
+ *     $debugbar['messages']->addMessage("foobar");
+ * </code>
+ */
+class DebugBar implements ArrayAccess
+{
+    public static $useOpenHandlerWhenSendingDataHeaders = false;
+
+    protected $collectors = array();
+
+    protected $data;
+
+    protected $jsRenderer;
+
+    protected $requestIdGenerator;
+
+    protected $requestId;
+
+    protected $storage;
+
+    protected $httpDriver;
+
+    protected $stackSessionNamespace = 'PHPDEBUGBAR_STACK_DATA';
+
+    protected $stackAlwaysUseSessionStorage = false;
+
+    /**
+     * Adds a data collector
+     *
+     * @param DataCollectorInterface $collector
+     *
+     * @throws DebugBarException
+     * @return $this
+     */
+    public function addCollector(DataCollectorInterface $collector)
+    {
+        if ($collector->getName() === '__meta') {
+            throw new DebugBarException("'__meta' is a reserved name and cannot be used as a collector name");
+        }
+        if (isset($this->collectors[$collector->getName()])) {
+            throw new DebugBarException("'{$collector->getName()}' is already a registered collector");
+        }
+        $this->collectors[$collector->getName()] = $collector;
+        return $this;
+    }
+
+    /**
+     * Checks if a data collector has been added
+     *
+     * @param string $name
+     * @return boolean
+     */
+    public function hasCollector($name)
+    {
+        return isset($this->collectors[$name]);
+    }
+
+    /**
+     * Returns a data collector
+     *
+     * @param string $name
+     * @return DataCollectorInterface
+     */
+    public function getCollector($name)
+    {
+        if (!isset($this->collectors[$name])) {
+            throw new DebugBarException("'$name' is not a registered collector");
+        }
+        return $this->collectors[$name];
+    }
+
+    /**
+     * Returns an array of all data collectors
+     *
+     * @return array[DataCollectorInterface]
+     */
+    public function getCollectors()
+    {
+        return $this->collectors;
+    }
+
+    /**
+     * Sets the request id generator
+     *
+     * @param RequestIdGeneratorInterface $generator
+     */
+    public function setRequestIdGenerator(RequestIdGeneratorInterface $generator)
+    {
+        $this->requestIdGenerator = $generator;
+        return $this;
+    }
+
+    /**
+     * @return RequestIdGeneratorInterface
+     */
+    public function getRequestIdGenerator()
+    {
+        if ($this->requestIdGenerator === null) {
+            $this->requestIdGenerator = new RequestIdGenerator();
+        }
+        return $this->requestIdGenerator;
+    }
+
+    /**
+     * Returns the id of the current request
+     *
+     * @return string
+     */
+    public function getCurrentRequestId()
+    {
+        if ($this->requestId === null) {
+            $this->requestId = $this->getRequestIdGenerator()->generate();
+        }
+        return $this->requestId;
+    }
+
+    /**
+     * Sets the storage backend to use to store the collected data
+     *
+     * @param StorageInterface $storage
+     */
+    public function setStorage(StorageInterface $storage = null)
+    {
+        $this->storage = $storage;
+        return $this;
+    }
+
+    /**
+     * @return StorageInterface
+     */
+    public function getStorage()
+    {
+        return $this->storage;
+    }
+
+    /**
+     * Checks if the data will be persisted
+     *
+     * @return boolean
+     */
+    public function isDataPersisted()
+    {
+        return $this->storage !== null;
+    }
+
+    /**
+     * Sets the HTTP driver
+     *
+     * @param HttpDriverInterface $driver
+     */
+    public function setHttpDriver(HttpDriverInterface $driver)
+    {
+        $this->httpDriver = $driver;
+        return $this;
+    }
+
+    /**
+     * Returns the HTTP driver
+     *
+     * If no http driver where defined, a PhpHttpDriver is automatically created
+     *
+     * @return HttpDriverInterface
+     */
+    public function getHttpDriver()
+    {
+        if ($this->httpDriver === null) {
+            $this->httpDriver = new PhpHttpDriver();
+        }
+        return $this->httpDriver;
+    }
+
+    /**
+     * Collects the data from the collectors
+     *
+     * @return array
+     */
+    public function collect()
+    {
+        $this->data = array(
+            '__meta' => array(
+                'id' => $this->getCurrentRequestId(),
+                'datetime' => date('Y-m-d H:i:s'),
+                'utime' => microtime(true),
+                'method' => isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : null,
+                'uri' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : null,
+                'ip' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null
+            )
+        );
+
+        foreach ($this->collectors as $name => $collector) {
+            $this->data[$name] = $collector->collect();
+        }
+
+        // Remove all invalid (non UTF-8) characters
+        array_walk_recursive($this->data, function (&$item) {
+                if (is_string($item) && !mb_check_encoding($item, 'UTF-8')) {
+                    $item = mb_convert_encoding($item, 'UTF-8', 'UTF-8');
+                }
+            });
+
+        if ($this->storage !== null) {
+            $this->storage->save($this->getCurrentRequestId(), $this->data);
+        }
+
+        return $this->data;
+    }
+
+    /**
+     * Returns collected data
+     *
+     * Will collect the data if none have been collected yet
+     *
+     * @return array
+     */
+    public function getData()
+    {
+        if ($this->data === null) {
+            $this->collect();
+        }
+        return $this->data;
+    }
+
+    /**
+     * Returns an array of HTTP headers containing the data
+     *
+     * @param string $headerName
+     * @param integer $maxHeaderLength
+     * @return array
+     */
+    public function getDataAsHeaders($headerName = 'phpdebugbar', $maxHeaderLength = 4096, $maxTotalHeaderLength = 250000)
+    {
+        $data = rawurlencode(json_encode(array(
+            'id' => $this->getCurrentRequestId(),
+            'data' => $this->getData()
+        )));
+
+        if (strlen($data) > $maxTotalHeaderLength) {
+            $data = rawurlencode(json_encode(array(
+                'error' => 'Maximum header size exceeded'
+            )));
+        }
+
+        $chunks = array();
+
+        while (strlen($data) > $maxHeaderLength) {
+            $chunks[] = substr($data, 0, $maxHeaderLength);
+            $data = substr($data, $maxHeaderLength);
+        }
+        $chunks[] = $data;
+
+        $headers = array();
+        for ($i = 0, $c = count($chunks); $i < $c; $i++) {
+            $name = $headerName . ($i > 0 ? "-$i" : '');
+            $headers[$name] = $chunks[$i];
+        }
+
+        return $headers;
+    }
+
+    /**
+     * Sends the data through the HTTP headers
+     *
+     * @param bool $useOpenHandler
+     * @param string $headerName
+     * @param integer $maxHeaderLength
+     */
+    public function sendDataInHeaders($useOpenHandler = null, $headerName = 'phpdebugbar', $maxHeaderLength = 4096)
+    {
+        if ($useOpenHandler === null) {
+            $useOpenHandler = self::$useOpenHandlerWhenSendingDataHeaders;
+        }
+        if ($useOpenHandler && $this->storage !== null) {
+            $this->getData();
+            $headerName .= '-id';
+            $headers = array($headerName => $this->getCurrentRequestId());
+        } else {
+            $headers = $this->getDataAsHeaders($headerName, $maxHeaderLength);
+        }
+        $this->getHttpDriver()->setHeaders($headers);
+        return $this;
+    }
+
+    /**
+     * Stacks the data in the session for later rendering
+     */
+    public function stackData()
+    {
+        $http = $this->initStackSession();
+
+        $data = null;
+        if (!$this->isDataPersisted() || $this->stackAlwaysUseSessionStorage) {
+            $data = $this->getData();
+        } elseif ($this->data === null) {
+            $this->collect();
+        }
+
+        $stack = $http->getSessionValue($this->stackSessionNamespace);
+        $stack[$this->getCurrentRequestId()] = $data;
+        $http->setSessionValue($this->stackSessionNamespace, $stack);
+        return $this;
+    }
+
+    /**
+     * Checks if there is stacked data in the session
+     *
+     * @return boolean
+     */
+    public function hasStackedData()
+    {
+        try {
+            $http = $this->initStackSession();
+        } catch (DebugBarException $e) {
+            return false;
+        }
+        return count($http->getSessionValue($this->stackSessionNamespace)) > 0;
+    }
+
+    /**
+     * Returns the data stacked in the session
+     *
+     * @param boolean $delete Whether to delete the data in the session
+     * @return array
+     */
+    public function getStackedData($delete = true)
+    {
+        $http = $this->initStackSession();
+        $stackedData = $http->getSessionValue($this->stackSessionNamespace);
+        if ($delete) {
+            $http->deleteSessionValue($this->stackSessionNamespace);
+        }
+
+        $datasets = array();
+        if ($this->isDataPersisted() && !$this->stackAlwaysUseSessionStorage) {
+            foreach ($stackedData as $id => $data) {
+                $datasets[$id] = $this->getStorage()->get($id);
+            }
+        } else {
+            $datasets = $stackedData;
+        }
+
+        return $datasets;
+    }
+
+    /**
+     * Sets the key to use in the $_SESSION array
+     *
+     * @param string $ns
+     */
+    public function setStackDataSessionNamespace($ns)
+    {
+        $this->stackSessionNamespace = $ns;
+        return $this;
+    }
+
+    /**
+     * Returns the key used in the $_SESSION array
+     *
+     * @return string
+     */
+    public function getStackDataSessionNamespace()
+    {
+        return $this->stackSessionNamespace;
+    }
+
+    /**
+     * Sets whether to only use the session to store stacked data even
+     * if a storage is enabled
+     *
+     * @param boolean $enabled
+     */
+    public function setStackAlwaysUseSessionStorage($enabled = true)
+    {
+        $this->stackAlwaysUseSessionStorage = $enabled;
+        return $this;
+    }
+
+    /**
+     * Checks if the session is always used to store stacked data
+     * even if a storage is enabled
+     *
+     * @return boolean
+     */
+    public function isStackAlwaysUseSessionStorage()
+    {
+        return $this->stackAlwaysUseSessionStorage;
+    }
+
+    /**
+     * Initializes the session for stacked data
+     *
+     * @return HttpDriverInterface
+     */
+    protected function initStackSession()
+    {
+        $http = $this->getHttpDriver();
+        if (!$http->isSessionStarted()) {
+            throw new DebugBarException("Session must be started before using stack data in the debug bar");
+        }
+
+        if (!$http->hasSessionValue($this->stackSessionNamespace)) {
+            $http->setSessionValue($this->stackSessionNamespace, array());
+        }
+
+        return $http;
+    }
+
+    /**
+     * Returns a JavascriptRenderer for this instance
+     *
+     * @param string $baseUrl
+     * @param string $basePathng
+     * @return JavascriptRenderer
+     */
+    public function getJavascriptRenderer($baseUrl = null, $basePath = null)
+    {
+        if ($this->jsRenderer === null) {
+            $this->jsRenderer = new JavascriptRenderer($this, $baseUrl, $basePath);
+        }
+        return $this->jsRenderer;
+    }
+
+    // --------------------------------------------
+    // ArrayAccess implementation
+
+    public function offsetSet($key, $value)
+    {
+        throw new DebugBarException("DebugBar[] is read-only");
+    }
+
+    public function offsetGet($key)
+    {
+        return $this->getCollector($key);
+    }
+
+    public function offsetExists($key)
+    {
+        return $this->hasCollector($key);
+    }
+
+    public function offsetUnset($key)
+    {
+        throw new DebugBarException("DebugBar[] is read-only");
+    }
+}

+ 16 - 0
htdocs/includes/DebugBar/DebugBarException.php

@@ -0,0 +1,16 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar;
+
+class DebugBarException extends \Exception
+{
+    //
+}

+ 63 - 0
htdocs/includes/DebugBar/HttpDriverInterface.php

@@ -0,0 +1,63 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar;
+
+/**
+ * Provides an abstraction of PHP native features for easier integration
+ * in third party frameworks
+ */
+interface HttpDriverInterface
+{
+    /**
+     * Sets HTTP headers
+     *
+     * @param string $headers
+     */
+    function setHeaders(array $headers);
+
+    /**
+     * Checks if the session is started
+     *
+     * @return boolean
+     */
+    function isSessionStarted();
+
+    /**
+     * Sets a value in the session
+     *
+     * @param string $name
+     * @param string $value
+     */
+    function setSessionValue($name, $value);
+
+    /**
+     * Checks if a value is in the session
+     *
+     * @param string $name
+     * @return boolean
+     */
+    function hasSessionValue($name);
+
+    /**
+     * Returns a value from the session
+     *
+     * @param string $name
+     * @return mixed
+     */
+    function getSessionValue($name);
+
+    /**
+     * Deletes a value from the session
+     *
+     * @param string $name
+     */
+    function deleteSessionValue($name);
+}

+ 960 - 0
htdocs/includes/DebugBar/JavascriptRenderer.php

@@ -0,0 +1,960 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar;
+
+use DebugBar\DataCollector\AssetProvider;
+use DebugBar\DataCollector\Renderable;
+
+/**
+ * Renders the debug bar using the client side javascript implementation
+ *
+ * Generates all the needed initialization code of controls
+ */
+class JavascriptRenderer
+{
+    const INITIALIZE_CONSTRUCTOR = 2;
+
+    const INITIALIZE_CONTROLS = 4;
+
+    const REPLACEABLE_TAG = "{--DEBUGBAR_OB_START_REPLACE_ME--}";
+
+    const RELATIVE_PATH = 'path';
+
+    const RELATIVE_URL = 'url';
+
+    protected $debugBar;
+
+    protected $baseUrl;
+
+    protected $basePath;
+
+    protected $cssVendors = array(
+        'fontawesome' => 'vendor/font-awesome/css/font-awesome.min.css',
+        'highlightjs' => 'vendor/highlightjs/styles/github.css'
+    );
+
+    protected $jsVendors = array(
+        'jquery' => 'vendor/jquery/dist/jquery.min.js',
+        'highlightjs' => 'vendor/highlightjs/highlight.pack.js'
+    );
+
+    protected $includeVendors = true;
+
+    protected $cssFiles = array('debugbar.css', 'widgets.css', 'openhandler.css');
+
+    protected $jsFiles = array('debugbar.js', 'widgets.js', 'openhandler.js');
+
+    protected $additionalAssets = array();
+
+    protected $javascriptClass = 'PhpDebugBar.DebugBar';
+
+    protected $variableName = 'phpdebugbar';
+
+    protected $enableJqueryNoConflict = true;
+
+    protected $initialization;
+
+    protected $controls = array();
+
+    protected $ignoredCollectors = array();
+
+    protected $ajaxHandlerClass = 'PhpDebugBar.AjaxHandler';
+
+    protected $ajaxHandlerBindToJquery = true;
+
+    protected $ajaxHandlerBindToXHR = false;
+
+    protected $openHandlerClass = 'PhpDebugBar.OpenHandler';
+
+    protected $openHandlerUrl;
+
+    /**
+     * @param \DebugBar\DebugBar $debugBar
+     * @param string $baseUrl
+     * @param string $basePath
+     */
+    public function __construct(DebugBar $debugBar, $baseUrl = null, $basePath = null)
+    {
+        $this->debugBar = $debugBar;
+
+        if ($baseUrl === null) {
+            $baseUrl = '/vendor/maximebf/debugbar/src/DebugBar/Resources';
+        }
+        $this->baseUrl = $baseUrl;
+
+        if ($basePath === null) {
+            $basePath = __DIR__ . DIRECTORY_SEPARATOR . 'Resources';
+        }
+        $this->basePath = $basePath;
+
+        // bitwise operations cannot be done in class definition :(
+        $this->initialization = self::INITIALIZE_CONSTRUCTOR | self::INITIALIZE_CONTROLS;
+    }
+
+    /**
+     * Sets options from an array
+     *
+     * Options:
+     *  - base_path
+     *  - base_url
+     *  - include_vendors
+     *  - javascript_class
+     *  - variable_name
+     *  - initialization
+     *  - enable_jquery_noconflict
+     *  - controls
+     *  - disable_controls
+     *  - ignore_collectors
+     *  - ajax_handler_classname
+     *  - ajax_handler_bind_to_jquery
+     *  - open_handler_classname
+     *  - open_handler_url
+     *
+     * @param array $options [description]
+     */
+    public function setOptions(array $options)
+    {
+        if (array_key_exists('base_path', $options)) {
+            $this->setBasePath($options['base_path']);
+        }
+        if (array_key_exists('base_url', $options)) {
+            $this->setBaseUrl($options['base_url']);
+        }
+        if (array_key_exists('include_vendors', $options)) {
+            $this->setIncludeVendors($options['include_vendors']);
+        }
+        if (array_key_exists('javascript_class', $options)) {
+            $this->setJavascriptClass($options['javascript_class']);
+        }
+        if (array_key_exists('variable_name', $options)) {
+            $this->setVariableName($options['variable_name']);
+        }
+        if (array_key_exists('initialization', $options)) {
+            $this->setInitialization($options['initialization']);
+        }
+        if (array_key_exists('enable_jquery_noconflict', $options)) {
+            $this->setEnableJqueryNoConflict($options['enable_jquery_noconflict']);
+        }
+        if (array_key_exists('controls', $options)) {
+            foreach ($options['controls'] as $name => $control) {
+                $this->addControl($name, $control);
+            }
+        }
+        if (array_key_exists('disable_controls', $options)) {
+            foreach ((array) $options['disable_controls'] as $name) {
+                $this->disableControl($name);
+            }
+        }
+        if (array_key_exists('ignore_collectors', $options)) {
+            foreach ((array) $options['ignore_collectors'] as $name) {
+                $this->ignoreCollector($name);
+            }
+        }
+        if (array_key_exists('ajax_handler_classname', $options)) {
+            $this->setAjaxHandlerClass($options['ajax_handler_classname']);
+        }
+        if (array_key_exists('ajax_handler_bind_to_jquery', $options)) {
+            $this->setBindAjaxHandlerToJquery($options['ajax_handler_bind_to_jquery']);
+        }
+        if (array_key_exists('open_handler_classname', $options)) {
+            $this->setOpenHandlerClass($options['open_handler_classname']);
+        }
+        if (array_key_exists('open_handler_url', $options)) {
+            $this->setOpenHandlerUrl($options['open_handler_url']);
+        }
+    }
+
+    /**
+     * Sets the path which assets are relative to
+     *
+     * @param string $path
+     */
+    public function setBasePath($path)
+    {
+        $this->basePath = $path;
+        return $this;
+    }
+
+    /**
+     * Returns the path which assets are relative to
+     *
+     * @return string
+     */
+    public function getBasePath()
+    {
+        return $this->basePath;
+    }
+
+    /**
+     * Sets the base URL from which assets will be served
+     *
+     * @param string $url
+     */
+    public function setBaseUrl($url)
+    {
+        $this->baseUrl = $url;
+        return $this;
+    }
+
+    /**
+     * Returns the base URL from which assets will be served
+     *
+     * @return string
+     */
+    public function getBaseUrl()
+    {
+        return $this->baseUrl;
+    }
+
+    /**
+     * Whether to include vendor assets
+     *
+     * You can only include js or css vendors using
+     * setIncludeVendors('css') or setIncludeVendors('js')
+     *
+     * @param boolean $enabled
+     */
+    public function setIncludeVendors($enabled = true)
+    {
+        if (is_string($enabled)) {
+            $enabled = array($enabled);
+        }
+        $this->includeVendors = $enabled;
+
+        if (!$enabled || (is_array($enabled) && !in_array('js', $enabled))) {
+            // no need to call jQuery.noConflict() if we do not include our own version
+            $this->enableJqueryNoConflict = false;
+        }
+
+        return $this;
+    }
+
+    /**
+     * Checks if vendors assets are included
+     *
+     * @return boolean
+     */
+    public function areVendorsIncluded()
+    {
+        return $this->includeVendors !== false;
+    }
+
+    /**
+     * Disable a specific vendor's assets.
+     *
+     * @param  string $name "jquery", "fontawesome", "highlightjs"
+     *
+     * @return void
+     */
+    public function disableVendor($name)
+    {
+        if (array_key_exists($name, $this->cssVendors)) {
+            unset($this->cssVendors[$name]);
+        }
+        if (array_key_exists($name, $this->jsVendors)) {
+            unset($this->jsVendors[$name]);
+        }
+    }
+
+    /**
+     * Sets the javascript class name
+     *
+     * @param string $className
+     */
+    public function setJavascriptClass($className)
+    {
+        $this->javascriptClass = $className;
+        return $this;
+    }
+
+    /**
+     * Returns the javascript class name
+     *
+     * @return string
+     */
+    public function getJavascriptClass()
+    {
+        return $this->javascriptClass;
+    }
+
+    /**
+     * Sets the variable name of the class instance
+     *
+     * @param string $name
+     */
+    public function setVariableName($name)
+    {
+        $this->variableName = $name;
+        return $this;
+    }
+
+    /**
+     * Returns the variable name of the class instance
+     *
+     * @return string
+     */
+    public function getVariableName()
+    {
+        return $this->variableName;
+    }
+
+    /**
+     * Sets what should be initialized
+     *
+     *  - INITIALIZE_CONSTRUCTOR: only initializes the instance
+     *  - INITIALIZE_CONTROLS: initializes the controls and data mapping
+     *  - INITIALIZE_CONSTRUCTOR | INITIALIZE_CONTROLS: initialize everything (default)
+     *
+     * @param integer $init
+     */
+    public function setInitialization($init)
+    {
+        $this->initialization = $init;
+        return $this;
+    }
+
+    /**
+     * Returns what should be initialized
+     *
+     * @return integer
+     */
+    public function getInitialization()
+    {
+        return $this->initialization;
+    }
+
+    /**
+     * Sets whether to call jQuery.noConflict()
+     *
+     * @param boolean $enabled
+     */
+    public function setEnableJqueryNoConflict($enabled = true)
+    {
+        $this->enableJqueryNoConflict = $enabled;
+        return $this;
+    }
+
+    /**
+     * Checks if jQuery.noConflict() will be called
+     *
+     * @return boolean
+     */
+    public function isJqueryNoConflictEnabled()
+    {
+        return $this->enableJqueryNoConflict;
+    }
+
+    /**
+     * Adds a control to initialize
+     *
+     * Possible options:
+     *  - icon: icon name
+     *  - tooltip: string
+     *  - widget: widget class name
+     *  - title: tab title
+     *  - map: a property name from the data to map the control to
+     *  - default: a js string, default value of the data map
+     *
+     * "icon" or "widget" are at least needed
+     *
+     * @param string $name
+     * @param array $options
+     */
+    public function addControl($name, array $options)
+    {
+        if (count(array_intersect(array_keys($options), array('icon', 'widget', 'tab', 'indicator'))) === 0) {
+            throw new DebugBarException("Not enough options for control '$name'");
+        }
+        $this->controls[$name] = $options;
+        return $this;
+    }
+
+    /**
+     * Disables a control
+     *
+     * @param string $name
+     */
+    public function disableControl($name)
+    {
+        $this->controls[$name] = null;
+        return $this;
+    }
+
+    /**
+     * Returns the list of controls
+     *
+     * This does not include controls provided by collectors
+     *
+     * @return array
+     */
+    public function getControls()
+    {
+        return $this->controls;
+    }
+
+    /**
+     * Ignores widgets provided by a collector
+     *
+     * @param string $name
+     */
+    public function ignoreCollector($name)
+    {
+        $this->ignoredCollectors[] = $name;
+        return $this;
+    }
+
+    /**
+     * Returns the list of ignored collectors
+     *
+     * @return array
+     */
+    public function getIgnoredCollectors()
+    {
+        return $this->ignoredCollectors;
+    }
+
+    /**
+     * Sets the class name of the ajax handler
+     *
+     * Set to false to disable
+     *
+     * @param string $className
+     */
+    public function setAjaxHandlerClass($className)
+    {
+        $this->ajaxHandlerClass = $className;
+        return $this;
+    }
+
+    /**
+     * Returns the class name of the ajax handler
+     *
+     * @return string
+     */
+    public function getAjaxHandlerClass()
+    {
+        return $this->ajaxHandlerClass;
+    }
+
+    /**
+     * Sets whether to call bindToJquery() on the ajax handler
+     *
+     * @param boolean $bind
+     */
+    public function setBindAjaxHandlerToJquery($bind = true)
+    {
+        $this->ajaxHandlerBindToJquery = $bind;
+        return $this;
+    }
+
+    /**
+     * Checks whether bindToJquery() will be called on the ajax handler
+     *
+     * @return boolean
+     */
+    public function isAjaxHandlerBoundToJquery()
+    {
+        return $this->ajaxHandlerBindToJquery;
+    }
+
+    /**
+     * Sets whether to call bindToXHR() on the ajax handler
+     *
+     * @param boolean $bind
+     */
+    public function setBindAjaxHandlerToXHR($bind = true)
+    {
+        $this->ajaxHandlerBindToXHR = $bind;
+        return $this;
+    }
+
+    /**
+     * Checks whether bindToXHR() will be called on the ajax handler
+     *
+     * @return boolean
+     */
+    public function isAjaxHandlerBoundToXHR()
+    {
+        return $this->ajaxHandlerBindToXHR;
+    }
+
+    /**
+     * Sets the class name of the js open handler
+     *
+     * @param string $className
+     */
+    public function setOpenHandlerClass($className)
+    {
+        $this->openHandlerClass = $className;
+        return $this;
+    }
+
+    /**
+     * Returns the class name of the js open handler
+     *
+     * @return string
+     */
+    public function getOpenHandlerClass()
+    {
+        return $this->openHandlerClass;
+    }
+
+    /**
+     * Sets the url of the open handler
+     *
+     * @param string $url
+     */
+    public function setOpenHandlerUrl($url)
+    {
+        $this->openHandlerUrl = $url;
+        return $this;
+    }
+
+    /**
+     * Returns the url for the open handler
+     *
+     * @return string
+     */
+    public function getOpenHandlerUrl()
+    {
+        return $this->openHandlerUrl;
+    }
+
+    /**
+     * Add assets to render in the head
+     *
+     * @param array $cssFiles An array of filenames
+     * @param array $jsFiles  An array of filenames
+     * @param string $basePath Base path of those files
+     * @param string $baseUrl  Base url of those files
+     */
+    public function addAssets($cssFiles, $jsFiles, $basePath = null, $baseUrl = null)
+    {
+        $this->additionalAssets[] = array(
+            'base_path' => $basePath,
+            'base_url' => $baseUrl,
+            'css' => (array) $cssFiles,
+            'js' => (array) $jsFiles
+        );
+        return $this;
+    }
+
+    /**
+     * Returns the list of asset files
+     *
+     * @param string $type Only return css or js files
+     * @param string $relativeTo The type of path to which filenames must be relative (path, url or null)
+     * @return array
+     */
+    public function getAssets($type = null, $relativeTo = self::RELATIVE_PATH)
+    {
+        $cssFiles = $this->cssFiles;
+        $jsFiles = $this->jsFiles;
+
+        if ($this->includeVendors !== false) {
+            if ($this->includeVendors === true || in_array('css', $this->includeVendors)) {
+                $cssFiles = array_merge($this->cssVendors, $cssFiles);
+            }
+            if ($this->includeVendors === true || in_array('js', $this->includeVendors)) {
+                $jsFiles = array_merge($this->jsVendors, $jsFiles);
+            }
+        }
+
+        if ($relativeTo) {
+            $root = $this->getRelativeRoot($relativeTo, $this->basePath, $this->baseUrl);
+            $cssFiles = $this->makeUriRelativeTo($cssFiles, $root);
+            $jsFiles = $this->makeUriRelativeTo($jsFiles, $root);
+        }
+
+        $additionalAssets = $this->additionalAssets;
+        // finds assets provided by collectors
+        foreach ($this->debugBar->getCollectors() as $collector) {
+            if (($collector instanceof AssetProvider) && !in_array($collector->getName(), $this->ignoredCollectors)) {
+                $additionalAssets[] = $collector->getAssets();
+            }
+        }
+
+        foreach ($additionalAssets as $assets) {
+            $basePath = isset($assets['base_path']) ? $assets['base_path'] : null;
+            $baseUrl = isset($assets['base_url']) ? $assets['base_url'] : null;
+            $root = $this->getRelativeRoot($relativeTo,
+                $this->makeUriRelativeTo($basePath, $this->basePath),
+                $this->makeUriRelativeTo($baseUrl, $this->baseUrl));
+            $cssFiles = array_merge($cssFiles, $this->makeUriRelativeTo((array) $assets['css'], $root));
+            $jsFiles = array_merge($jsFiles, $this->makeUriRelativeTo((array) $assets['js'], $root));
+        }
+
+        return $this->filterAssetArray(array($cssFiles, $jsFiles), $type);
+    }
+
+    /**
+     * Returns the correct base according to the type
+     *
+     * @param string $relativeTo
+     * @param string $basePath
+     * @param string $baseUrl
+     * @return string
+     */
+    protected function getRelativeRoot($relativeTo, $basePath, $baseUrl)
+    {
+        if ($relativeTo === self::RELATIVE_PATH) {
+            return $basePath;
+        }
+        if ($relativeTo === self::RELATIVE_URL) {
+            return $baseUrl;
+        }
+        return null;
+    }
+
+    /**
+     * Makes a URI relative to another
+     *
+     * @param string|array $uri
+     * @param string $root
+     * @return string
+     */
+    protected function makeUriRelativeTo($uri, $root)
+    {
+        if (!$root) {
+            return $uri;
+        }
+
+        if (is_array($uri)) {
+            $uris = array();
+            foreach ($uri as $u) {
+                $uris[] = $this->makeUriRelativeTo($u, $root);
+            }
+            return $uris;
+        }
+
+        if (substr($uri, 0, 1) === '/' || preg_match('/^([a-zA-Z]+:\/\/|[a-zA-Z]:\/|[a-zA-Z]:\\\)/', $uri)) {
+            return $uri;
+        }
+        return rtrim($root, '/') . "/$uri";
+    }
+
+    /**
+     * Filters a tuple of (css, js) assets according to $type
+     *
+     * @param array $array
+     * @param string $type 'css', 'js' or null for both
+     * @return array
+     */
+    protected function filterAssetArray($array, $type = null)
+    {
+        $type = strtolower($type);
+        if ($type === 'css') {
+            return $array[0];
+        }
+        if ($type === 'js') {
+            return $array[1];
+        }
+        return $array;
+    }
+
+    /**
+     * Returns a tuple where the both items are Assetic AssetCollection,
+     * the first one being css files and the second js files
+     *
+     * @param string $type Only return css or js collection
+     * @return array or \Assetic\Asset\AssetCollection
+     */
+    public function getAsseticCollection($type = null)
+    {
+        list($cssFiles, $jsFiles) = $this->getAssets();
+        return $this->filterAssetArray(array(
+            $this->createAsseticCollection($cssFiles),
+            $this->createAsseticCollection($jsFiles)
+        ), $type);
+    }
+
+    /**
+     * Create an Assetic AssetCollection with the given files.
+     * Filenames will be converted to absolute path using
+     * the base path.
+     *
+     * @param array $files
+     * @return \Assetic\Asset\AssetCollection
+     */
+    protected function createAsseticCollection($files)
+    {
+        $assets = array();
+        foreach ($files as $file) {
+            $assets[] = new \Assetic\Asset\FileAsset($file);
+        }
+        return new \Assetic\Asset\AssetCollection($assets);
+    }
+
+    /**
+     * Write all CSS assets to standard output or in a file
+     *
+     * @param string $targetFilename
+     */
+    public function dumpCssAssets($targetFilename = null)
+    {
+        $this->dumpAssets($this->getAssets('css'), $targetFilename);
+    }
+
+    /**
+     * Write all JS assets to standard output or in a file
+     *
+     * @param string $targetFilename
+     */
+    public function dumpJsAssets($targetFilename = null)
+    {
+        $this->dumpAssets($this->getAssets('js'), $targetFilename);
+    }
+
+    /**
+     * Write assets to standard output or in a file
+     *
+     * @param array $files
+     * @param string $targetFilename
+     */
+    protected function dumpAssets($files, $targetFilename = null)
+    {
+        $content = '';
+        foreach ($files as $file) {
+            $content .= file_get_contents($file) . "\n";
+        }
+        if ($targetFilename !== null) {
+            file_put_contents($targetFilename, $content);
+        } else {
+            echo $content;
+        }
+    }
+
+    /**
+     * Renders the html to include needed assets
+     *
+     * Only useful if Assetic is not used
+     *
+     * @return string
+     */
+    public function renderHead()
+    {
+        list($cssFiles, $jsFiles) = $this->getAssets(null, self::RELATIVE_URL);
+        $html = '';
+
+        foreach ($cssFiles as $file) {
+            $html .= sprintf('<link rel="stylesheet" type="text/css" href="%s">' . "\n", $file);
+        }
+
+        foreach ($jsFiles as $file) {
+            $html .= sprintf('<script type="text/javascript" src="%s"></script>' . "\n", $file);
+        }
+
+        if ($this->enableJqueryNoConflict) {
+            $html .= '<script type="text/javascript">jQuery.noConflict(true);</script>' . "\n";
+        }
+
+        return $html;
+    }
+
+    /**
+     * Register shutdown to display the debug bar
+     *
+     * @param boolean $here Set position of HTML. True if is to current position or false for end file
+     * @param boolean $initialize Whether to render the de bug bar initialization code
+     * @return string Return "{--DEBUGBAR_OB_START_REPLACE_ME--}" or return an empty string if $here == false
+     */
+    public function renderOnShutdown($here = true, $initialize = true, $renderStackedData = true, $head = false)
+    {
+        register_shutdown_function(array($this, "replaceTagInBuffer"), $here, $initialize, $renderStackedData, $head);
+
+        if (ob_get_level() === 0) {
+            ob_start();
+        }
+
+        return ($here) ? self::REPLACEABLE_TAG : "";
+    }
+
+    /**
+     * Same as renderOnShutdown() with $head = true
+     *
+     * @param boolean $here
+     * @param boolean $initialize
+     * @param boolean $renderStackedData
+     * @return string
+     */
+    public function renderOnShutdownWithHead($here = true, $initialize = true, $renderStackedData = true)
+    {
+        return $this->renderOnShutdown($here, $initialize, $renderStackedData, true);
+    }
+
+    /**
+     * Is callback function for register_shutdown_function(...)
+     *
+     * @param boolean $here Set position of HTML. True if is to current position or false for end file
+     * @param boolean $initialize Whether to render the de bug bar initialization code
+     */
+    public function replaceTagInBuffer($here = true, $initialize = true, $renderStackedData = true, $head = false)
+    {
+        $render = ($head ? $this->renderHead() : "")
+                . $this->render($initialize, $renderStackedData);
+
+        $current = ($here && ob_get_level() > 0) ? ob_get_clean() : self::REPLACEABLE_TAG;
+
+        echo str_replace(self::REPLACEABLE_TAG, $render, $current, $count);
+
+        if ($count === 0) {
+            echo $render;
+        }
+    }
+
+    /**
+     * Returns the code needed to display the debug bar
+     *
+     * AJAX request should not render the initialization code.
+     *
+     * @param boolean $initialize Whether to render the de bug bar initialization code
+     * @return string
+     */
+    public function render($initialize = true, $renderStackedData = true)
+    {
+        $js = '';
+
+        if ($initialize) {
+            $js = $this->getJsInitializationCode();
+        }
+
+        if ($renderStackedData && $this->debugBar->hasStackedData()) {
+            foreach ($this->debugBar->getStackedData() as $id => $data) {
+                $js .= $this->getAddDatasetCode($id, $data, '(stacked)');
+            }
+        }
+
+        $suffix = !$initialize ? '(ajax)' : null;
+        $js .= $this->getAddDatasetCode($this->debugBar->getCurrentRequestId(), $this->debugBar->getData(), $suffix);
+
+        return "<script type=\"text/javascript\">\n$js\n</script>\n";
+    }
+
+    /**
+     * Returns the js code needed to initialize the debug bar
+     *
+     * @return string
+     */
+    protected function getJsInitializationCode()
+    {
+        $js = '';
+
+        if (($this->initialization & self::INITIALIZE_CONSTRUCTOR) === self::INITIALIZE_CONSTRUCTOR) {
+            $js .= sprintf("var %s = new %s();\n", $this->variableName, $this->javascriptClass);
+        }
+
+        if (($this->initialization & self::INITIALIZE_CONTROLS) === self::INITIALIZE_CONTROLS) {
+            $js .= $this->getJsControlsDefinitionCode($this->variableName);
+        }
+
+        if ($this->ajaxHandlerClass) {
+            $js .= sprintf("%s.ajaxHandler = new %s(%s);\n", $this->variableName, $this->ajaxHandlerClass, $this->variableName);
+            if ($this->ajaxHandlerBindToXHR) {
+                $js .= sprintf("%s.ajaxHandler.bindToXHR();\n", $this->variableName);
+            } elseif ($this->ajaxHandlerBindToJquery) {
+                $js .= sprintf("if (jQuery) %s.ajaxHandler.bindToJquery(jQuery);\n", $this->variableName);
+            }
+        }
+
+        if ($this->openHandlerUrl !== null) {
+            $js .= sprintf("%s.setOpenHandler(new %s(%s));\n", $this->variableName,
+                $this->openHandlerClass,
+                json_encode(array("url" => $this->openHandlerUrl)));
+        }
+
+        return $js;
+    }
+
+    /**
+     * Returns the js code needed to initialized the controls and data mapping of the debug bar
+     *
+     * Controls can be defined by collectors themselves or using {@see addControl()}
+     *
+     * @param string $varname Debug bar's variable name
+     * @return string
+     */
+    protected function getJsControlsDefinitionCode($varname)
+    {
+        $js = '';
+        $dataMap = array();
+        $excludedOptions = array('indicator', 'tab', 'map', 'default', 'widget', 'position');
+
+        // finds controls provided by collectors
+        $widgets = array();
+        foreach ($this->debugBar->getCollectors() as $collector) {
+            if (($collector instanceof Renderable) && !in_array($collector->getName(), $this->ignoredCollectors)) {
+                if ($w = $collector->getWidgets()) {
+                    $widgets = array_merge($widgets, $w);
+                }
+            }
+        }
+        $controls = array_merge($widgets, $this->controls);
+
+        foreach (array_filter($controls) as $name => $options) {
+            $opts = array_diff_key($options, array_flip($excludedOptions));
+
+            if (isset($options['tab']) || isset($options['widget'])) {
+                if (!isset($opts['title'])) {
+                    $opts['title'] = ucfirst(str_replace('_', ' ', $name));
+                }
+                $js .= sprintf("%s.addTab(\"%s\", new %s({%s%s}));\n",
+                    $varname,
+                    $name,
+                    isset($options['tab']) ? $options['tab'] : 'PhpDebugBar.DebugBar.Tab',
+                    substr(json_encode($opts, JSON_FORCE_OBJECT), 1, -1),
+                    isset($options['widget']) ? sprintf('%s"widget": new %s()', count($opts) ? ', ' : '', $options['widget']) : ''
+                );
+            } elseif (isset($options['indicator']) || isset($options['icon'])) {
+                $js .= sprintf("%s.addIndicator(\"%s\", new %s(%s), \"%s\");\n",
+                    $varname,
+                    $name,
+                    isset($options['indicator']) ? $options['indicator'] : 'PhpDebugBar.DebugBar.Indicator',
+                    json_encode($opts, JSON_FORCE_OBJECT),
+                    isset($options['position']) ? $options['position'] : 'right'
+                );
+            }
+
+            if (isset($options['map']) && isset($options['default'])) {
+                $dataMap[$name] = array($options['map'], $options['default']);
+            }
+        }
+
+        // creates the data mapping object
+        $mapJson = array();
+        foreach ($dataMap as $name => $values) {
+            $mapJson[] = sprintf('"%s": ["%s", %s]', $name, $values[0], $values[1]);
+        }
+        $js .= sprintf("%s.setDataMap({\n%s\n});\n", $varname, implode(",\n", $mapJson));
+
+        // activate state restoration
+        $js .= sprintf("%s.restoreState();\n", $varname);
+
+        return $js;
+    }
+
+    /**
+     * Returns the js code needed to add a dataset
+     *
+     * @param string $requestId
+     * @param array $data
+     * @return string
+     */
+    protected function getAddDatasetCode($requestId, $data, $suffix = null)
+    {
+        $js = sprintf("%s.addDataSet(%s, \"%s\"%s);\n",
+            $this->variableName,
+            json_encode($data),
+            $requestId,
+            $suffix ? ", " . json_encode($suffix) : ''
+        );
+        return $js;
+    }
+}

+ 107 - 0
htdocs/includes/DebugBar/OpenHandler.php

@@ -0,0 +1,107 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar;
+
+/**
+ * Handler to list and open saved dataset
+ */
+class OpenHandler
+{
+    protected $debugBar;
+
+    /**
+     * @param DebugBar $debugBar
+     */
+    public function __construct(DebugBar $debugBar)
+    {
+        if (!$debugBar->isDataPersisted()) {
+            throw new DebugBarException("DebugBar must have a storage backend to use OpenHandler");
+        }
+        $this->debugBar = $debugBar;
+    }
+
+    /**
+     * Handles the current request
+     *
+     * @param array $request Request data
+     */
+    public function handle($request = null, $echo = true, $sendHeader = true)
+    {
+        if ($request === null) {
+            $request = $_REQUEST;
+        }
+
+        $op = 'find';
+        if (isset($request['op'])) {
+            $op = $request['op'];
+            if (!in_array($op, array('find', 'get', 'clear'))) {
+                throw new DebugBarException("Invalid operation '{$request['op']}'");
+            }
+        }
+
+        if ($sendHeader) {
+            $this->debugBar->getHttpDriver()->setHeaders(array(
+                    'Content-Type' => 'application/json'
+                ));
+        }
+
+        $response = json_encode(call_user_func(array($this, $op), $request));
+        if ($echo) {
+            echo $response;
+        }
+        return $response;
+    }
+
+    /**
+     * Find operation
+     */
+    protected function find($request)
+    {
+        $max = 20;
+        if (isset($request['max'])) {
+            $max = $request['max'];
+        }
+
+        $offset = 0;
+        if (isset($request['offset'])) {
+            $offset = $request['offset'];
+        }
+
+        $filters = array();
+        foreach (array('utime', 'datetime', 'ip', 'uri', 'method') as $key) {
+            if (isset($request[$key])) {
+                $filters[$key] = $request[$key];
+            }
+        }
+
+        return $this->debugBar->getStorage()->find($filters, $max, $offset);
+    }
+
+    /**
+     * Get operation
+     */
+    protected function get($request)
+    {
+        if (!isset($request['id'])) {
+            throw new DebugBarException("Missing 'id' parameter in 'get' operation");
+        }
+        return $this->debugBar->getStorage()->get($request['id']);
+    }
+
+    /**
+     * Clear operation
+     */
+    protected function clear($request)
+    {
+        $this->debugBar->getStorage()->clear();
+        return array('success' => true);
+    }
+}

+ 49 - 0
htdocs/includes/DebugBar/PhpHttpDriver.php

@@ -0,0 +1,49 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar;
+
+/**
+ * HTTP driver for native php
+ */
+class PhpHttpDriver implements HttpDriverInterface
+{
+    function setHeaders(array $headers)
+    {
+        foreach ($headers as $name => $value) {
+            header("$name: $value");
+        }
+    }
+
+    function isSessionStarted()
+    {
+        return isset($_SESSION);
+    }
+
+    function setSessionValue($name, $value)
+    {
+        $_SESSION[$name] = $value;
+    }
+
+    function hasSessionValue($name)
+    {
+        return array_key_exists($name, $_SESSION);
+    }
+
+    function getSessionValue($name)
+    {
+        return $_SESSION[$name];
+    }
+
+    function deleteSessionValue($name)
+    {
+        unset($_SESSION[$name]);
+    }
+}

+ 22 - 0
htdocs/includes/DebugBar/RequestIdGenerator.php

@@ -0,0 +1,22 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar;
+
+/**
+ * Request id generator based on the $_SERVER array
+ */
+class RequestIdGenerator implements RequestIdGeneratorInterface
+{
+    public function generate()
+    {
+        return md5(serialize($_SERVER) . microtime());
+    }
+}

+ 21 - 0
htdocs/includes/DebugBar/RequestIdGeneratorInterface.php

@@ -0,0 +1,21 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar;
+
+interface RequestIdGeneratorInterface
+{
+    /**
+     * Generates a unique id for the current request
+     *
+     * @return string
+     */
+    function generate();
+}

+ 289 - 0
htdocs/includes/DebugBar/Resources/debugbar.css

@@ -0,0 +1,289 @@
+/* Hide debugbar when printing a page */
+@media print {
+  div.phpdebugbar {
+    display: none;
+  }
+}
+
+div.phpdebugbar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  border-top: 0;
+  font-family: arial, sans-serif;
+  background: #fff;
+  z-index: 10000;
+  font-size: 14px;
+  color: #000;
+  text-align: left;
+}
+
+div.phpdebugbar a {
+  cursor: pointer;
+}
+
+div.phpdebugbar-drag-capture {
+  position: fixed;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: 10001;
+  background: none;
+  display: none;
+  cursor: n-resize;
+}
+
+div.phpdebugbar-closed {
+    width: auto;
+}
+
+div.phpdebugbar * {
+
+    -moz-box-sizing: content-box;
+         box-sizing: content-box;
+}
+
+div.phpdebugbar code, div.phpdebugbar pre {
+    background: none;
+    font-family: monospace;
+    font-size: 1em;
+    border: 0;
+    padding: 0;
+    margin: 0;
+}
+
+a.phpdebugbar-restore-btn {
+  float: left;
+  padding: 10px 8px;
+  font-size: 14px;
+  color: #555;
+  text-decoration: none;
+  border-right: 1px solid #ddd;
+}
+
+div.phpdebugbar-resize-handle {
+  display: none;
+  height: 4px;
+  margin-top: -4px;
+  width: 100%;
+  background: none;
+  border-bottom: 1px solid #ccc;
+  cursor: n-resize;
+}
+
+div.phpdebugbar-closed, div.phpdebugbar-minimized{
+  border-top: 1px solid #ccc;
+}
+/* -------------------------------------- */
+
+div.phpdebugbar-header, a.phpdebugbar-restore-btn {
+    background: #efefef url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADsAAAA7CAMAAADy+wKBAAABelBMVEUaSnYbS3ccTHgdTXkeTnofT3sgUHwhUX0iUn4jU38kVIAlVYEmVoInV4MoWIQpWYUqWoYrW4csXIgtXYkuXoovWoQvX4swW4UwX4wxYI0yYY4zYo80Y5A1ZJE2Yow2ZZI3ZpM4ZI04Z5Q5aJU6Zo86aZY7apc8a5g9aJI9bJk+bZo/bptAaI5Da5JFcZtGcptHcJZKcplPdJhPdZlbfZ5bgKRcfp9cgaVdfp9df6BegKFfgaJggqNghalhg6RihKVjhaZkhqdlh6dlh6hmiKlniapoiapoiqtpi6xqiadqjK1rja5sjq9tj7Bwjqx9mbOCnriCnrmDn7mEoLuFobyGoryHobmIobmJorqKo7uLpLyRqsKUrcWVrsaXq8GcscaftMqous2pu82pu863xtW3xta8zNvDz9vE0NzF0d3F0d7G0t7G09/H09/I1ODT2+TT3OXU3ebW3+fX4Onh6O3j6e/j6u/j6vDv8/bw8/bw8/fx9Pfx9Pj////KUJTtAAABt0lEQVR4AaXNZ1PVQBSH8WORWy5YROwIgr2IBbuigF2wKwj2HpXYS+R8eIcXnjzZTHLj7u/Vzn/OMyt7/ckef0Htbn9B7U5/Qe0Of0Htdn8ySEOTrqHBYtl2UvO+vXl8tqAdILb069GBgbzyFlBb208TWuznRL9D+ui6lrnfl5Vvq8fSS9e03L1eks3Urp0/zusqLcSZdhNd0Xbu4lo20mU1P2YXPIvns+1XXBe2sW2x0lG0G+gS2nS9w79n013W00U1c5hvo8Ve0sIcYrRr6QJa7rfQ7rJV1tA4Wu6H0I7bKt00puZjN6EdszGoXU2jaj5w34Z21FZZRefROrs5aGu19inadJWVdA4t5q1/1HxJZ1lBaN9jfq2pl+ksy2lETWTjllcKN9Lrtu3Ii4RpgmvpojNou25GUfRdHc9xLZ10Gm3nDBKzH9f/2c7wWlqE9l3rYT793CJp0im0zXybHGlSpRYpSYNOom1Mu+nhRpbU6QTautN+2ld3VG2T6XqO1OiYmre1KTW/p9bV8qSDhtF2PPj3enIVJ4DW1TO8oKf4QJb5k6X+gtol/oLaxf6C2kX+gtoAfwHyDK0lVxrWYgAAAABJRU5ErkJggg==) no-repeat 10px 8px / 20px 20px;
+}
+div.phpdebugbar-header {
+  padding-left: 40px;
+  min-height: 26px;
+  line-height: 16px;
+}
+div.phpdebugbar-header:before, div.phpdebugbar-header:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+div.phpdebugbar-header:after {
+  clear: both;
+}
+div.phpdebugbar-header-left {
+  float: left;
+}
+div.phpdebugbar-header-right {
+  float: right;
+}
+div.phpdebugbar-header > div > * {
+  padding: 10px 10px;
+  font-size: 14px;
+  color: #555;
+  text-decoration: none;
+  outline: none;
+}
+div.phpdebugbar-header-left > * {
+  float: left;
+}
+div.phpdebugbar-header-right > * {
+  float: right;
+}
+div.phpdebugbar-header-right > select {
+  padding: 0;
+}
+
+/* -------------------------------------- */
+
+span.phpdebugbar-indicator,
+a.phpdebugbar-indicator,
+a.phpdebugbar-close-btn {
+  border-right: 1px solid #ddd;
+}
+
+a.phpdebugbar-tab.phpdebugbar-active {
+  background: #3d6c99;
+  color: #fff;
+}
+
+a.phpdebugbar-tab.phpdebugbar-active span.phpdebugbar-badge {
+  background: #fff;
+  color: #3d6c99;
+}
+
+  a.phpdebugbar-tab span.phpdebugbar-badge {
+    display: none;
+    margin-left: 5px;
+    float: right;
+    font-size: 11px;
+    line-height: 14px;
+    padding: 1px 7px;
+    background: #3d6c99;
+    border-radius: 4px;
+    color: #fff;
+    font-weight: normal;
+    text-shadow: none;
+    vertical-align: middle;
+  }
+  a.phpdebugbar-tab i {
+    display: none;
+    vertical-align: middle;
+  }
+  a.phpdebugbar-tab span.phpdebugbar-badge.phpdebugbar-important {
+    background: #ed6868;
+    color: white;
+  }
+
+a.phpdebugbar-close-btn, a.phpdebugbar-open-btn, a.phpdebugbar-minimize-btn , a.phpdebugbar-maximize-btn {
+  width: 16px;
+  height: 16px;
+}
+
+a.phpdebugbar-restore-btn {
+  width: 26px;
+  height: 16px;
+}
+
+a.phpdebugbar-minimize-btn , a.phpdebugbar-maximize-btn {
+  padding-right: 0px !important;
+}
+
+a.phpdebugbar-maximize-btn { display: none}
+
+a.phpdebugbar-minimize-btn { display: block}
+
+div.phpdebugbar-minimized a.phpdebugbar-maximize-btn { display: block}
+
+div.phpdebugbar-minimized a.phpdebugbar-minimize-btn { display: none}
+
+a.phpdebugbar-minimize-btn {
+  background: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201792%201792%22%20id%3D%22chevron-down%22%3E%3Cpath%20d%3D%22M1683%20808l-742%20741q-19%2019-45%2019t-45-19l-742-741q-19-19-19-45.5t19-45.5l166-165q19-19%2045-19t45%2019l531%20531%20531-531q19-19%2045-19t45%2019l166%20165q19%2019%2019%2045.5t-19%2045.5z%22%2F%3E%3C%2Fsvg%3E) no-repeat 10px 10px / 14px 14px;
+}
+
+a.phpdebugbar-maximize-btn {
+  background: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201792%201792%22%20id%3D%22chevron-up%22%3E%3Cpath%20d%3D%22M1683%201331l-166%20165q-19%2019-45%2019t-45-19l-531-531-531%20531q-19%2019-45%2019t-45-19l-166-165q-19-19-19-45.5t19-45.5l742-741q19-19%2045-19t45%2019l742%20741q19%2019%2019%2045.5t-19%2045.5z%22%2F%3E%3C%2Fsvg%3E) no-repeat 10px 10px / 14px 14px;
+}
+
+a.phpdebugbar-close-btn {
+  background: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201792%201792%22%20id%3D%22close%22%3E%3Cpath%20d%3D%22M1490%201322q0%2040-28%2068l-136%20136q-28%2028-68%2028t-68-28l-294-294-294%20294q-28%2028-68%2028t-68-28l-136-136q-28-28-28-68t28-68l294-294-294-294q-28-28-28-68t28-68l136-136q28-28%2068-28t68%2028l294%20294%20294-294q28-28%2068-28t68%2028l136%20136q28%2028%2028%2068t-28%2068l-294%20294%20294%20294q28%2028%2028%2068z%22%2F%3E%3C%2Fsvg%3E) no-repeat 10px 10px / 14px 14px;
+}
+
+a.phpdebugbar-open-btn {
+  background: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201792%201792%22%20id%3D%22folder-open%22%3E%3Cpath%20d%3D%22M1815%20952q0%2031-31%2066l-336%20396q-43%2051-120.5%2086.5t-143.5%2035.5h-1088q-34%200-60.5-13t-26.5-43q0-31%2031-66l336-396q43-51%20120.5-86.5t143.5-35.5h1088q34%200%2060.5%2013t26.5%2043zm-343-344v160h-832q-94%200-197%2047.5t-164%20119.5l-337%20396-5%206q0-4-.5-12.5t-.5-12.5v-960q0-92%2066-158t158-66h320q92%200%20158%2066t66%20158v32h544q92%200%20158%2066t66%20158z%22%2F%3E%3C%2Fsvg%3E) no-repeat 10px 10px / 14px 14px;
+}
+
+.phpdebugbar-indicator {
+  position: relative;
+  cursor: pointer;
+}
+  .phpdebugbar-indicator span.phpdebugbar-text {
+    margin-left: 5px;
+  }
+  .phpdebugbar-indicator span.phpdebugbar-tooltip {
+    display: none;
+    position: absolute;
+    bottom: 45px;
+    background: #efefef;
+    border: 1px solid #ccc;
+    color: #555;
+    font-size: 11px;
+    padding: 2px 3px;
+    z-index: 1000;
+    text-align: center;
+    width: 200%;
+    right: 0;
+  }
+  .phpdebugbar-indicator:hover span.phpdebugbar-tooltip:not(.phpdebugbar-disabled) {
+    display: block;
+  }
+  .phpdebugbar-indicator span.phpdebugbar-tooltip-wide {
+    width: 300%;
+  }
+  .phpdebugbar-indicator span.phpdebugbar-tooltip-extra-wide {
+    width: 400%;
+  }
+  .phpdebugbar-indicator span.phpdebugbar-tooltip-html {
+    display: inline-block;
+    text-align: left;
+  }
+
+select.phpdebugbar-datasets-switcher {
+  float: right;
+  display: none;
+  margin: 8px 0 0 7px;
+  max-width: 200px;
+  max-height: 23px;
+  padding: 0;
+}
+
+/* -------------------------------------- */
+
+div.phpdebugbar-body {
+  border-top: 1px solid #ccc;
+  display: none;
+  position: relative;
+  height: 300px;
+}
+
+/* -------------------------------------- */
+
+div.phpdebugbar-panel {
+  display: none;
+  height: 100%;
+  overflow: auto;
+  width: 100%;
+}
+div.phpdebugbar-panel.phpdebugbar-active {
+  display: block;
+}
+
+/* -------------------------------------- */
+
+div.phpdebugbar-mini-design a.phpdebugbar-tab {
+  position: relative;
+  border-right: 1px solid #ddd;
+}
+  div.phpdebugbar-mini-design a.phpdebugbar-tab span.phpdebugbar-text {
+    display: none;
+  }
+  div.phpdebugbar-mini-design a.phpdebugbar-tab:hover span.phpdebugbar-text {
+    display: block;
+    position: absolute;
+    top: -30px;
+    background: #efefef;
+    opacity: .7;
+    border: 1px solid #ccc;
+    color: #555;
+    font-size: 11px;
+    padding: 2px 3px;
+    z-index: 1000;
+    text-align: center;
+    right: 0;
+  }
+  div.phpdebugbar-mini-design a.phpdebugbar-tab i {
+    display:inline-block;
+  }

+ 1161 - 0
htdocs/includes/DebugBar/Resources/debugbar.js

@@ -0,0 +1,1161 @@
+if (typeof(PhpDebugBar) == 'undefined') {
+    // namespace
+    var PhpDebugBar = {};
+    PhpDebugBar.$ = jQuery;
+}
+
+(function($) {
+
+    if (typeof(localStorage) == 'undefined') {
+        // provide mock localStorage object for dumb browsers
+        localStorage = {
+            setItem: function(key, value) {},
+            getItem: function(key) { return null; }
+        };
+    }
+
+    if (typeof(PhpDebugBar.utils) == 'undefined') {
+        PhpDebugBar.utils = {};
+    }
+
+    /**
+     * Returns the value from an object property.
+     * Using dots in the key, it is possible to retrieve nested property values
+     *
+     * @param {Object} dict
+     * @param {String} key
+     * @param {Object} default_value
+     * @return {Object}
+     */
+    var getDictValue = PhpDebugBar.utils.getDictValue = function(dict, key, default_value) {
+        var d = dict, parts = key.split('.');
+        for (var i = 0; i < parts.length; i++) {
+            if (!d[parts[i]]) {
+                return default_value;
+            }
+            d = d[parts[i]];
+        }
+        return d;
+    }
+
+    /**
+     * Counts the number of properties in an object
+     *
+     * @param {Object} obj
+     * @return {Integer}
+     */
+    var getObjectSize = PhpDebugBar.utils.getObjectSize = function(obj) {
+        if (Object.keys) {
+            return Object.keys(obj).length;
+        }
+        var count = 0;
+        for (var k in obj) {
+            if (obj.hasOwnProperty(k)) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Returns a prefixed css class name
+     *
+     * @param {String} cls
+     * @return {String}
+     */
+    PhpDebugBar.utils.csscls = function(cls, prefix) {
+        if (cls.indexOf(' ') > -1) {
+            var clss = cls.split(' '), out = [];
+            for (var i = 0, c = clss.length; i < c; i++) {
+                out.push(PhpDebugBar.utils.csscls(clss[i], prefix));
+            }
+            return out.join(' ');
+        }
+        if (cls.indexOf('.') === 0) {
+            return '.' + prefix + cls.substr(1);
+        }
+        return prefix + cls;
+    };
+
+    /**
+     * Creates a partial function of csscls where the second
+     * argument is already defined
+     *
+     * @param  {string} prefix
+     * @return {Function}
+     */
+    PhpDebugBar.utils.makecsscls = function(prefix) {
+        var f = function(cls) {
+            return PhpDebugBar.utils.csscls(cls, prefix);
+        };
+        return f;
+    }
+
+    var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-');
+
+
+    // ------------------------------------------------------------------
+    
+    /**
+     * Base class for all elements with a visual component
+     *
+     * @param {Object} options
+     * @constructor
+     */
+    var Widget = PhpDebugBar.Widget = function(options) {
+        this._attributes = $.extend({}, this.defaults);
+        this._boundAttributes = {};
+        this.$el = $('<' + this.tagName + ' />');
+        if (this.className) {
+            this.$el.addClass(this.className);
+        }
+        this.initialize.apply(this, [options || {}]);
+        this.render.apply(this);
+    };
+
+    $.extend(Widget.prototype, {
+
+        tagName: 'div',
+
+        className: null,
+
+        defaults: {},
+
+        /**
+         * Called after the constructor
+         * 
+         * @param {Object} options
+         */
+        initialize: function(options) {
+            this.set(options);
+        },
+
+        /**
+         * Called after the constructor to render the element
+         */
+        render: function() {},
+
+        /**
+         * Sets the value of an attribute
+         * 
+         * @param {String} attr Can also be an object to set multiple attributes at once
+         * @param {Object} value
+         */
+        set: function(attr, value) {
+            if (typeof(attr) != 'string') {
+                for (var k in attr) {
+                    this.set(k, attr[k]);
+                }
+                return;
+            }
+
+            this._attributes[attr] = value;
+            if (typeof(this._boundAttributes[attr]) !== 'undefined') {
+                for (var i = 0, c = this._boundAttributes[attr].length; i < c; i++) {
+                    this._boundAttributes[attr][i].apply(this, [value]);
+                }
+            }
+        },
+
+        /**
+         * Checks if an attribute exists and is not null
+         * 
+         * @param {String} attr
+         * @return {[type]} [description]
+         */
+        has: function(attr) {
+            return typeof(this._attributes[attr]) !== 'undefined' && this._attributes[attr] !== null;
+        },
+
+        /**
+         * Returns the value of an attribute
+         * 
+         * @param {String} attr
+         * @return {Object}
+         */
+        get: function(attr) {
+            return this._attributes[attr];
+        },
+
+        /**
+         * Registers a callback function that will be called whenever the value of the attribute changes
+         *
+         * If cb is a jQuery element, text() will be used to fill the element
+         * 
+         * @param {String} attr
+         * @param {Function} cb
+         */
+        bindAttr: function(attr, cb) {
+            if ($.isArray(attr)) {
+                for (var i = 0, c = attr.length; i < c; i++) {
+                    this.bindAttr(attr[i], cb);
+                }
+                return;
+            }
+
+            if (typeof(this._boundAttributes[attr]) == 'undefined') {
+                this._boundAttributes[attr] = [];
+            }
+            if (typeof(cb) == 'object') {
+                var el = cb;
+                cb = function(value) { el.text(value || ''); };
+            }
+            this._boundAttributes[attr].push(cb);
+            if (this.has(attr)) {
+                cb.apply(this, [this._attributes[attr]]);
+            }
+        }
+
+    });
+
+
+    /**
+     * Creates a subclass
+     *
+     * Code from Backbone.js
+     * 
+     * @param {Array} props Prototype properties
+     * @return {Function}
+     */
+    Widget.extend = function(props) {
+        var parent = this;
+
+        var child = function() { return parent.apply(this, arguments); };
+        $.extend(child, parent);
+
+        var Surrogate = function(){ this.constructor = child; };
+        Surrogate.prototype = parent.prototype;
+        child.prototype = new Surrogate;
+        $.extend(child.prototype, props);
+
+        child.__super__ = parent.prototype;
+
+        return child;
+    };
+
+    // ------------------------------------------------------------------
+
+    /**
+     * Tab
+     * 
+     * A tab is composed of a tab label which is always visible and
+     * a tab panel which is visible only when the tab is active.
+     *
+     * The panel must contain a widget. A widget is an object which has
+     * an element property containing something appendable to a jQuery object.
+     *
+     * Options:
+     *  - title
+     *  - badge
+     *  - widget
+     *  - data: forward data to widget data
+     */
+    var Tab = Widget.extend({
+
+        className: csscls('panel'),
+
+        render: function() {
+            this.$tab = $('<a />').addClass(csscls('tab'));
+
+            this.$icon = $('<i />').appendTo(this.$tab);
+            this.bindAttr('icon', function(icon) {
+                if (icon) {
+                    this.$icon.attr('class', 'fa fa-' + icon);
+                } else {
+                    this.$icon.attr('class', '');
+                }
+            });
+
+            this.bindAttr('title', $('<span />').addClass(csscls('text')).appendTo(this.$tab));
+
+            this.$badge = $('<span />').addClass(csscls('badge')).appendTo(this.$tab);
+            this.bindAttr('badge', function(value) {
+                if (value !== null) {
+                    this.$badge.text(value);
+                    this.$badge.show();
+                } else {
+                    this.$badge.hide();
+                }
+            });
+
+            this.bindAttr('widget', function(widget) {
+                this.$el.empty().append(widget.$el);
+            });
+
+            this.bindAttr('data', function(data) {
+                if (this.has('widget')) {
+                    this.get('widget').set('data', data);
+                }
+            })
+        }
+
+    });
+
+    // ------------------------------------------------------------------
+
+    /**
+     * Indicator
+     *
+     * An indicator is a text and an icon to display single value information
+     * right inside the always visible part of the debug bar
+     *
+     * Options:
+     *  - icon
+     *  - title
+     *  - tooltip
+     *  - data: alias of title
+     */
+    var Indicator = Widget.extend({
+
+        tagName: 'span',
+
+        className: csscls('indicator'),
+
+        render: function() {
+            this.$icon = $('<i />').appendTo(this.$el);
+            this.bindAttr('icon', function(icon) {
+                if (icon) {
+                    this.$icon.attr('class', 'fa fa-' + icon);
+                } else {
+                    this.$icon.attr('class', '');
+                }
+            });
+
+            this.bindAttr(['title', 'data'], $('<span />').addClass(csscls('text')).appendTo(this.$el));
+
+            this.$tooltip = $('<span />').addClass(csscls('tooltip disabled')).appendTo(this.$el);
+            this.bindAttr('tooltip', function(tooltip) {
+                if (tooltip) {
+                    this.$tooltip.text(tooltip).removeClass(csscls('disabled'));
+                } else {
+                    this.$tooltip.addClass(csscls('disabled'));
+                }
+            });
+        }
+
+    });
+
+    // ------------------------------------------------------------------
+
+    /**
+     * Dataset title formater
+     *
+     * Formats the title of a dataset for the select box
+     */
+    var DatasetTitleFormater = PhpDebugBar.DatasetTitleFormater = function(debugbar) {
+        this.debugbar = debugbar;
+    };
+
+    $.extend(DatasetTitleFormater.prototype, {
+
+        /**
+         * Formats the title of a dataset
+         * 
+         * @this {DatasetTitleFormater}
+         * @param {String} id
+         * @param {Object} data
+         * @param {String} suffix
+         * @return {String}
+         */
+        format: function(id, data, suffix) {
+            if (suffix) {
+                suffix = ' ' + suffix;
+            } else {
+                suffix = '';
+            }
+
+            var nb = getObjectSize(this.debugbar.datasets) + 1;
+
+            if (typeof(data['__meta']) === 'undefined') {
+                return "#" + nb + suffix;
+            }
+
+            var uri = data['__meta']['uri'], filename;
+            if (uri.length && uri.charAt(uri.length - 1) === '/') {
+                // URI ends in a trailing /: get the portion before then to avoid returning an empty string
+                filename = uri.substr(0, uri.length - 1); // strip trailing '/'
+                filename = filename.substr(filename.lastIndexOf('/') + 1); // get last path segment
+                filename += '/'; // add the trailing '/' back
+            } else {
+                filename = uri.substr(uri.lastIndexOf('/') + 1);
+            }
+
+            // truncate the filename in the label, if it's too long
+            var maxLength = 150;
+            if (filename.length > maxLength) {
+                filename = filename.substr(0, maxLength) + '...';
+            }
+
+            var label = "#" + nb + " " + filename + suffix + ' (' + data['__meta']['datetime'].split(' ')[1] + ')';
+            return label;
+        }
+
+    });
+
+    // ------------------------------------------------------------------
+
+
+    /**
+     * DebugBar
+     *
+     * Creates a bar that appends itself to the body of your page
+     * and sticks to the bottom.
+     *
+     * The bar can be customized by adding tabs and indicators.
+     * A data map is used to fill those controls with data provided
+     * from datasets.
+     */
+    var DebugBar = PhpDebugBar.DebugBar = Widget.extend({
+
+        className: "phpdebugbar " + csscls('minimized'),
+
+        options: {
+            bodyMarginBottom: true,
+            bodyMarginBottomHeight: parseInt($('body').css('margin-bottom'))
+        },
+
+        initialize: function() {
+            this.controls = {};
+            this.dataMap = {};
+            this.datasets = {};
+            this.firstTabName = null;
+            this.activePanelName = null;
+            this.datesetTitleFormater = new DatasetTitleFormater(this);
+            this.registerResizeHandler();
+        },
+
+        /**
+         * Register resize event, for resize debugbar with reponsive css.
+         *
+         * @this {DebugBar}
+         */
+        registerResizeHandler: function() {
+            if (typeof this.resize.bind == 'undefined') return;
+
+            var f = this.resize.bind(this);
+            this.respCSSSize = 0;
+            $(window).resize(f);
+            setTimeout(f, 20);
+        },
+
+        /**
+         * Resizes the debugbar to fit the current browser window
+         */
+        resize: function() {
+            var contentSize = this.respCSSSize;
+            if (this.respCSSSize == 0) {
+                this.$header.find("> div > *:visible").each(function () {
+                    contentSize += $(this).outerWidth();
+                });
+            }
+
+            var currentSize = this.$header.width();
+            var cssClass = "phpdebugbar-mini-design";
+            var bool = this.$header.hasClass(cssClass);
+
+            if (currentSize <= contentSize && !bool) {
+                this.respCSSSize = contentSize;
+                this.$header.addClass(cssClass);
+            } else if (contentSize < currentSize && bool) {
+                this.respCSSSize = 0;
+                this.$header.removeClass(cssClass);
+            }
+
+            // Reset height to ensure bar is still visible
+            this.setHeight(this.$body.height());
+        },
+
+        /**
+         * Initialiazes the UI
+         *
+         * @this {DebugBar}
+         */
+        render: function() {
+            var self = this;
+            this.$el.appendTo('body');
+            this.$dragCapture = $('<div />').addClass(csscls('drag-capture')).appendTo(this.$el);
+            this.$resizehdle = $('<div />').addClass(csscls('resize-handle')).appendTo(this.$el);
+            this.$header = $('<div />').addClass(csscls('header')).appendTo(this.$el);
+            this.$headerLeft = $('<div />').addClass(csscls('header-left')).appendTo(this.$header);
+            this.$headerRight = $('<div />').addClass(csscls('header-right')).appendTo(this.$header);
+            var $body = this.$body = $('<div />').addClass(csscls('body')).appendTo(this.$el);
+            this.recomputeBottomOffset();
+
+            // dragging of resize handle
+            var pos_y, orig_h;
+            this.$resizehdle.on('mousedown', function(e) {
+                orig_h = $body.height(), pos_y = e.pageY;
+                $body.parents().on('mousemove', mousemove).on('mouseup', mouseup);
+                self.$dragCapture.show();
+                e.preventDefault();
+            });
+            var mousemove = function(e) {
+                var h = orig_h + (pos_y - e.pageY);
+                self.setHeight(h);
+            };
+            var mouseup = function() {
+                $body.parents().off('mousemove', mousemove).off('mouseup', mouseup);
+                self.$dragCapture.hide();
+            };
+
+            // close button
+            this.$closebtn = $('<a />').addClass(csscls('close-btn')).appendTo(this.$headerRight);
+            this.$closebtn.click(function() {
+                self.close();
+            });
+
+            // minimize button
+            this.$minimizebtn = $('<a />').addClass(csscls('minimize-btn') ).appendTo(this.$headerRight);
+            this.$minimizebtn.click(function() {
+                self.minimize();
+            });
+
+            // maximize button
+            this.$maximizebtn = $('<a />').addClass(csscls('maximize-btn') ).appendTo(this.$headerRight);
+            this.$maximizebtn.click(function() {
+                self.restore();
+            });
+
+            // restore button
+            this.$restorebtn = $('<a />').addClass(csscls('restore-btn')).hide().appendTo(this.$el);
+            this.$restorebtn.click(function() {
+                self.restore();
+            });
+
+            // open button
+            this.$openbtn = $('<a />').addClass(csscls('open-btn')).appendTo(this.$headerRight).hide();
+            this.$openbtn.click(function() {
+                self.openHandler.show(function(id, dataset) {
+                    self.addDataSet(dataset, id, "(opened)");
+                    self.showTab();
+                });
+            });
+
+            // select box for data sets
+            this.$datasets = $('<select />').addClass(csscls('datasets-switcher')).appendTo(this.$headerRight);
+            this.$datasets.change(function() {
+                self.dataChangeHandler(self.datasets[this.value]);
+                self.showTab();
+            });
+        },
+
+        /**
+         * Sets the height of the debugbar body section
+         * Forces the height to lie within a reasonable range
+         * Stores the height in local storage so it can be restored
+         * Resets the document body bottom offset
+         *
+         * @this {DebugBar}
+         */
+        setHeight: function(height) {
+          var min_h = 40;
+          var max_h = $(window).innerHeight() - this.$header.height() - 10;
+          height = Math.min(height, max_h);
+          height = Math.max(height, min_h);
+          this.$body.css('height', height);
+          localStorage.setItem('phpdebugbar-height', height);
+          this.recomputeBottomOffset();
+        },
+
+        /**
+         * Restores the state of the DebugBar using localStorage
+         * This is not called by default in the constructor and
+         * needs to be called by subclasses in their init() method
+         *
+         * @this {DebugBar}
+         */
+        restoreState: function() {
+            // bar height
+            var height = localStorage.getItem('phpdebugbar-height');
+            this.setHeight(height || this.$body.height());
+
+            // bar visibility
+            var open = localStorage.getItem('phpdebugbar-open');
+            if (open && open == '0') {
+                this.close();
+            } else {
+                var visible = localStorage.getItem('phpdebugbar-visible');
+                if (visible && visible == '1') {
+                    var tab = localStorage.getItem('phpdebugbar-tab');
+                    if (this.isTab(tab)) {
+                        this.showTab(tab);
+                    }
+                }
+            }
+        },
+
+        /**
+         * Creates and adds a new tab
+         *
+         * @this {DebugBar}
+         * @param {String} name Internal name
+         * @param {Object} widget A widget object with an element property
+         * @param {String} title The text in the tab, if not specified, name will be used
+         * @return {Tab}
+         */
+        createTab: function(name, widget, title) {
+            var tab = new Tab({
+                title: title || (name.replace(/[_\-]/g, ' ').charAt(0).toUpperCase() + name.slice(1)), 
+                widget: widget
+            });
+            return this.addTab(name, tab);
+        },
+
+        /**
+         * Adds a new tab
+         *
+         * @this {DebugBar}
+         * @param {String} name Internal name
+         * @param {Tab} tab Tab object
+         * @return {Tab}
+         */
+        addTab: function(name, tab) {
+            if (this.isControl(name)) {
+                throw new Error(name + ' already exists');
+            }
+
+            var self = this;
+            tab.$tab.appendTo(this.$headerLeft).click(function() {
+                if (!self.isMinimized() && self.activePanelName == name) {
+                    self.minimize();
+                } else {
+                    self.showTab(name);
+                }
+            });
+            tab.$el.appendTo(this.$body);
+
+            this.controls[name] = tab;
+            if (this.firstTabName == null) {
+                this.firstTabName = name;
+            }
+            return tab;
+        },
+
+        /**
+         * Creates and adds an indicator
+         *
+         * @this {DebugBar}
+         * @param {String} name Internal name
+         * @param {String} icon
+         * @param {String} tooltip
+         * @param {String} position "right" or "left", default is "right"
+         * @return {Indicator}
+         */
+        createIndicator: function(name, icon, tooltip, position) {
+            var indicator = new Indicator({
+                icon: icon,
+                tooltip: tooltip
+            });
+            return this.addIndicator(name, indicator, position);
+        },
+
+        /**
+         * Adds an indicator
+         * 
+         * @this {DebugBar}
+         * @param {String} name Internal name
+         * @param {Indicator} indicator Indicator object
+         * @return {Indicator}
+         */
+        addIndicator: function(name, indicator, position) {
+            if (this.isControl(name)) {
+                throw new Error(name + ' already exists');
+            }
+
+            if (position == 'left') {
+                indicator.$el.insertBefore(this.$headerLeft.children().first());
+            } else {
+                indicator.$el.appendTo(this.$headerRight);
+            }
+
+            this.controls[name] = indicator;
+            return indicator;
+        },
+
+        /**
+         * Returns a control
+         * 
+         * @param {String} name
+         * @return {Object}
+         */
+        getControl: function(name) {
+            if (this.isControl(name)) {
+                return this.controls[name];
+            }
+        },
+
+        /**
+         * Checks if there's a control under the specified name
+         * 
+         * @this {DebugBar}
+         * @param {String} name
+         * @return {Boolean}
+         */
+        isControl: function(name) {
+            return typeof(this.controls[name]) != 'undefined';
+        },
+
+        /**
+         * Checks if a tab with the specified name exists
+         * 
+         * @this {DebugBar}
+         * @param {String} name
+         * @return {Boolean}
+         */
+        isTab: function(name) {
+            return this.isControl(name) && this.controls[name] instanceof Tab;
+        },
+
+        /**
+         * Checks if an indicator with the specified name exists
+         * 
+         * @this {DebugBar}
+         * @param {String} name
+         * @return {Boolean}
+         */
+        isIndicator: function(name) {
+            return this.isControl(name) && this.controls[name] instanceof Indicator;
+        },
+
+        /**
+         * Removes all tabs and indicators from the debug bar and hides it
+         * 
+         * @this {DebugBar}
+         */
+        reset: function() {
+            this.minimize();
+            var self = this;
+            $.each(this.controls, function(name, control) {
+                if (self.isTab(name)) {
+                    control.$tab.remove();
+                }
+                control.$el.remove();
+            });
+            this.controls = {};
+        },
+
+        /**
+         * Open the debug bar and display the specified tab
+         * 
+         * @this {DebugBar}
+         * @param {String} name If not specified, display the first tab
+         */
+        showTab: function(name) {
+            if (!name) {
+                if (this.activePanelName) {
+                    name = this.activePanelName;
+                } else {
+                    name = this.firstTabName;
+                }
+            }
+
+            if (!this.isTab(name)) {
+                throw new Error("Unknown tab '" + name + "'");
+            }
+
+            this.$resizehdle.show();
+            this.$body.show();
+            this.recomputeBottomOffset();
+
+            $(this.$header).find('> div > .' + csscls('active')).removeClass(csscls('active'));
+            $(this.$body).find('> .' + csscls('active')).removeClass(csscls('active'));
+
+            this.controls[name].$tab.addClass(csscls('active'));
+            this.controls[name].$el.addClass(csscls('active'));
+            this.activePanelName = name;
+
+            this.$el.removeClass(csscls('minimized'));
+            localStorage.setItem('phpdebugbar-visible', '1');
+            localStorage.setItem('phpdebugbar-tab', name);
+            this.resize();
+        },
+
+        /**
+         * Hide panels and minimize the debug bar
+         *
+         * @this {DebugBar}
+         */
+        minimize: function() {
+            this.$header.find('> div > .' + csscls('active')).removeClass(csscls('active'));
+            this.$body.hide();
+            this.$resizehdle.hide();
+            this.recomputeBottomOffset();
+            localStorage.setItem('phpdebugbar-visible', '0');
+            this.$el.addClass(csscls('minimized'));
+            this.resize();
+        },
+
+        /**
+         * Checks if the panel is minimized
+         * 
+         * @return {Boolean}
+         */
+        isMinimized: function() {
+            return this.$el.hasClass(csscls('minimized'));
+        },
+        
+        /**
+         * Close the debug bar
+         *
+         * @this {DebugBar}
+         */
+        close: function() {
+            this.$resizehdle.hide();
+            this.$header.hide();
+            this.$body.hide();
+            this.$restorebtn.show();
+            localStorage.setItem('phpdebugbar-open', '0');
+            this.$el.addClass(csscls('closed'));
+            this.recomputeBottomOffset();
+        },
+
+        /**
+         * Checks if the panel is closed
+         *
+         * @return {Boolean}
+         */
+        isClosed: function() {
+            return this.$el.hasClass(csscls('closed'));
+        },
+
+        /**
+         * Restore the debug bar
+         *
+         * @this {DebugBar}
+         */
+        restore: function() {
+            this.$resizehdle.show();
+            this.$header.show();
+            this.$restorebtn.hide();
+            localStorage.setItem('phpdebugbar-open', '1');
+            var tab = localStorage.getItem('phpdebugbar-tab');
+            if (this.isTab(tab)) {
+                this.showTab(tab);
+            } else {
+                this.showTab();
+            }
+            this.$el.removeClass(csscls('closed'));
+            this.resize();
+        },
+
+        /**
+         * Recomputes the margin-bottom css property of the body so
+         * that the debug bar never hides any content
+         */
+        recomputeBottomOffset: function() {
+            if (this.options.bodyMarginBottom) {
+                if (this.isClosed()) {
+                    return $('body').css('margin-bottom', this.options.bodyMarginBottomHeight || '');
+                }
+                
+                var offset = parseInt(this.$el.height()) + this.options.bodyMarginBottomHeight;
+                $('body').css('margin-bottom', offset);
+            }
+        },
+
+        /**
+         * Sets the data map used by dataChangeHandler to populate
+         * indicators and widgets
+         *
+         * A data map is an object where properties are control names.
+         * The value of each property should be an array where the first
+         * item is the name of a property from the data object (nested properties
+         * can be specified) and the second item the default value.
+         *
+         * Example:
+         *     {"memory": ["memory.peak_usage_str", "0B"]}
+         * 
+         * @this {DebugBar}
+         * @param {Object} map
+         */
+        setDataMap: function(map) {
+            this.dataMap = map;
+        },
+
+        /**
+         * Same as setDataMap() but appends to the existing map
+         * rather than replacing it
+         *
+         * @this {DebugBar}
+         * @param {Object} map
+         */
+        addDataMap: function(map) {
+            $.extend(this.dataMap, map);
+        },
+
+        /**
+         * Resets datasets and add one set of data
+         *
+         * For this method to be usefull, you need to specify
+         * a dataMap using setDataMap()
+         * 
+         * @this {DebugBar}
+         * @param {Object} data
+         * @return {String} Dataset's id
+         */
+        setData: function(data) {
+            this.datasets = {};
+            return this.addDataSet(data);
+        },
+
+        /**
+         * Adds a dataset
+         *
+         * If more than one dataset are added, the dataset selector
+         * will be displayed.
+         * 
+         * For this method to be usefull, you need to specify
+         * a dataMap using setDataMap()
+         * 
+         * @this {DebugBar}
+         * @param {Object} data
+         * @param {String} id The name of this set, optional
+         * @param {String} suffix
+         * @return {String} Dataset's id
+         */
+        addDataSet: function(data, id, suffix) {
+            var label = this.datesetTitleFormater.format(id, data, suffix);
+            id = id || (getObjectSize(this.datasets) + 1);
+            this.datasets[id] = data;
+
+            this.$datasets.append($('<option value="' + id + '">' + label + '</option>'));
+            if (this.$datasets.children().length > 1) {
+                this.$datasets.show();
+            }
+
+            this.showDataSet(id);
+            return id;
+        },
+
+        /**
+         * Loads a dataset using the open handler
+         * 
+         * @param {String} id
+         */
+        loadDataSet: function(id, suffix, callback) {
+            if (!this.openHandler) {
+                throw new Error('loadDataSet() needs an open handler');
+            }
+            var self = this;
+            this.openHandler.load(id, function(data) {
+                self.addDataSet(data, id, suffix);
+                callback && callback(data);
+            });
+        },
+
+        /**
+         * Returns the data from a dataset
+         * 
+         * @this {DebugBar}
+         * @param {String} id
+         * @return {Object}
+         */
+        getDataSet: function(id) {
+            return this.datasets[id];
+        },
+
+        /**
+         * Switch the currently displayed dataset
+         * 
+         * @this {DebugBar}
+         * @param {String} id
+         */
+        showDataSet: function(id) {
+            this.dataChangeHandler(this.datasets[id]);
+            this.$datasets.val(id);
+        },
+
+        /**
+         * Called when the current dataset is modified.
+         * 
+         * @this {DebugBar}
+         * @param {Object} data
+         */
+        dataChangeHandler: function(data) {
+            var self = this;
+            $.each(this.dataMap, function(key, def) {
+                var d = getDictValue(data, def[0], def[1]);
+                if (key.indexOf(':') != -1) {
+                    key = key.split(':');
+                    self.getControl(key[0]).set(key[1], d);
+                } else {
+                    self.getControl(key).set('data', d);
+                }
+            });
+        },
+
+        /**
+         * Sets the handler to open past dataset
+         * 
+         * @this {DebugBar}
+         * @param {object} handler
+         */
+        setOpenHandler: function(handler) {
+            this.openHandler = handler;
+            if (handler !== null) {
+                this.$openbtn.show();
+            } else {
+                this.$openbtn.hide();
+            }
+        },
+
+        /**
+         * Returns the handler to open past dataset
+         * 
+         * @this {DebugBar}
+         * @return {object}
+         */
+        getOpenHandler: function() {
+            return this.openHandler;
+        }
+
+    });
+
+    DebugBar.Tab = Tab;
+    DebugBar.Indicator = Indicator;
+
+    // ------------------------------------------------------------------
+    
+    /**
+     * AjaxHandler
+     *
+     * Extract data from headers of an XMLHttpRequest and adds a new dataset
+     */
+    var AjaxHandler = PhpDebugBar.AjaxHandler = function(debugbar, headerName) {
+        this.debugbar = debugbar;
+        this.headerName = headerName || 'phpdebugbar';
+    };
+
+    $.extend(AjaxHandler.prototype, {
+
+        /**
+         * Handles an XMLHttpRequest
+         * 
+         * @this {AjaxHandler}
+         * @param {XMLHttpRequest} xhr
+         * @return {Bool}
+         */
+        handle: function(xhr) {
+            if (!this.loadFromId(xhr)) {
+                return this.loadFromData(xhr);
+            }
+            return true;
+        },
+
+        /**
+         * Checks if the HEADER-id exists and loads the dataset using the open handler
+         * 
+         * @param {XMLHttpRequest} xhr
+         * @return {Bool}
+         */
+        loadFromId: function(xhr) {
+            var id = this.extractIdFromHeaders(xhr);
+            if (id && this.debugbar.openHandler) {
+                this.debugbar.loadDataSet(id, "(ajax)");
+                return true;
+            }
+            return false;
+        },
+
+        /**
+         * Extracts the id from the HEADER-id
+         * 
+         * @param {XMLHttpRequest} xhr
+         * @return {String}
+         */
+        extractIdFromHeaders: function(xhr) {
+            return xhr.getResponseHeader(this.headerName + '-id');
+        },
+
+        /**
+         * Checks if the HEADER exists and loads the dataset
+         * 
+         * @param {XMLHttpRequest} xhr
+         * @return {Bool}
+         */
+        loadFromData: function(xhr) {
+            var raw = this.extractDataFromHeaders(xhr);
+            if (!raw) {
+                return false;
+            }
+
+            var data = this.parseHeaders(raw);
+            if (data.error) {
+                throw new Error('Error loading debugbar data: ' + data.error);
+            } else if(data.data) {
+                this.debugbar.addDataSet(data.data, data.id, "(ajax)");
+            }
+            return true;
+        },
+
+        /**
+         * Extract the data as a string from headers of an XMLHttpRequest
+         * 
+         * @this {AjaxHandler}
+         * @param {XMLHttpRequest} xhr
+         * @return {string}
+         */
+        extractDataFromHeaders: function(xhr) {
+            var data = xhr.getResponseHeader(this.headerName);
+            if (!data) {
+                return;
+            }
+            for (var i = 1;; i++) {
+                var header = xhr.getResponseHeader(this.headerName + '-' + i);
+                if (!header) {
+                    break;
+                }
+                data += header;
+            }
+            return decodeURIComponent(data);
+        },
+
+        /**
+         * Parses the string data into an object
+         * 
+         * @this {AjaxHandler}
+         * @param {string} data
+         * @return {string}
+         */
+        parseHeaders: function(data) {
+            return JSON.parse(data);
+        },
+
+        /**
+         * Attaches an event listener to jQuery.ajaxComplete()
+         * 
+         * @this {AjaxHandler}
+         * @param {jQuery} jq Optional
+         */
+        bindToJquery: function(jq) {
+            var self = this;
+            jq(document).ajaxComplete(function(e, xhr, settings) {
+                if (!settings.ignoreDebugBarAjaxHandler) {
+                    self.handle(xhr);
+                }
+            });
+        },
+        
+        /**
+         * Attaches an event listener to XMLHttpRequest
+         * 
+         * @this {AjaxHandler}
+         */
+        bindToXHR: function() {
+            var self = this;
+            var proxied = XMLHttpRequest.prototype.open;
+            XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
+                var xhr = this;
+                this.addEventListener("readystatechange", function() {
+                    var skipUrl = self.debugbar.openHandler ? self.debugbar.openHandler.get('url') : null;
+                    if (xhr.readyState == 4 && url.indexOf(skipUrl) !== 0) {
+                        self.handle(xhr);
+                    }
+                }, false);
+                proxied.apply(this, Array.prototype.slice.call(arguments));
+            };
+        }
+
+    });
+
+})(PhpDebugBar.$);

File diff suppressed because it is too large
+ 32 - 0
htdocs/includes/DebugBar/Resources/openhandler.css


+ 202 - 0
htdocs/includes/DebugBar/Resources/openhandler.js

@@ -0,0 +1,202 @@
+if (typeof(PhpDebugBar) == 'undefined') {
+    // namespace
+    var PhpDebugBar = {};
+    PhpDebugBar.$ = jQuery;
+}
+
+(function($) {
+
+    var csscls = function(cls) {
+        return PhpDebugBar.utils.csscls(cls, 'phpdebugbar-openhandler-');
+    };
+
+    PhpDebugBar.OpenHandler = PhpDebugBar.Widget.extend({
+
+        className: 'phpdebugbar-openhandler',
+
+        defaults: {
+            items_per_page: 20
+        },
+
+        render: function() {
+            var self = this;
+            
+            this.$el.appendTo('body').hide();
+            this.$closebtn = $('<a><i class="fa fa-times"></i></a>');
+            this.$table = $('<tbody />');
+            $('<div>PHP DebugBar | Open</div>').addClass(csscls('header')).append(this.$closebtn).appendTo(this.$el);
+            $('<table><thead><tr><th width="150">Date</th><th width="55">Method</th><th>URL</th><th width="125">IP</th><th width="100">Filter data</th></tr></thead></table>').append(this.$table).appendTo(this.$el);
+            this.$actions = $('<div />').addClass(csscls('actions')).appendTo(this.$el);
+
+            this.$closebtn.on('click', function() {
+                self.hide();
+            });
+
+            this.$loadmorebtn = $('<a>Load more</a>')
+                .appendTo(this.$actions)
+                .on('click', function() {
+                    self.find(self.last_find_request, self.last_find_request.offset + self.get('items_per_page'), self.handleFind.bind(self));
+                });
+
+            this.$showonlycurrentbtn = $('<a>Show only current URL</a>')
+                .appendTo(this.$actions)
+                .on('click', function() {
+                    self.$table.empty();
+                    self.find({uri: window.location.pathname}, 0, self.handleFind.bind(self));
+                });
+
+            this.$showallbtn = $('<a>Show all</a>')
+                .appendTo(this.$actions)
+                .on('click', function() {
+                    self.refresh();
+                });
+
+            this.$clearbtn = $('<a>Delete all</a>')
+                .appendTo(this.$actions)
+                .on('click', function() {
+                    self.clear(function() {
+                        self.hide();
+                    });
+                });
+                
+            this.addSearch();
+
+            this.$overlay = $('<div />').addClass(csscls('overlay')).hide().appendTo('body');
+            this.$overlay.on('click', function() {
+                self.hide();
+            });
+        },
+
+        refresh: function() {
+            this.$table.empty();
+            this.$loadmorebtn.show();
+            this.find({}, 0, this.handleFind.bind(this));
+        },
+        
+        addSearch: function(){
+            var self = this;
+            var searchBtn = $('<button />')
+                .text('Search')
+                .attr('type', 'submit')
+                .on('click', function(e) {
+                    self.$table.empty();
+                    var search = {};
+                    var a = $(this).parent().serializeArray();
+                    $.each(a, function() {
+                        if(this.value){
+                            search[this.name] = this.value;
+                        }
+                    });
+
+                    self.find(search, 0, self.handleFind.bind(self));
+                    e.preventDefault();
+                });
+
+            $('<form />')
+                .append('<br/><b>Filter results</b><br/>')
+                .append('Method: <select name="method"><option></option><option>GET</option><option>POST</option><option>PUT</option><option>DELETE</option></select><br/>')
+                .append('Uri: <input type="text" name="uri"><br/>')
+                .append('IP: <input type="text" name="ip"><br/>')
+                .append(searchBtn)
+                .appendTo(this.$actions);
+        },
+
+        handleFind: function(data) {
+            var self = this;
+            $.each(data, function(i, meta) {
+               var a = $('<a />')
+                    .text('Load dataset')
+                    .on('click', function(e) {
+                       self.hide();
+                       self.load(meta['id'], function(data) {
+                           self.callback(meta['id'], data);
+                       });
+                       e.preventDefault();
+                    });
+                    
+                var method = $('<a />')
+                    .text(meta['method'])
+                    .on('click', function(e) {
+                        self.$table.empty();
+                        self.find({method: meta['method']}, 0, self.handleFind.bind(self));
+                        e.preventDefault();
+                    });
+
+                var uri = $('<a />')
+                    .text(meta['uri'])
+                    .on('click', function(e) {
+                        self.hide();
+                        self.load(meta['id'], function(data) {
+                            self.callback(meta['id'], data);
+                        });
+                        e.preventDefault();
+                    });
+
+                var ip = $('<a />')
+                    .text(meta['ip'])
+                    .on('click', function(e) {
+                        self.$table.empty();
+                        self.find({ip: meta['ip']}, 0, self.handleFind.bind(self));
+                        e.preventDefault();
+                    });
+
+                var search = $('<a />')
+                    .text('Show URL')
+                    .on('click', function(e) {
+                        self.$table.empty();
+                        self.find({uri: meta['uri']}, 0, self.handleFind.bind(self));
+                        e.preventDefault();
+                    });
+                    
+                $('<tr />')
+                    .append('<td>' + meta['datetime'] + '</td>')
+                    .append('<td>' + meta['method'] + '</td>')
+                    .append($('<td />').append(uri))
+                    .append($('<td />').append(ip))
+                    .append($('<td />').append(search))
+                    .appendTo(self.$table);
+            });
+            if (data.length < this.get('items_per_page')) {
+                this.$loadmorebtn.hide();
+            }
+        },
+
+        show: function(callback) {
+            this.callback = callback;
+            this.$el.show();
+            this.$overlay.show();
+            this.refresh();
+        },
+
+        hide: function() {
+            this.$el.hide();
+            this.$overlay.hide();
+        },
+
+        find: function(filters, offset, callback) {
+            var data = $.extend({}, filters, {max: this.get('items_per_page'), offset: offset || 0});
+            this.last_find_request = data;
+            this.ajax(data, callback);
+        },
+
+        load: function(id, callback) {
+            this.ajax({op: "get", id: id}, callback);
+        },
+
+        clear: function(callback) {
+            this.ajax({op: "clear"}, callback);
+        },
+
+        ajax: function(data, callback) {
+            $.ajax({
+                dataType: 'json',
+                url: this.get('url'),
+                data: data,
+                success: callback,
+                ignoreDebugBarAjaxHandler: true
+            });
+        }
+
+    });
+
+})(PhpDebugBar.$);

File diff suppressed because it is too large
+ 3 - 0
htdocs/includes/DebugBar/Resources/vendor/font-awesome/css/font-awesome.min.css


BIN
htdocs/includes/DebugBar/Resources/vendor/font-awesome/fonts/FontAwesome.otf


BIN
htdocs/includes/DebugBar/Resources/vendor/font-awesome/fonts/fontawesome-webfont.eot


File diff suppressed because it is too large
+ 196 - 0
htdocs/includes/DebugBar/Resources/vendor/font-awesome/fonts/fontawesome-webfont.svg


BIN
htdocs/includes/DebugBar/Resources/vendor/font-awesome/fonts/fontawesome-webfont.ttf


BIN
htdocs/includes/DebugBar/Resources/vendor/font-awesome/fonts/fontawesome-webfont.woff


File diff suppressed because it is too large
+ 0 - 0
htdocs/includes/DebugBar/Resources/vendor/highlightjs/highlight.pack.js


+ 125 - 0
htdocs/includes/DebugBar/Resources/vendor/highlightjs/styles/github.css

@@ -0,0 +1,125 @@
+/*
+
+github.com style (c) Vasily Polovnyov <vast@whiteants.net>
+
+*/
+
+.hljs {
+  display: block; padding: 0.5em;
+  color: #333;
+  background: #f8f8f8
+}
+
+.hljs-comment,
+.hljs-template_comment,
+.diff .hljs-header,
+.hljs-javadoc {
+  color: #998;
+  font-style: italic
+}
+
+.hljs-keyword,
+.css .rule .hljs-keyword,
+.hljs-winutils,
+.javascript .hljs-title,
+.nginx .hljs-title,
+.hljs-subst,
+.hljs-request,
+.hljs-status {
+  color: #333;
+  font-weight: bold
+}
+
+.hljs-number,
+.hljs-hexcolor,
+.ruby .hljs-constant {
+  color: #099;
+}
+
+.hljs-string,
+.hljs-tag .hljs-value,
+.hljs-phpdoc,
+.tex .hljs-formula {
+  color: #d14
+}
+
+.hljs-title,
+.hljs-id,
+.coffeescript .hljs-params,
+.scss .hljs-preprocessor {
+  color: #900;
+  font-weight: bold
+}
+
+.javascript .hljs-title,
+.lisp .hljs-title,
+.clojure .hljs-title,
+.hljs-subst {
+  font-weight: normal
+}
+
+.hljs-class .hljs-title,
+.haskell .hljs-type,
+.vhdl .hljs-literal,
+.tex .hljs-command {
+  color: #458;
+  font-weight: bold
+}
+
+.hljs-tag,
+.hljs-tag .hljs-title,
+.hljs-rules .hljs-property,
+.django .hljs-tag .hljs-keyword {
+  color: #000080;
+  font-weight: normal
+}
+
+.hljs-attribute,
+.hljs-variable,
+.lisp .hljs-body {
+  color: #008080
+}
+
+.hljs-regexp {
+  color: #009926
+}
+
+.hljs-symbol,
+.ruby .hljs-symbol .hljs-string,
+.lisp .hljs-keyword,
+.tex .hljs-special,
+.hljs-prompt {
+  color: #990073
+}
+
+.hljs-built_in,
+.lisp .hljs-title,
+.clojure .hljs-built_in {
+  color: #0086b3
+}
+
+.hljs-preprocessor,
+.hljs-pragma,
+.hljs-pi,
+.hljs-doctype,
+.hljs-shebang,
+.hljs-cdata {
+  color: #999;
+  font-weight: bold
+}
+
+.hljs-deletion {
+  background: #fdd
+}
+
+.hljs-addition {
+  background: #dfd
+}
+
+.diff .hljs-change {
+  background: #0086b3
+}
+
+.hljs-chunk {
+  color: #aaa
+}

File diff suppressed because it is too large
+ 1 - 0
htdocs/includes/DebugBar/Resources/vendor/jquery/dist/jquery.min.js


+ 243 - 0
htdocs/includes/DebugBar/Resources/widgets.css

@@ -0,0 +1,243 @@
+pre.phpdebugbar-widgets-code-block {
+    white-space: pre;
+    word-wrap: normal;
+    overflow: hidden;
+}
+  pre.phpdebugbar-widgets-code-block code {
+    display: block;
+    overflow-x: auto;
+    overflow-y: hidden;
+  }
+  pre.phpdebugbar-widgets-code-block code.phpdebugbar-widgets-numbered-code {
+    padding: 5px;
+  }
+    pre.phpdebugbar-widgets-code-block code span.phpdebugbar-widgets-highlighted-line {
+      background: #800000;
+      color: #fff;
+      display: inline-block;
+      min-width: 100%;
+    }
+      pre.phpdebugbar-widgets-code-block code span.phpdebugbar-widgets-highlighted-line span {
+        background: none !important;
+        color: inherit !important;
+      }
+  pre.phpdebugbar-widgets-code-block ul {
+    float: left;
+    padding: 5px;
+    background: #cacaca;
+    border-right: 1px solid #aaa;
+    text-align: right;
+  }
+/* -------------------------------------- */
+
+ul.phpdebugbar-widgets-list {
+  margin: 0;
+  padding: 0;
+  list-style: none;
+  font-family: monospace;
+}
+  ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item {
+    padding: 3px 6px;
+    border-bottom: 1px solid #eee;
+    position: relative;
+    overflow: hidden;
+  }
+  ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item:hover {
+    background: #fafafa;
+  }
+
+/* -------------------------------------- */
+
+div.phpdebugbar-widgets-messages {
+  position: relative;
+  height: 100%;
+}
+  div.phpdebugbar-widgets-messages ul.phpdebugbar-widgets-list {
+    padding-bottom: 20px;
+  }
+  div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-warning:before {
+    font-family: FontAwesome;
+    content: "\f071";
+    margin-right: 8px;
+    font-size: 11px;
+    color: #ecb03d;
+  }
+  div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-error {
+    color: red;
+  }
+  div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-error:before {
+    font-family: FontAwesome;
+    content: "\f057";
+    margin-right: 8px;
+    font-size: 11px;
+    color: red;
+  }
+  div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-collector,
+  div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-label {
+    float: right;
+    font-size: 12px;
+    padding: 2px 4px;
+    color: #888;
+    margin: 0 2px;
+    text-decoration: none;
+    text-shadow: none;
+    background: none;
+    font-weight: normal;
+  }
+  div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-collector {
+    color: #555;
+    font-style: italic;
+  }
+  div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar {
+    position: fixed;
+    bottom: 0;
+    width: 100%;
+    background: #fff;
+  }
+  div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar i.fa-search {
+    margin-left: 7px;
+  }
+    div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar input {
+      border: 0;
+      margin: 0;
+      margin-left: 7px;
+      width: 50%;
+      box-shadow: none;
+    }
+    div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar input:focus {
+      outline: none;
+    }
+      div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter {
+        float: right;
+        font-size: 12px;
+        padding: 2px 4px;
+        background: #3d6c99;
+        margin: 0 2px;
+        border-radius: 4px;
+        color: #fff;
+        text-decoration: none;
+      }
+      div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded {
+        background: #eee;
+        color: #888;
+      }
+
+/* -------------------------------------- */
+
+dl.phpdebugbar-widgets-kvlist {
+  margin: 0;
+}
+  dl.phpdebugbar-widgets-kvlist dt {
+    float: left;
+    width: 150px;
+    padding: 5px;
+    border-top: 1px solid #eee;
+    font-weight: bold;
+    clear: both;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  dl.phpdebugbar-widgets-kvlist dd {
+    margin-left: 160px;
+    padding: 5px;
+    border-top: 1px solid #eee;
+    cursor: pointer;
+    min-height: 17px;
+  }
+
+/* -------------------------------------- */
+
+dl.phpdebugbar-widgets-varlist {
+  font-family: monospace;
+}
+
+/* -------------------------------------- */
+
+ul.phpdebugbar-widgets-timeline {
+  margin: 0;
+  padding: 0;
+  list-style: none;
+}
+  ul.phpdebugbar-widgets-timeline .phpdebugbar-widgets-measure {
+    height: 20px;
+    position: relative;
+    border-bottom: 1px solid #eee;
+    display: block;
+  }
+  ul.phpdebugbar-widgets-timeline li:hover {
+    background: #fafafa;
+  }
+    ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label,
+    ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-collector {
+      position: absolute;
+      font-size: 12px;
+      font-family: monospace;
+      color: #555;
+      top: 4px;
+      left: 5px;
+      background: none;
+      text-shadow: none;
+      font-weight: normal;
+    }
+    ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-collector {
+      left: initial;
+      right: 5px;
+    }
+    ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-value {
+      display: block; 
+      position: absolute;
+      height: 10px;
+      background: #3db9ec;
+      top: 5px;
+      border-radius: 2px;
+      min-width: 1px;
+    }
+    ul.phpdebugbar-widgets-timeline table.phpdebugbar-widgets-params {
+      display: none;
+      width: 70%;
+      margin: 10px;
+      border: 1px solid #ddd;
+      font-family: monospace;
+      border-collapse: collapse;
+    }
+      ul.phpdebugbar-widgets-timeline table.phpdebugbar-widgets-params td {
+        border: 1px solid #ddd;
+        padding: 0 5px;
+      }
+      ul.phpdebugbar-widgets-timeline table.phpdebugbar-widgets-params .phpdebugbar-widgets-name {
+        width: 20%;
+        font-weight: bold;
+      }
+
+/* -------------------------------------- */
+
+div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item {
+  cursor: pointer;
+}
+  div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-message {
+    display: block;
+    color: red;
+  }
+
+  div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-filename {
+    display: block;
+    font-style: italic;
+    color: #555;
+  }
+
+  div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-type {
+    display: block;
+    position: absolute;
+    right: 4px;
+    top: 4px;
+    font-weight: bold;
+  }
+
+  div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item pre.phpdebugbar-widgets-file {
+    display: none;
+    margin: 10px;
+    padding: 5px;
+    border: 1px solid #ddd;
+    font-family: monospace;
+  }

+ 499 - 0
htdocs/includes/DebugBar/Resources/widgets.js

@@ -0,0 +1,499 @@
+if (typeof(PhpDebugBar) == 'undefined') {
+    // namespace
+    var PhpDebugBar = {};
+    PhpDebugBar.$ = jQuery;
+}
+
+(function($) {
+
+    /**
+     * @namespace
+     */
+    PhpDebugBar.Widgets = {};
+
+    var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-');
+
+    /**
+     * Replaces spaces with &nbsp; and line breaks with <br>
+     *
+     * @param {String} text
+     * @return {String}
+     */
+    var htmlize = PhpDebugBar.Widgets.htmlize = function(text) {
+        return text.replace(/\n/g, '<br>').replace(/\s/g, "&nbsp;")
+    };
+
+    /**
+     * Returns a string representation of value, using JSON.stringify
+     * if it's an object.
+     *
+     * @param {Object} value
+     * @param {Boolean} prettify Uses htmlize() if true
+     * @return {String}
+     */
+    var renderValue = PhpDebugBar.Widgets.renderValue = function(value, prettify) {
+        if (typeof(value) !== 'string') {
+            if (prettify) {
+                return htmlize(JSON.stringify(value, undefined, 2));
+            }
+            return JSON.stringify(value);
+        }
+        return value;
+    };
+
+    /**
+     * Highlights a block of code
+     *
+     * @param  {String} code
+     * @param  {String} lang
+     * @return {String}
+     */
+    var highlight = PhpDebugBar.Widgets.highlight = function(code, lang) {
+        if (typeof(code) === 'string') {
+            if (typeof(hljs) === 'undefined') {
+                return htmlize(code);
+            }
+            if (lang) {
+                return hljs.highlight(lang, code).value;
+            }
+            return hljs.highlightAuto(code).value;
+        }
+
+        if (typeof(hljs) === 'object') {
+            code.each(function(i, e) { hljs.highlightBlock(e); });
+        }
+        return code;
+    };
+
+     /**
+     * Creates a <pre> element with a block of code
+     *
+     * @param  {String} code
+     * @param  {String} lang
+     * @param  {Number} [firstLineNumber] If provided, shows line numbers beginning with the given value.
+     * @param  {Number} [highlightedLine] If provided, the given line number will be highlighted.
+     * @return {String}
+     */
+    var createCodeBlock = PhpDebugBar.Widgets.createCodeBlock = function(code, lang, firstLineNumber, highlightedLine) {
+        var pre = $('<pre />').addClass(csscls('code-block'));
+        // Add a newline to prevent <code> element from vertically collapsing too far if the last
+        // code line was empty: that creates problems with the horizontal scrollbar being
+        // incorrectly positioned - most noticeable when line numbers are shown.
+        var codeElement = $('<code />').text(code + '\n').appendTo(pre);
+
+        // Add a span with a special class if we are supposed to highlight a line.  highlight.js will
+        // still correctly format code even with existing markup in it.
+        if ($.isNumeric(highlightedLine)) {
+            if ($.isNumeric(firstLineNumber)) {
+                highlightedLine = highlightedLine - firstLineNumber + 1;
+            }
+            codeElement.html(function (index, html) {
+                var currentLine = 1;
+                return html.replace(/^.*$/gm, function(line) {
+                    if (currentLine++ == highlightedLine) {
+                        return '<span class="' + csscls('highlighted-line') + '">' + line + '</span>';
+                    } else {
+                        return line;
+                    }
+                });
+            });
+        }
+
+        // Format the code
+        if (lang) {
+            pre.addClass("language-" + lang);
+        }
+        highlight(pre);
+
+        // Show line numbers in a list
+        if ($.isNumeric(firstLineNumber)) {
+            var lineCount = code.split('\n').length;
+            var $lineNumbers = $('<ul />').prependTo(pre);
+            pre.children().addClass(csscls('numbered-code'));
+            for (var i = firstLineNumber; i < firstLineNumber + lineCount; i++) {
+                $('<li />').text(i).appendTo($lineNumbers);
+            }
+        }
+
+        return pre;
+    };
+
+
+    // ------------------------------------------------------------------
+    // Generic widgets
+    // ------------------------------------------------------------------
+
+    /**
+     * Displays array element in a <ul> list
+     *
+     * Options:
+     *  - data
+     *  - itemRenderer: a function used to render list items (optional)
+     */
+    var ListWidget = PhpDebugBar.Widgets.ListWidget = PhpDebugBar.Widget.extend({
+
+        tagName: 'ul',
+
+        className: csscls('list'),
+
+        initialize: function(options) {
+            if (!options['itemRenderer']) {
+                options['itemRenderer'] = this.itemRenderer;
+            }
+            this.set(options);
+        },
+
+        render: function() {
+            this.bindAttr(['itemRenderer', 'data'], function() {
+                this.$el.empty();
+                if (!this.has('data')) {
+                    return;
+                }
+
+                var data = this.get('data');
+                for (var i = 0; i < data.length; i++) {
+                    var li = $('<li />').addClass(csscls('list-item')).appendTo(this.$el);
+                    this.get('itemRenderer')(li, data[i]);
+                }
+            });
+        },
+
+        /**
+         * Renders the content of a <li> element
+         *
+         * @param {jQuery} li The <li> element as a jQuery Object
+         * @param {Object} value An item from the data array
+         */
+        itemRenderer: function(li, value) {
+            li.html(renderValue(value));
+        }
+
+    });
+
+    // ------------------------------------------------------------------
+
+    /**
+     * Displays object property/value paris in a <dl> list
+     *
+     * Options:
+     *  - data
+     *  - itemRenderer: a function used to render list items (optional)
+     */
+    var KVListWidget = PhpDebugBar.Widgets.KVListWidget = ListWidget.extend({
+
+        tagName: 'dl',
+
+        className: csscls('kvlist'),
+
+        render: function() {
+            this.bindAttr(['itemRenderer', 'data'], function() {
+                this.$el.empty();
+                if (!this.has('data')) {
+                    return;
+                }
+
+                var self = this;
+                $.each(this.get('data'), function(key, value) {
+                    var dt = $('<dt />').addClass(csscls('key')).appendTo(self.$el);
+                    var dd = $('<dd />').addClass(csscls('value')).appendTo(self.$el);
+                    self.get('itemRenderer')(dt, dd, key, value);
+                });
+            });
+        },
+
+        /**
+         * Renders the content of the <dt> and <dd> elements
+         *
+         * @param {jQuery} dt The <dt> element as a jQuery Object
+         * @param {jQuery} dd The <dd> element as a jQuery Object
+         * @param {String} key Property name
+         * @param {Object} value Property value
+         */
+        itemRenderer: function(dt, dd, key, value) {
+            dt.text(key);
+            dd.html(htmlize(value));
+        }
+
+    });
+
+    // ------------------------------------------------------------------
+    
+    /**
+     * An extension of KVListWidget where the data represents a list
+     * of variables
+     * 
+     * Options:
+     *  - data
+     */
+    var VariableListWidget = PhpDebugBar.Widgets.VariableListWidget = KVListWidget.extend({
+
+        className: csscls('kvlist varlist'),
+
+        itemRenderer: function(dt, dd, key, value) {
+            $('<span />').attr('title', key).text(key).appendTo(dt);
+
+            var v = value;
+            if (v && v.length > 100) {
+                v = v.substr(0, 100) + "...";
+            }
+            var prettyVal = null;
+            dd.text(v).click(function() {
+                if (dd.hasClass(csscls('pretty'))) {
+                    dd.text(v).removeClass(csscls('pretty'));
+                } else {
+                    prettyVal = prettyVal || createCodeBlock(value);
+                    dd.addClass(csscls('pretty')).empty().append(prettyVal);
+                }
+            });
+        }
+
+    });
+
+    // ------------------------------------------------------------------
+    
+    /**
+     * Iframe widget
+     *
+     * Options:
+     *  - data
+     */
+    var IFrameWidget = PhpDebugBar.Widgets.IFrameWidget = PhpDebugBar.Widget.extend({
+
+        tagName: 'iframe',
+
+        className: csscls('iframe'),
+
+        render: function() {
+            this.$el.attr({
+                seamless: "seamless",
+                border: "0",
+                width: "100%",
+                height: "100%"
+            });
+            this.bindAttr('data', function(url) { this.$el.attr('src', url); });
+        }
+
+    });
+
+
+    // ------------------------------------------------------------------
+    // Collector specific widgets
+    // ------------------------------------------------------------------
+
+    /**
+     * Widget for the MessagesCollector
+     *
+     * Uses ListWidget under the hood
+     *
+     * Options:
+     *  - data
+     */
+    var MessagesWidget = PhpDebugBar.Widgets.MessagesWidget = PhpDebugBar.Widget.extend({
+
+        className: csscls('messages'),
+
+        render: function() {
+            var self = this;
+
+            this.$list = new ListWidget({ itemRenderer: function(li, value) {
+                var m = value.message;
+                if (m.length > 100) {
+                    m = m.substr(0, 100) + "...";
+                }
+
+                var val = $('<span />').addClass(csscls('value')).text(m).appendTo(li);
+                if (!value.is_string || value.message.length > 100) {
+                    var prettyVal = value.message;
+                    if (!value.is_string) {
+                        prettyVal = null;
+                    }
+                    li.css('cursor', 'pointer').click(function() {
+                        if (val.hasClass(csscls('pretty'))) {
+                            val.text(m).removeClass(csscls('pretty'));
+                        } else {
+                            prettyVal = prettyVal || createCodeBlock(value.message, 'php');
+                            val.addClass(csscls('pretty')).empty().append(prettyVal);
+                        }
+                    });
+                }
+
+                if (value.label) {
+                    val.addClass(csscls(value.label));
+                    $('<span />').addClass(csscls('label')).text(value.label).appendTo(li);
+                }
+                if (value.collector) {
+                    $('<span />').addClass(csscls('collector')).text(value.collector).appendTo(li);
+                }
+            }});
+
+            this.$list.$el.appendTo(this.$el);
+            this.$toolbar = $('<div><i class="fa fa-search"></i></div>').addClass(csscls('toolbar')).appendTo(this.$el);
+
+            $('<input type="text" />')
+                .on('change', function() { self.set('search', this.value); })
+                .appendTo(this.$toolbar);
+
+            this.bindAttr('data', function(data) {
+                this.set({ exclude: [], search: '' });
+                this.$toolbar.find(csscls('.filter')).remove();
+
+                var filters = [], self = this;
+                for (var i = 0; i < data.length; i++) {
+                    if (!data[i].label || $.inArray(data[i].label, filters) > -1) {
+                        continue;
+                    }
+                    filters.push(data[i].label);
+                    $('<a />')
+                        .addClass(csscls('filter'))
+                        .text(data[i].label)
+                        .attr('rel', data[i].label)
+                        .on('click', function() { self.onFilterClick(this); })
+                        .appendTo(this.$toolbar);
+                }
+            });
+
+            this.bindAttr(['exclude', 'search'], function() {
+                var data = this.get('data'),
+                    exclude = this.get('exclude'),
+                    search = this.get('search'),
+                    caseless = false,
+                    fdata = [];
+
+                if (search && search === search.toLowerCase()) {
+                    caseless = true;
+                }
+
+                for (var i = 0; i < data.length; i++) {
+                    var message = caseless ? data[i].message.toLowerCase() : data[i].message;
+
+                    if ((!data[i].label || $.inArray(data[i].label, exclude) === -1) && (!search || message.indexOf(search) > -1)) {
+                        fdata.push(data[i]);
+                    }
+                }
+
+                this.$list.set('data', fdata);
+            });
+        },
+
+        onFilterClick: function(el) {
+            $(el).toggleClass(csscls('excluded'));
+
+            var excludedLabels = [];
+            this.$toolbar.find(csscls('.filter') + csscls('.excluded')).each(function() {
+                excludedLabels.push(this.rel);
+            });
+
+            this.set('exclude', excludedLabels);
+        }
+
+    });
+
+    // ------------------------------------------------------------------
+
+    /**
+     * Widget for the TimeDataCollector
+     *
+     * Options:
+     *  - data
+     */
+    var TimelineWidget = PhpDebugBar.Widgets.TimelineWidget = PhpDebugBar.Widget.extend({
+
+        tagName: 'ul',
+
+        className: csscls('timeline'),
+
+        render: function() {
+            this.bindAttr('data', function(data) {
+                this.$el.empty();
+                if (data.measures) {
+                    for (var i = 0; i < data.measures.length; i++) {
+                        var measure = data.measures[i];
+                        var m = $('<div />').addClass(csscls('measure')),
+                            li = $('<li />'),
+                            left = (measure.relative_start * 100 / data.duration).toFixed(2),
+                            width = Math.min((measure.duration * 100 / data.duration).toFixed(2), 100 - left);
+
+                        m.append($('<span />').addClass(csscls('value')).css({
+                            left: left + "%",
+                            width: width + "%"
+                        }));
+                        m.append($('<span />').addClass(csscls('label')).text(measure.label + " (" + measure.duration_str + ")"));
+
+                        if (measure.collector) {
+                            $('<span />').addClass(csscls('collector')).text(measure.collector).appendTo(m);
+                        }
+
+                        m.appendTo(li);
+                        this.$el.append(li);
+                        
+                        if (measure.params && !$.isEmptyObject(measure.params)) {
+                            var table = $('<table><tr><th colspan="2">Params</th></tr></table>').addClass(csscls('params')).appendTo(li);
+                            for (var key in measure.params) {
+                                if (typeof measure.params[key] !== 'function') {
+                                    table.append('<tr><td class="' + csscls('name') + '">' + key + '</td><td class="' + csscls('value') +
+                                    '"><pre><code>' + measure.params[key] + '</code></pre></td></tr>');
+                                }
+                            }
+                            li.css('cursor', 'pointer').click(function() {
+                                var table = $(this).find('table');
+                                if (table.is(':visible')) {
+                                    table.hide();
+                                } else {
+                                    table.show();
+                                }
+                            });
+                        }
+                    }
+                }
+            });
+        }
+
+    });
+
+    // ------------------------------------------------------------------
+    
+    /**
+     * Widget for the displaying exceptions
+     *
+     * Options:
+     *  - data
+     */
+    var ExceptionsWidget = PhpDebugBar.Widgets.ExceptionsWidget = PhpDebugBar.Widget.extend({
+
+        className: csscls('exceptions'),
+
+        render: function() {
+            this.$list = new ListWidget({ itemRenderer: function(li, e) {
+                $('<span />').addClass(csscls('message')).text(e.message).appendTo(li);
+                if (e.file) {
+                    $('<span />').addClass(csscls('filename')).text(e.file + "#" + e.line).appendTo(li);
+                }
+                if (e.type) {
+                    $('<span />').addClass(csscls('type')).text(e.type).appendTo(li);
+                }
+                if (e.surrounding_lines) {
+                    var pre = createCodeBlock(e.surrounding_lines.join(""), 'php').addClass(csscls('file')).appendTo(li);
+                    li.click(function() {
+                        if (pre.is(':visible')) {
+                            pre.hide();
+                        } else {
+                            pre.show();
+                        }
+                    });
+                }
+            }});
+            this.$list.$el.appendTo(this.$el);
+
+            this.bindAttr('data', function(data) {
+                this.$list.set('data', data);
+                if (data.length == 1) {
+                    this.$list.$el.children().first().find(csscls('.file')).show();
+                }
+            });
+
+        }
+
+    });
+    
+
+})(PhpDebugBar.$);

+ 12 - 0
htdocs/includes/DebugBar/Resources/widgets/mails/widget.css

@@ -0,0 +1,12 @@
+
+div.phpdebugbar-widgets-mails span.phpdebugbar-widgets-subject {
+  display: block;
+}
+
+div.phpdebugbar-widgets-mails li.phpdebugbar-widgets-list-item pre.phpdebugbar-widgets-headers {
+  display: none;
+  margin: 10px;
+  padding: 5px;
+  border: 1px solid #ddd;
+  font-family: monospace;
+}

+ 40 - 0
htdocs/includes/DebugBar/Resources/widgets/mails/widget.js

@@ -0,0 +1,40 @@
+(function($) {
+
+    var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-');
+
+    /**
+     * Widget for the displaying mails data
+     *
+     * Options:
+     *  - data
+     */
+    var MailsWidget = PhpDebugBar.Widgets.MailsWidget = PhpDebugBar.Widget.extend({
+
+        className: csscls('mails'),
+
+        render: function() {
+            this.$list = new  PhpDebugBar.Widgets.ListWidget({ itemRenderer: function(li, mail) {
+                $('<span />').addClass(csscls('subject')).text(mail.subject).appendTo(li);
+                $('<span />').addClass(csscls('to')).text(mail.to).appendTo(li);
+                if (mail.headers) {
+                    var headers = $('<pre />').addClass(csscls('headers')).appendTo(li);
+                    $('<code />').text(mail.headers).appendTo(headers);
+                    li.click(function() {
+                        if (headers.is(':visible')) {
+                            headers.hide();
+                        } else {
+                            headers.show();
+                        }
+                    });
+                }
+            }});
+            this.$list.$el.appendTo(this.$el);
+
+            this.bindAttr('data', function(data) {
+                this.$list.set('data', data);
+            });
+        }
+
+    });
+
+})(PhpDebugBar.$);

+ 82 - 0
htdocs/includes/DebugBar/Resources/widgets/sqlqueries/widget.css

@@ -0,0 +1,82 @@
+div.phpdebugbar-widgets-sqlqueries .phpdebugbar-widgets-status {
+  font-family: monospace;
+  padding: 6px 6px;
+  border-bottom: 1px solid #ddd;
+  font-weight: bold;
+  color: #555;
+  background: #fafafa;
+}
+
+div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-error {
+  color: red;
+}
+
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-duration,
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-memory,
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-row-count,
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-stmt-id {
+  float: right;
+  margin-left: 8px;
+  color: #888;
+}
+div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-duration,
+div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-memory,
+div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-row-count,
+div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-stmt-id {
+  color: #555;
+}
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-duration:before,
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-memory:before,
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-row-count:before,
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-stmt-id:before {
+  font-family: FontAwesome;
+  margin-right: 4px;
+  font-size: 12px;
+}
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-duration:before {
+  content: "\f017";
+}
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-memory:before {
+  content: "\f085";
+}
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-row-count:before {
+  content: "\f0ce";
+}
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-stmt-id:before {
+  content: "\f08d";
+}
+div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params {
+  display: none;
+  width: 70%;
+  margin: 10px;
+  border: 1px solid #ddd;
+  font-family: monospace;
+  border-collapse: collapse;
+}
+  div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td {
+    border: 1px solid #ddd;
+    text-align: center;
+  }
+  div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params .phpdebugbar-widgets-name {
+    width: 20%;
+    font-weight: bold;
+  }
+
+div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-error {
+  display: block;
+  font-weight: bold;
+}
+
+code.phpdebugbar-widgets-sql {
+  white-space: pre-wrap;
+  overflow-wrap: break-word;
+  word-wrap: break-word;
+}
+
+div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-sql-duplicate {
+  background-color: #edeff0;
+}
+
+div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-sql-duplicate:hover {
+  background-color: #ffc;
+}

+ 96 - 0
htdocs/includes/DebugBar/Resources/widgets/sqlqueries/widget.js

@@ -0,0 +1,96 @@
+(function($) {
+
+    var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-');
+
+    /**
+     * Widget for the displaying sql queries
+     *
+     * Options:
+     *  - data
+     */
+    var SQLQueriesWidget = PhpDebugBar.Widgets.SQLQueriesWidget = PhpDebugBar.Widget.extend({
+
+        className: csscls('sqlqueries'),
+
+        render: function() {
+            this.$status = $('<div />').addClass(csscls('status')).appendTo(this.$el);
+
+            this.$list = new  PhpDebugBar.Widgets.ListWidget({ itemRenderer: function(li, stmt) {
+                $('<code />').addClass(csscls('sql')).html(PhpDebugBar.Widgets.highlight(stmt.sql, 'sql')).appendTo(li);
+                if (stmt.duration_str) {
+                    $('<span title="Duration" />').addClass(csscls('duration')).text(stmt.duration_str).appendTo(li);
+                }
+                if (stmt.memory_str) {
+                    $('<span title="Memory usage" />').addClass(csscls('memory')).text(stmt.memory_str).appendTo(li);
+                }
+                if (typeof(stmt.is_success) != 'undefined' && !stmt.is_success) {
+                    li.addClass(csscls('error'));
+                    li.append($('<span />').addClass(csscls('error')).text("[" + stmt.error_code + "] " + stmt.error_message));
+                } else if (typeof(stmt.row_count) != 'undefined') {
+                    $('<span title="Row count" />').addClass(csscls('row-count')).text(stmt.row_count).appendTo(li);
+                }
+                if (typeof(stmt.stmt_id) != 'undefined' && stmt.stmt_id) {
+                    $('<span title="Prepared statement ID" />').addClass(csscls('stmt-id')).text(stmt.stmt_id).appendTo(li);
+                }
+                if (stmt.params && !$.isEmptyObject(stmt.params)) {
+                    var table = $('<table><tr><th colspan="2">Params</th></tr></table>').addClass(csscls('params')).appendTo(li);
+                    for (var key in stmt.params) {
+                        if (typeof stmt.params[key] !== 'function') {
+                            table.append('<tr><td class="' + csscls('name') + '">' + key + '</td><td class="' + csscls('value') +
+                            '">' + stmt.params[key] + '</td></tr>');
+                        }
+                    }
+                    li.css('cursor', 'pointer').click(function() {
+                        if (table.is(':visible')) {
+                            table.hide();
+                        } else {
+                            table.show();
+                        }
+                    });
+                }
+            }});
+            this.$list.$el.appendTo(this.$el);
+
+            this.bindAttr('data', function(data) {
+                this.$list.set('data', data.statements);
+                this.$status.empty();
+
+                // Search for duplicate statements.
+                for (var sql = {}, duplicate = 0, i = 0; i < data.statements.length; i++) {
+                    var stmt = data.statements[i].sql;
+                    if (data.statements[i].params && !$.isEmptyObject(data.statements[i].params)) {
+                        stmt += ' {' + $.param(data.statements[i].params, false) + '}';
+                    }
+                    sql[stmt] = sql[stmt] || { keys: [] };
+                    sql[stmt].keys.push(i);
+                }
+                // Add classes to all duplicate SQL statements.
+                for (var stmt in sql) {
+                    if (sql[stmt].keys.length > 1) {
+                        duplicate++;
+                        for (var i = 0; i < sql[stmt].keys.length; i++) {
+                            this.$list.$el.find('.' + csscls('list-item')).eq(sql[stmt].keys[i])
+                                .addClass(csscls('sql-duplicate')).addClass(csscls('sql-duplicate-'+duplicate));
+                        }
+                    }
+                }
+
+                var t = $('<span />').text(data.nb_statements + " statements were executed").appendTo(this.$status);
+                if (data.nb_failed_statements) {
+                    t.append(", " + data.nb_failed_statements + " of which failed");
+                }
+                if (duplicate) {
+                    t.append(", " + duplicate + " of which were duplicated");
+                }
+                if (data.accumulated_duration_str) {
+                    this.$status.append($('<span title="Accumulated duration" />').addClass(csscls('duration')).text(data.accumulated_duration_str));
+                }
+                if (data.memory_usage_str) {
+                    this.$status.append($('<span title="Memory usage" />').addClass(csscls('memory')).text(data.memory_usage_str));
+                }
+            });
+        }
+
+    });
+
+})(PhpDebugBar.$);

+ 60 - 0
htdocs/includes/DebugBar/Resources/widgets/templates/widget.css

@@ -0,0 +1,60 @@
+
+div.phpdebugbar-widgets-templates div.phpdebugbar-widgets-status {
+  font-family: monospace;
+  padding: 6px 6px;
+  border-bottom: 1px solid #ddd;
+  font-weight: bold;
+  color: #555;
+  background: #fafafa;
+}
+
+div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-render-time,
+div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-memory,
+div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-param-count,
+div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-type {
+  float: right;
+  margin-left: 8px;
+  color: #888;
+}
+div.phpdebugbar-widgets-templates div.phpdebugbar-widgets-status span.phpdebugbar-widgets-render-time,
+div.phpdebugbar-widgets-templates div.phpdebugbar-widgets-status span.phpdebugbar-widgets-memory,
+div.phpdebugbar-widgets-templates div.phpdebugbar-widgets-status span.phpdebugbar-widgets-param-count,
+div.phpdebugbar-widgets-templates div.phpdebugbar-widgets-status span.phpdebugbar-widgets-type {
+  color: #555;
+}
+div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-render-time:before,
+div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-memory:before,
+div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-param-count:before,
+div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-type:before {
+  font-family: FontAwesome;
+  margin-right: 4px;
+  font-size: 12px;
+}
+div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-render-time:before {
+  content: "\f017";
+}
+div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-memory:before {
+  content: "\f085";
+}
+div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-param-count:before {
+  content: "\f0ce";
+}
+div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-type:before {
+  content: "\f121";
+}
+div.phpdebugbar-widgets-templates table.phpdebugbar-widgets-params {
+  display: none;
+  width: 70%;
+  margin: 10px;
+  border: 1px solid #ddd;
+  font-family: monospace;
+  border-collapse: collapse;
+}
+div.phpdebugbar-widgets-templates table.phpdebugbar-widgets-params td {
+  border: 1px solid #ddd;
+  padding: 0 5px;
+}
+div.phpdebugbar-widgets-templates table.phpdebugbar-widgets-params .phpdebugbar-widgets-name {
+  width: 20%;
+  font-weight: bold;
+}

+ 69 - 0
htdocs/includes/DebugBar/Resources/widgets/templates/widget.js

@@ -0,0 +1,69 @@
+(function($) {
+
+    var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-');
+
+    /**
+     * Widget for the displaying templates data
+     *
+     * Options:
+     *  - data
+     */
+    var TemplatesWidget = PhpDebugBar.Widgets.TemplatesWidget = PhpDebugBar.Widget.extend({
+
+        className: csscls('templates'),
+
+        render: function() {
+            this.$status = $('<div />').addClass(csscls('status')).appendTo(this.$el);
+
+            this.$list = new  PhpDebugBar.Widgets.ListWidget({ itemRenderer: function(li, tpl) {
+                $('<span />').addClass(csscls('name')).text(tpl.name).appendTo(li);
+                if (tpl.render_time_str) {
+                    $('<span title="Render time" />').addClass(csscls('render-time')).text(tpl.render_time_str).appendTo(li);
+                }
+                if (tpl.memory_str) {
+                    $('<span title="Memory usage" />').addClass(csscls('memory')).text(tpl.memory_str).appendTo(li);
+                }
+                if (typeof(tpl.param_count) != 'undefined') {
+                    $('<span title="Parameter count" />').addClass(csscls('param-count')).text(tpl.param_count).appendTo(li);
+                }
+                if (typeof(tpl.type) != 'undefined' && tpl.type) {
+                    $('<span title="Type" />').addClass(csscls('type')).text(tpl.type).appendTo(li);
+                }
+                if (tpl.params && !$.isEmptyObject(tpl.params)) {
+                    var table = $('<table><tr><th colspan="2">Params</th></tr></table>').addClass(csscls('params')).appendTo(li);
+                    for (var key in tpl.params) {
+                        if (typeof tpl.params[key] !== 'function') {
+                            table.append('<tr><td class="' + csscls('name') + '">' + key + '</td><td class="' + csscls('value') +
+                            '"><pre><code>' + tpl.params[key] + '</code></pre></td></tr>');
+                        }
+                    }
+                    li.css('cursor', 'pointer').click(function() {
+                        if (table.is(':visible')) {
+                            table.hide();
+                        } else {
+                            table.show();
+                        }
+                    });
+                }
+            }});
+            this.$list.$el.appendTo(this.$el);
+
+            this.bindAttr('data', function(data) {
+                this.$list.set('data', data.templates);
+                this.$status.empty();
+
+                var sentence = data.sentence || "templates were rendered";
+                $('<span />').text(data.templates.length + " " + sentence).appendTo(this.$status);
+
+                if (data.accumulated_render_time_str) {
+                    this.$status.append($('<span title="Accumulated render time" />').addClass(csscls('render-time')).text(data.accumulated_render_time_str));
+                }
+                if (data.memory_usage_str) {
+                    this.$status.append($('<span title="Memory usage" />').addClass(csscls('memory')).text(data.memory_usage_str));
+                }
+            });
+        }
+
+    });
+
+})(PhpDebugBar.$);

+ 34 - 0
htdocs/includes/DebugBar/StandardDebugBar.php

@@ -0,0 +1,34 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar;
+
+use DebugBar\DataCollector\ExceptionsCollector;
+use DebugBar\DataCollector\MemoryCollector;
+use DebugBar\DataCollector\MessagesCollector;
+use DebugBar\DataCollector\PhpInfoCollector;
+use DebugBar\DataCollector\RequestDataCollector;
+use DebugBar\DataCollector\TimeDataCollector;
+
+/**
+ * Debug bar subclass which adds all included collectors
+ */
+class StandardDebugBar extends DebugBar
+{
+    public function __construct()
+    {
+        $this->addCollector(new PhpInfoCollector());
+        $this->addCollector(new MessagesCollector());
+        $this->addCollector(new RequestDataCollector());
+        $this->addCollector(new TimeDataCollector());
+        $this->addCollector(new MemoryCollector());
+        $this->addCollector(new ExceptionsCollector());
+    }
+}

+ 108 - 0
htdocs/includes/DebugBar/Storage/FileStorage.php

@@ -0,0 +1,108 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Storage;
+
+/**
+ * Stores collected data into files
+ */
+class FileStorage implements StorageInterface
+{
+    protected $dirname;
+
+    /**
+     * @param string $dirname Directories where to store files
+     */
+    public function __construct($dirname)
+    {
+        $this->dirname = rtrim($dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+    }
+
+    public function save($id, $data)
+    {
+        if (!file_exists($this->dirname)) {
+            mkdir($this->dirname, 0777, true);
+        }
+        file_put_contents($this->makeFilename($id), json_encode($data));
+    }
+
+    public function get($id)
+    {
+        return json_decode(file_get_contents($this->makeFilename($id)), true);
+    }
+
+    public function find(array $filters = array(), $max = 20, $offset = 0)
+    {
+        //Loop through all .json files and remember the modified time and id.
+        $files = array();
+        foreach (new \DirectoryIterator($this->dirname) as $file) {
+            if ($file->getExtension() == 'json') {
+                $files[] = array(
+                    'time' => $file->getMTime(),
+                    'id' => $file->getBasename('.json')
+                );
+            }
+        }
+
+        //Sort the files, newest first
+        usort($files, function ($a, $b) {
+                return $a['time'] < $b['time'];
+            });
+
+        //Load the metadata and filter the results.
+        $results = array();
+        $i = 0;
+        foreach ($files as $file) {
+            //When filter is empty, skip loading the offset
+            if ($i++ < $offset && empty($filters)) {
+                $results[] = null;
+                continue;
+            }
+            $data = $this->get($file['id']);
+            $meta = $data['__meta'];
+            unset($data);
+            if ($this->filter($meta, $filters)) {
+                $results[] = $meta;
+            }
+            if (count($results) >= ($max + $offset)) {
+                break;
+            }
+        }
+
+        return array_slice($results, $offset, $max);
+    }
+
+    /**
+     * Filter the metadata for matches.
+     */
+    protected function filter($meta, $filters)
+    {
+        foreach ($filters as $key => $value) {
+            if (!isset($meta[$key]) || fnmatch($value, $meta[$key]) === false) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public function clear()
+    {
+        foreach (new \DirectoryIterator($this->dirname) as $file) {
+            if (substr($file->getFilename(), 0, 1) !== '.') {
+                unlink($file->getPathname());
+            }
+        }
+    }
+
+    public function makeFilename($id)
+    {
+        return $this->dirname . basename($id). ".json";
+    }
+}

+ 91 - 0
htdocs/includes/DebugBar/Storage/MemcachedStorage.php

@@ -0,0 +1,91 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Storage;
+
+use Memcached;
+
+/**
+ * Stores collected data into Memcache using the Memcached extension
+ */
+class MemcachedStorage implements StorageInterface
+{
+    protected $memcached;
+
+    protected $keyNamespace;
+
+    /**
+     * @param Memcached $memcached
+     */
+    public function __construct(Memcached $memcached, $keyNamespace = 'phpdebugbar')
+    {
+        $this->memcached = $memcached;
+        $this->keyNamespace = $keyNamespace;
+    }
+
+    public function save($id, $data)
+    {
+        $key = $this->createKey($id);
+        $this->memcached->set($key, $data);
+        if (!$this->memcached->append($this->keyNamespace, "|$key")) {
+            $this->memcached->set($this->keyNamespace, $key);
+        }
+    }
+
+    public function get($id)
+    {
+        return $this->memcached->get($this->createKey($id));
+    }
+
+    public function find(array $filters = array(), $max = 20, $offset = 0)
+    {
+        if (!($keys = $this->memcached->get($this->keyNamespace))) {
+            return array();
+        }
+
+        $results = array();
+        foreach (explode('|', $keys) as $key) {
+            if ($data = $this->memcached->get($key)) {
+                $meta = $data['__meta'];
+                if ($this->filter($meta, $filters)) {
+                    $results[] = $meta;
+                }
+            }
+        }
+        return array_slice($results, $offset, $max);
+    }
+
+    /**
+     * Filter the metadata for matches.
+     */
+    protected function filter($meta, $filters)
+    {
+        foreach ($filters as $key => $value) {
+            if (!isset($meta[$key]) || fnmatch($value, $meta[$key]) === false) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public function clear()
+    {
+        if (!($keys = $this->memcached->get($this->keyNamespace))) {
+            return;
+        }
+        $this->memcached->delete($this->keyNamespace);
+        $this->memcached->deleteMulti(explode('|', $keys));
+    }
+
+    protected function createKey($id)
+    {
+        return md5("{$this->keyNamespace}.$id");
+    }
+}

+ 117 - 0
htdocs/includes/DebugBar/Storage/PdoStorage.php

@@ -0,0 +1,117 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Storage;
+
+use PDO;
+
+/**
+ * Stores collected data into a database using PDO
+ */
+class PdoStorage implements StorageInterface
+{
+    protected $pdo;
+
+    protected $tableName;
+
+    protected $sqlQueries = array(
+        'save' => "INSERT INTO %tablename% (id, data, meta_utime, meta_datetime, meta_uri, meta_ip, meta_method) VALUES (?, ?, ?, ?, ?, ?, ?)",
+        'get' => "SELECT data FROM %tablename% WHERE id = ?",
+        'find' => "SELECT data FROM %tablename% %where% LIMIT %limit% OFFSET %offset%",
+        'clear' => "DELETE FROM %tablename%"
+    );
+
+    /**
+     * @param string $dirname Directories where to store files
+     * @param array $sqlQueries
+     */
+    public function __construct(PDO $pdo, $tableName = 'phpdebugbar', array $sqlQueries = array())
+    {
+        $this->pdo = $pdo;
+        $this->tableName = $tableName;
+        $this->setSqlQueries($sqlQueries);
+    }
+
+    /**
+     * Sets the sql queries to be used
+     *
+     * @param array $queries
+     */
+    public function setSqlQueries(array $queries)
+    {
+        $this->sqlQueries = array_merge($this->sqlQueries, $queries);
+    }
+
+    public function save($id, $data)
+    {
+        $sql = $this->getSqlQuery('save');
+        $stmt = $this->pdo->prepare($sql);
+        $meta = $data['__meta'];
+        $stmt->execute(array($id, serialize($data), $meta['utime'], $meta['datetime'], $meta['uri'], $meta['ip'], $meta['method']));
+    }
+
+    public function get($id)
+    {
+        $sql = $this->getSqlQuery('get');
+        $stmt = $this->pdo->prepare($sql);
+        $stmt->execute(array($id));
+        if (($data = $stmt->fetchColumn(0)) !== false) {
+            return unserialize($data);
+        }
+        return null;
+    }
+
+    public function find(array $filters = array(), $max = 20, $offset = 0)
+    {
+        $where = array();
+        $params = array();
+        foreach ($filters as $key => $value) {
+            $where[] = "meta_$key = ?";
+            $params[] = $value;
+        }
+        if (count($where)) {
+            $where = " WHERE " . implode(' AND ', $where);
+        } else {
+            $where = '';
+        }
+
+        $sql = $this->getSqlQuery('find', array(
+            'where' => $where,
+            'offset' => $offset,
+            'limit' => $max
+        ));
+
+        $stmt = $this->pdo->prepare($sql);
+        $stmt->execute($params);
+
+        $results = array();
+        foreach ($stmt->fetchAll() as $row) {
+            $data = unserialize($row['data']);
+            $results[] = $data['__meta'];
+            unset($data);
+        }
+        return $results;
+    }
+
+    public function clear()
+    {
+        $this->pdo->exec($this->getSqlQuery('clear'));
+    }
+
+    protected function getSqlQuery($name, array $vars = array())
+    {
+        $sql = $this->sqlQueries[$name];
+        $vars = array_merge(array('tablename' => $this->tableName), $vars);
+        foreach ($vars as $k => $v) {
+            $sql = str_replace("%$k%", $v, $sql);
+        }
+        return $sql;
+    }
+}

+ 74 - 0
htdocs/includes/DebugBar/Storage/RedisStorage.php

@@ -0,0 +1,74 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Storage;
+
+use Predis\Client;
+
+/**
+ * Stores collected data into Redis
+ */
+class RedisStorage implements StorageInterface
+{
+    protected $redis;
+
+    protected $hash;
+
+    /**
+     * @param string $dirname Directories where to store files
+     */
+    public function __construct(Client $redis, $hash = 'phpdebugbar')
+    {
+        $this->redis = $redis;
+        $this->hash = $hash;
+    }
+
+    public function save($id, $data)
+    {
+        $this->redis->hset($this->hash, $id, serialize($data));
+    }
+
+    public function get($id)
+    {
+        return unserialize($this->redis->hget($this->hash, $id));
+    }
+
+    public function find(array $filters = array(), $max = 20, $offset = 0)
+    {
+        $results = array();
+        foreach ($this->redis->hgetall($this->hash) as $id => $data) {
+            if ($data = unserialize($data)) {
+                $meta = $data['__meta'];
+                if ($this->filter($meta, $filters)) {
+                    $results[] = $meta;
+                }
+            }
+        }
+        return array_slice($results, $offset, $max);
+    }
+
+    /**
+     * Filter the metadata for matches.
+     */
+    protected function filter($meta, $filters)
+    {
+        foreach ($filters as $key => $value) {
+            if (!isset($meta[$key]) || fnmatch($value, $meta[$key]) === false) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public function clear()
+    {
+        $this->redis->del($this->hash);
+    }
+}

+ 45 - 0
htdocs/includes/DebugBar/Storage/StorageInterface.php

@@ -0,0 +1,45 @@
+<?php
+/*
+ * This file is part of the DebugBar package.
+ *
+ * (c) 2013 Maxime Bouroumeau-Fuseau
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace DebugBar\Storage;
+
+interface StorageInterface
+{
+    /**
+     * Saves collected data
+     *
+     * @param string $id
+     * @param string $data
+     */
+    function save($id, $data);
+
+    /**
+     * Returns collected data with the specified id
+     *
+     * @param string $id
+     * @return array
+     */
+    function get($id);
+
+    /**
+     * Returns a metadata about collected data
+     *
+     * @param array $filters
+     * @param integer $max
+     * @param integer $offset
+     * @return array
+     */
+    function find(array $filters = array(), $max = 20, $offset = 0);
+
+    /**
+     * Clears all the collected data
+     */
+    function clear();
+}

+ 16 - 0
htdocs/includes/DebugBar/Storage/pdo_storage_schema.sql

@@ -0,0 +1,16 @@
+CREATE TABLE phpdebugbar (
+    id TEXT PRIMARY KEY,
+    data TEXT,
+    meta_utime TEXT,
+    meta_datetime TEXT,
+    meta_uri TEXT,
+    meta_ip TEXT,
+    meta_method TEXT
+);
+
+CREATE INDEX idx_debugbar_id ON phpdebugbar (id);
+CREATE INDEX idx_debugbar_meta_utime ON phpdebugbar (meta_utime);
+CREATE INDEX idx_debugbar_meta_datetime ON phpdebugbar (meta_datetime);
+CREATE INDEX idx_debugbar_meta_uri ON phpdebugbar (meta_uri);
+CREATE INDEX idx_debugbar_meta_ip ON phpdebugbar (meta_ip);
+CREATE INDEX idx_debugbar_meta_method ON phpdebugbar (meta_method);

+ 128 - 0
htdocs/includes/Psr/Log/AbstractLogger.php

@@ -0,0 +1,128 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * This is a simple Logger implementation that other Loggers can inherit from.
+ *
+ * It simply delegates all log-level-specific methods to the `log` method to
+ * reduce boilerplate code that a simple Logger that does the same thing with
+ * messages regardless of the error level has to implement.
+ */
+abstract class AbstractLogger implements LoggerInterface
+{
+    /**
+     * System is unusable.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function emergency($message, array $context = array())
+    {
+        $this->log(LogLevel::EMERGENCY, $message, $context);
+    }
+
+    /**
+     * Action must be taken immediately.
+     *
+     * Example: Entire website down, database unavailable, etc. This should
+     * trigger the SMS alerts and wake you up.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function alert($message, array $context = array())
+    {
+        $this->log(LogLevel::ALERT, $message, $context);
+    }
+
+    /**
+     * Critical conditions.
+     *
+     * Example: Application component unavailable, unexpected exception.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function critical($message, array $context = array())
+    {
+        $this->log(LogLevel::CRITICAL, $message, $context);
+    }
+
+    /**
+     * Runtime errors that do not require immediate action but should typically
+     * be logged and monitored.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function error($message, array $context = array())
+    {
+        $this->log(LogLevel::ERROR, $message, $context);
+    }
+
+    /**
+     * Exceptional occurrences that are not errors.
+     *
+     * Example: Use of deprecated APIs, poor use of an API, undesirable things
+     * that are not necessarily wrong.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function warning($message, array $context = array())
+    {
+        $this->log(LogLevel::WARNING, $message, $context);
+    }
+
+    /**
+     * Normal but significant events.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function notice($message, array $context = array())
+    {
+        $this->log(LogLevel::NOTICE, $message, $context);
+    }
+
+    /**
+     * Interesting events.
+     *
+     * Example: User logs in, SQL logs.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function info($message, array $context = array())
+    {
+        $this->log(LogLevel::INFO, $message, $context);
+    }
+
+    /**
+     * Detailed debug information.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function debug($message, array $context = array())
+    {
+        $this->log(LogLevel::DEBUG, $message, $context);
+    }
+}

+ 7 - 0
htdocs/includes/Psr/Log/InvalidArgumentException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Psr\Log;
+
+class InvalidArgumentException extends \InvalidArgumentException
+{
+}

+ 18 - 0
htdocs/includes/Psr/Log/LogLevel.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes log levels.
+ */
+class LogLevel
+{
+    const EMERGENCY = 'emergency';
+    const ALERT     = 'alert';
+    const CRITICAL  = 'critical';
+    const ERROR     = 'error';
+    const WARNING   = 'warning';
+    const NOTICE    = 'notice';
+    const INFO      = 'info';
+    const DEBUG     = 'debug';
+}

+ 18 - 0
htdocs/includes/Psr/Log/LoggerAwareInterface.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes a logger-aware instance.
+ */
+interface LoggerAwareInterface
+{
+    /**
+     * Sets a logger instance on the object.
+     *
+     * @param LoggerInterface $logger
+     *
+     * @return void
+     */
+    public function setLogger(LoggerInterface $logger);
+}

+ 26 - 0
htdocs/includes/Psr/Log/LoggerAwareTrait.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Basic Implementation of LoggerAwareInterface.
+ */
+trait LoggerAwareTrait
+{
+    /**
+     * The logger instance.
+     *
+     * @var LoggerInterface
+     */
+    protected $logger;
+
+    /**
+     * Sets a logger.
+     *
+     * @param LoggerInterface $logger
+     */
+    public function setLogger(LoggerInterface $logger)
+    {
+        $this->logger = $logger;
+    }
+}

+ 123 - 0
htdocs/includes/Psr/Log/LoggerInterface.php

@@ -0,0 +1,123 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes a logger instance.
+ *
+ * The message MUST be a string or object implementing __toString().
+ *
+ * The message MAY contain placeholders in the form: {foo} where foo
+ * will be replaced by the context data in key "foo".
+ *
+ * The context array can contain arbitrary data. The only assumption that
+ * can be made by implementors is that if an Exception instance is given
+ * to produce a stack trace, it MUST be in a key named "exception".
+ *
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
+ * for the full interface specification.
+ */
+interface LoggerInterface
+{
+    /**
+     * System is unusable.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function emergency($message, array $context = array());
+
+    /**
+     * Action must be taken immediately.
+     *
+     * Example: Entire website down, database unavailable, etc. This should
+     * trigger the SMS alerts and wake you up.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function alert($message, array $context = array());
+
+    /**
+     * Critical conditions.
+     *
+     * Example: Application component unavailable, unexpected exception.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function critical($message, array $context = array());
+
+    /**
+     * Runtime errors that do not require immediate action but should typically
+     * be logged and monitored.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function error($message, array $context = array());
+
+    /**
+     * Exceptional occurrences that are not errors.
+     *
+     * Example: Use of deprecated APIs, poor use of an API, undesirable things
+     * that are not necessarily wrong.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function warning($message, array $context = array());
+
+    /**
+     * Normal but significant events.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function notice($message, array $context = array());
+
+    /**
+     * Interesting events.
+     *
+     * Example: User logs in, SQL logs.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function info($message, array $context = array());
+
+    /**
+     * Detailed debug information.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function debug($message, array $context = array());
+
+    /**
+     * Logs with an arbitrary level.
+     *
+     * @param mixed  $level
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function log($level, $message, array $context = array());
+}

+ 140 - 0
htdocs/includes/Psr/Log/LoggerTrait.php

@@ -0,0 +1,140 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * This is a simple Logger trait that classes unable to extend AbstractLogger
+ * (because they extend another class, etc) can include.
+ *
+ * It simply delegates all log-level-specific methods to the `log` method to
+ * reduce boilerplate code that a simple Logger that does the same thing with
+ * messages regardless of the error level has to implement.
+ */
+trait LoggerTrait
+{
+    /**
+     * System is unusable.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function emergency($message, array $context = array())
+    {
+        $this->log(LogLevel::EMERGENCY, $message, $context);
+    }
+
+    /**
+     * Action must be taken immediately.
+     *
+     * Example: Entire website down, database unavailable, etc. This should
+     * trigger the SMS alerts and wake you up.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function alert($message, array $context = array())
+    {
+        $this->log(LogLevel::ALERT, $message, $context);
+    }
+
+    /**
+     * Critical conditions.
+     *
+     * Example: Application component unavailable, unexpected exception.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function critical($message, array $context = array())
+    {
+        $this->log(LogLevel::CRITICAL, $message, $context);
+    }
+
+    /**
+     * Runtime errors that do not require immediate action but should typically
+     * be logged and monitored.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function error($message, array $context = array())
+    {
+        $this->log(LogLevel::ERROR, $message, $context);
+    }
+
+    /**
+     * Exceptional occurrences that are not errors.
+     *
+     * Example: Use of deprecated APIs, poor use of an API, undesirable things
+     * that are not necessarily wrong.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function warning($message, array $context = array())
+    {
+        $this->log(LogLevel::WARNING, $message, $context);
+    }
+
+    /**
+     * Normal but significant events.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function notice($message, array $context = array())
+    {
+        $this->log(LogLevel::NOTICE, $message, $context);
+    }
+
+    /**
+     * Interesting events.
+     *
+     * Example: User logs in, SQL logs.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function info($message, array $context = array())
+    {
+        $this->log(LogLevel::INFO, $message, $context);
+    }
+
+    /**
+     * Detailed debug information.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function debug($message, array $context = array())
+    {
+        $this->log(LogLevel::DEBUG, $message, $context);
+    }
+
+    /**
+     * Logs with an arbitrary level.
+     *
+     * @param mixed  $level
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    abstract public function log($level, $message, array $context = array());
+}

+ 28 - 0
htdocs/includes/Psr/Log/NullLogger.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * This Logger can be used to avoid conditional log calls.
+ *
+ * Logging should always be optional, and if no logger is provided to your
+ * library creating a NullLogger instance to have something to throw logs at
+ * is a good way to avoid littering your code with `if ($this->logger) { }`
+ * blocks.
+ */
+class NullLogger extends AbstractLogger
+{
+    /**
+     * Logs with an arbitrary level.
+     *
+     * @param mixed  $level
+     * @param string $message
+     * @param array  $context
+     *
+     * @return void
+     */
+    public function log($level, $message, array $context = array())
+    {
+        // noop
+    }
+}

+ 140 - 0
htdocs/includes/Psr/Log/Test/LoggerInterfaceTest.php

@@ -0,0 +1,140 @@
+<?php
+
+namespace Psr\Log\Test;
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+
+/**
+ * Provides a base test class for ensuring compliance with the LoggerInterface.
+ *
+ * Implementors can extend the class and implement abstract methods to run this
+ * as part of their test suite.
+ */
+abstract class LoggerInterfaceTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @return LoggerInterface
+     */
+    abstract public function getLogger();
+
+    /**
+     * This must return the log messages in order.
+     *
+     * The simple formatting of the messages is: "<LOG LEVEL> <MESSAGE>".
+     *
+     * Example ->error('Foo') would yield "error Foo".
+     *
+     * @return string[]
+     */
+    abstract public function getLogs();
+
+    public function testImplements()
+    {
+        $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
+    }
+
+    /**
+     * @dataProvider provideLevelsAndMessages
+     */
+    public function testLogsAtAllLevels($level, $message)
+    {
+        $logger = $this->getLogger();
+        $logger->{$level}($message, array('user' => 'Bob'));
+        $logger->log($level, $message, array('user' => 'Bob'));
+
+        $expected = array(
+            $level.' message of level '.$level.' with context: Bob',
+            $level.' message of level '.$level.' with context: Bob',
+        );
+        $this->assertEquals($expected, $this->getLogs());
+    }
+
+    public function provideLevelsAndMessages()
+    {
+        return array(
+            LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
+            LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
+            LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
+            LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
+            LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
+            LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
+            LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
+            LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
+        );
+    }
+
+    /**
+     * @expectedException \Psr\Log\InvalidArgumentException
+     */
+    public function testThrowsOnInvalidLevel()
+    {
+        $logger = $this->getLogger();
+        $logger->log('invalid level', 'Foo');
+    }
+
+    public function testContextReplacement()
+    {
+        $logger = $this->getLogger();
+        $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
+
+        $expected = array('info {Message {nothing} Bob Bar a}');
+        $this->assertEquals($expected, $this->getLogs());
+    }
+
+    public function testObjectCastToString()
+    {
+        if (method_exists($this, 'createPartialMock')) {
+            $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
+        } else {
+            $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
+        }
+        $dummy->expects($this->once())
+            ->method('__toString')
+            ->will($this->returnValue('DUMMY'));
+
+        $this->getLogger()->warning($dummy);
+
+        $expected = array('warning DUMMY');
+        $this->assertEquals($expected, $this->getLogs());
+    }
+
+    public function testContextCanContainAnything()
+    {
+        $context = array(
+            'bool' => true,
+            'null' => null,
+            'string' => 'Foo',
+            'int' => 0,
+            'float' => 0.5,
+            'nested' => array('with object' => new DummyTest),
+            'object' => new \DateTime,
+            'resource' => fopen('php://memory', 'r'),
+        );
+
+        $this->getLogger()->warning('Crazy context data', $context);
+
+        $expected = array('warning Crazy context data');
+        $this->assertEquals($expected, $this->getLogs());
+    }
+
+    public function testContextExceptionKeyCanBeExceptionOrOtherValues()
+    {
+        $logger = $this->getLogger();
+        $logger->warning('Random message', array('exception' => 'oops'));
+        $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
+
+        $expected = array(
+            'warning Random message',
+            'critical Uncaught Exception!'
+        );
+        $this->assertEquals($expected, $this->getLogs());
+    }
+}
+
+class DummyTest
+{
+    public function __toString()
+    {
+    }
+}

+ 6 - 1
htdocs/langs/en_US/admin.lang

@@ -1862,4 +1862,9 @@ ThisValueCanOverwrittenOnUserLevel=This value can be overwritten by each user fr
 DefaultCustomerType=Default thirdparty type for "New customer" creation form
 ABankAccountMustBeDefinedOnPaymentModeSetup=Note: The bank account must be defined on the module of each payment mode (Paypal, Stripe, ...) to have this feature working.
 RootCategoryForProductsToSell=Root category of products to sell
-RootCategoryForProductsToSellDesc=If defined, only products inside this category or childs of this category will be available in the Point Of Sale
+RootCategoryForProductsToSellDesc=If defined, only products inside this category or childs of this category will be available in the Point Of Sale
+DebugBar=Debug Bar
+DebugBarDesc=Toolbar that comes with a plenty of tools to simplify debugging
+GeneralOptions=General Options
+LogsLinesNumber=Number of lines to show on logs tab
+UseDebugBar=Use the debug bar

+ 2 - 2
htdocs/modulebuilder/template/core/modules/modMyModule.class.php

@@ -143,8 +143,8 @@ class modMyModule extends DolibarrModules
 
         // Constants
         // List of particular constants to add when module is enabled (key, 'chaine', value, desc, visible, 'current' or 'allentities', deleteonunactive)
-        // Example: $this->const=array(0=>array('MYMODULE_MYNEWCONST1','chaine','myvalue','This is a constant to add',1),
-        //                             1=>array('MYMODULE_MYNEWCONST2','chaine','myvalue','This is another constant to add',0, 'current', 1)
+        // Example: $this->const=array(1 => array('MYMODULE_MYNEWCONST1', 'chaine', 'myvalue', 'This is a constant to add', 1),
+        //                             2 => array('MYMODULE_MYNEWCONST2', 'chaine', 'myvalue', 'This is another constant to add', 0, 'current', 1)
         // );
         $this->const = array(
             1 => array('MYMODULE_MYCONSTANT', 'chaine', 'avalue', 'This is a constant to add', 1, 'allentities', 1)

BIN
htdocs/theme/eldy/img/object_debugbar.png


BIN
htdocs/theme/md/img/object_debugbar.png


Some files were not shown because too many files changed in this diff