diff --git a/htdocs/core/modules/modDav.class.php b/htdocs/core/modules/modDav.class.php
index 55378e6b5fb..676b8e9b8bc 100644
--- a/htdocs/core/modules/modDav.class.php
+++ b/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
*/
diff --git a/htdocs/core/modules/modDebugBar.class.php b/htdocs/core/modules/modDebugBar.class.php
new file mode 100644
index 00000000000..84c18d1b220
--- /dev/null
+++ b/htdocs/core/modules/modDebugBar.class.php
@@ -0,0 +1,133 @@
+.
+ */
+
+/**
+ * \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'
+ ));
+ }
+}
diff --git a/htdocs/debugbar/class/DataCollector/DolConfigCollector.php b/htdocs/debugbar/class/DataCollector/DolConfigCollector.php
new file mode 100644
index 00000000000..c047f36a5ce
--- /dev/null
+++ b/htdocs/debugbar/class/DataCollector/DolConfigCollector.php
@@ -0,0 +1,81 @@
+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;
+ }
+}
\ No newline at end of file
diff --git a/htdocs/debugbar/class/DataCollector/DolExceptionsCollector.php b/htdocs/debugbar/class/DataCollector/DolExceptionsCollector.php
new file mode 100644
index 00000000000..a39d6189792
--- /dev/null
+++ b/htdocs/debugbar/class/DataCollector/DolExceptionsCollector.php
@@ -0,0 +1,34 @@
+transnoentities('Exceptions');
+
+ return array(
+ "$title" => array(
+ 'icon' => 'bug',
+ 'widget' => 'PhpDebugBar.Widgets.ExceptionsWidget',
+ 'map' => 'exceptions.exceptions',
+ 'default' => '[]'
+ ),
+ "$title:badge" => array(
+ 'map' => 'exceptions.count',
+ 'default' => 'null'
+ )
+ );
+ }
+}
\ No newline at end of file
diff --git a/htdocs/debugbar/class/DataCollector/DolLogsCollector.php b/htdocs/debugbar/class/DataCollector/DolLogsCollector.php
new file mode 100644
index 00000000000..b282f1a4a02
--- /dev/null
+++ b/htdocs/debugbar/class/DataCollector/DolLogsCollector.php
@@ -0,0 +1,182 @@
+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;
+ }
+}
\ No newline at end of file
diff --git a/htdocs/debugbar/class/DataCollector/DolMemoryCollector.php b/htdocs/debugbar/class/DataCollector/DolMemoryCollector.php
new file mode 100644
index 00000000000..0c956c420dc
--- /dev/null
+++ b/htdocs/debugbar/class/DataCollector/DolMemoryCollector.php
@@ -0,0 +1,28 @@
+ array(
+ "icon" => "cogs",
+ "tooltip" => $langs->transnoentities('MemoryUsage'),
+ "map" => "memory.peak_usage_str",
+ "default" => "'0B'"
+ )
+ );
+ }
+}
\ No newline at end of file
diff --git a/htdocs/debugbar/class/DataCollector/DolMessagesCollector.php b/htdocs/debugbar/class/DataCollector/DolMessagesCollector.php
new file mode 100644
index 00000000000..b989b882e3c
--- /dev/null
+++ b/htdocs/debugbar/class/DataCollector/DolMessagesCollector.php
@@ -0,0 +1,35 @@
+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"
+ )
+ );
+ }
+}
\ No newline at end of file
diff --git a/htdocs/debugbar/class/DataCollector/DolQueryCollector.php b/htdocs/debugbar/class/DataCollector/DolQueryCollector.php
new file mode 100644
index 00000000000..9ef01b58a79
--- /dev/null
+++ b/htdocs/debugbar/class/DataCollector/DolQueryCollector.php
@@ -0,0 +1,117 @@
+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'
+ );
+ }
+}
\ No newline at end of file
diff --git a/htdocs/debugbar/class/DataCollector/DolRequestDataCollector.php b/htdocs/debugbar/class/DataCollector/DolRequestDataCollector.php
new file mode 100644
index 00000000000..55cee351904
--- /dev/null
+++ b/htdocs/debugbar/class/DataCollector/DolRequestDataCollector.php
@@ -0,0 +1,28 @@
+transnoentities('Request') => array(
+ "icon" => "tags",
+ "widget" => "PhpDebugBar.Widgets.VariableListWidget",
+ "map" => "request",
+ "default" => "{}"
+ )
+ );
+ }
+}
\ No newline at end of file
diff --git a/htdocs/debugbar/class/DataCollector/DolTimeDataCollector.php b/htdocs/debugbar/class/DataCollector/DolTimeDataCollector.php
new file mode 100644
index 00000000000..dd78d21f726
--- /dev/null
+++ b/htdocs/debugbar/class/DataCollector/DolTimeDataCollector.php
@@ -0,0 +1,34 @@
+ 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" => "{}"
+ )
+ );
+ }
+}
\ No newline at end of file
diff --git a/htdocs/debugbar/class/DataCollector/DolibarrCollector.php b/htdocs/debugbar/class/DataCollector/DolibarrCollector.php
new file mode 100644
index 00000000000..ea1381774af
--- /dev/null
+++ b/htdocs/debugbar/class/DataCollector/DolibarrCollector.php
@@ -0,0 +1,140 @@
+trans('Host') . ': ' . $conf->db->host . '
';
+ $info .= $langs->trans('Port') . ': ' . $conf->db->port . '
';
+ $info .= $langs->trans('Name') . ': ' . $conf->db->name . '
';
+ $info .= $langs->trans('User') . ': ' . $conf->db->user . '
';
+ $info .= $langs->trans('Type') . ': ' . $conf->db->type . '
';
+ $info .= $langs->trans('Prefix') . ': ' . $conf->db->prefix . '
';
+ $info .= $langs->trans('Charset') . ': ' . $conf->db->character_set . '';
+
+ return $info;
+ }
+
+ /**
+ * Return dolibarr info as an HTML string
+ *
+ */
+ protected function getDolibarrInfo()
+ {
+ global $conf, $langs;
+
+ $info = $langs->trans('Version') . ': ' . DOL_VERSION . '
';
+ $info .= $langs->trans('Theme') . ': ' . $conf->theme . '
';
+ $info .= $langs->trans('Locale') . ': ' . $conf->global->MAIN_LANG_DEFAULT . '
';
+ $info .= $langs->trans('Currency') . ': ' . $conf->currency . '
';
+ $info .= $langs->trans('DolEntity') . ': ' . $conf->entity . '
';
+ $info .= $langs->trans('ListLimit') . ': ' . ($conf->liste_limit ?: $conf->global->MAIN_SIZE_LISTE_LIMIT) . '
';
+ $info .= $langs->trans('UploadSize') . ': ' . $conf->global->MAIN_UPLOAD_DOC . '';
+
+ return $info;
+ }
+
+ /**
+ * Return mail info as an HTML string
+ *
+ */
+ protected function getMailInfo()
+ {
+ global $conf, $langs;
+
+ $info = $langs->trans('Method') . ': ' . $conf->global->MAIN_MAIL_SENDMODE . '
';
+ $info .= $langs->trans('Server') . ': ' . $conf->global->MAIN_MAIL_SMTP_SERVER . '
';
+ $info .= $langs->trans('Port') . ': ' . $conf->global->MAIN_MAIL_SMTP_PORT . '
';
+ $info .= $langs->trans('ID') . ': ' . $conf->global->MAIN_MAIL_SMTPS_ID . '
';
+ $info .= $langs->trans('Pwd') . ': ' . $conf->global->MAIN_MAIL_SMTPS_PW . '
';
+ $info .= $langs->trans('TLS/STARTTLS') . ': ' . $conf->global->MAIN_MAIL_EMAIL_TLS . ' / ' . $conf->global->MAIN_MAIL_EMAIL_STARTTLS . '
';
+ $info .= $langs->trans('Status') . ': ' . ($conf->global->MAIN_DISABLE_ALL_MAILS ? $langs->trans('StatusDisabled') : $langs->trans('StatusEnabled')) . '';
+
+ 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'
+ );
+ }
+}
\ No newline at end of file
diff --git a/htdocs/debugbar/class/DebugBar.php b/htdocs/debugbar/class/DebugBar.php
new file mode 100644
index 00000000000..5ac6d828035
--- /dev/null
+++ b/htdocs/debugbar/class/DebugBar.php
@@ -0,0 +1,55 @@
+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');
+ }
+}
\ No newline at end of file
diff --git a/htdocs/debugbar/class/TraceableDB.php b/htdocs/debugbar/class/TraceableDB.php
new file mode 100644
index 00000000000..b0f871a97c4
--- /dev/null
+++ b/htdocs/debugbar/class/TraceableDB.php
@@ -0,0 +1,679 @@
+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);
+ }
+}
\ No newline at end of file
diff --git a/htdocs/debugbar/class/actions_debugbar.class.php b/htdocs/debugbar/class/actions_debugbar.class.php
new file mode 100644
index 00000000000..3678cd2189d
--- /dev/null
+++ b/htdocs/debugbar/class/actions_debugbar.class.php
@@ -0,0 +1,112 @@
+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;
+ }
+ }
+}
diff --git a/htdocs/debugbar/class/autoloader.php b/htdocs/debugbar/class/autoloader.php
new file mode 100644
index 00000000000..e7840dcb23c
--- /dev/null
+++ b/htdocs/debugbar/class/autoloader.php
@@ -0,0 +1,19 @@
+ '', 'class' => '')
+ * - data: alias of title
+ */
+ var TooltipIndicator = PhpDebugBar.DebugBar.TooltipIndicator = PhpDebugBar.DebugBar.Indicator.extend({
+
+ render: function() {
+ this.$icon = $('').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'], $('').addClass(csscls('text')).appendTo(this.$el));
+
+ this.$tooltip = $('').addClass(csscls('tooltip disabled')).appendTo(this.$el);
+ this.bindAttr('tooltip', function(tooltip) {
+ if (tooltip['html']) {
+ tooltipHTML = $('').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.$);
\ No newline at end of file
diff --git a/htdocs/includes/DebugBar/Bridge/CacheCacheCollector.php b/htdocs/includes/DebugBar/Bridge/CacheCacheCollector.php
new file mode 100644
index 00000000000..3d841c6784e
--- /dev/null
+++ b/htdocs/includes/DebugBar/Bridge/CacheCacheCollector.php
@@ -0,0 +1,62 @@
+
+ * $debugbar->addCollector(new CacheCacheCollector(CacheManager::get('default')));
+ * // or
+ * $debugbar->addCollector(new CacheCacheCollector());
+ * $debugbar['cache']->addCache(CacheManager::get('default'));
+ *
+ */
+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';
+ }
+}
diff --git a/htdocs/includes/DebugBar/Bridge/DoctrineCollector.php b/htdocs/includes/DebugBar/Bridge/DoctrineCollector.php
new file mode 100644
index 00000000000..6fa358e29ab
--- /dev/null
+++ b/htdocs/includes/DebugBar/Bridge/DoctrineCollector.php
@@ -0,0 +1,98 @@
+
+ * $debugStack = new Doctrine\DBAL\Logging\DebugStack();
+ * $entityManager->getConnection()->getConfiguration()->setSQLLogger($debugStack);
+ * $debugbar->addCollector(new DoctrineCollector($debugStack));
+ *
+ */
+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'
+ );
+ }
+}
diff --git a/htdocs/includes/DebugBar/Bridge/MonologCollector.php b/htdocs/includes/DebugBar/Bridge/MonologCollector.php
new file mode 100644
index 00000000000..609d5c5f837
--- /dev/null
+++ b/htdocs/includes/DebugBar/Bridge/MonologCollector.php
@@ -0,0 +1,103 @@
+
+ * $debugbar->addCollector(new MonologCollector($logger));
+ *
+ */
+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"
+ )
+ );
+ }
+}
diff --git a/htdocs/includes/DebugBar/Bridge/PropelCollector.php b/htdocs/includes/DebugBar/Bridge/PropelCollector.php
new file mode 100644
index 00000000000..93ad4ff82fd
--- /dev/null
+++ b/htdocs/includes/DebugBar/Bridge/PropelCollector.php
@@ -0,0 +1,253 @@
+
+ * $debugbar->addCollector(new PropelCollector($debugbar['messages']));
+ * PropelCollector::enablePropelProfiling();
+ *
+ */
+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'
+ );
+ }
+}
diff --git a/htdocs/includes/DebugBar/Bridge/SlimCollector.php b/htdocs/includes/DebugBar/Bridge/SlimCollector.php
new file mode 100644
index 00000000000..030a3baf624
--- /dev/null
+++ b/htdocs/includes/DebugBar/Bridge/SlimCollector.php
@@ -0,0 +1,66 @@
+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';
+ }
+}
diff --git a/htdocs/includes/DebugBar/Bridge/SwiftMailer/SwiftLogCollector.php b/htdocs/includes/DebugBar/Bridge/SwiftMailer/SwiftLogCollector.php
new file mode 100644
index 00000000000..fdef79a0bdc
--- /dev/null
+++ b/htdocs/includes/DebugBar/Bridge/SwiftMailer/SwiftLogCollector.php
@@ -0,0 +1,44 @@
+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';
+ }
+}
diff --git a/htdocs/includes/DebugBar/Bridge/SwiftMailer/SwiftMailCollector.php b/htdocs/includes/DebugBar/Bridge/SwiftMailer/SwiftMailCollector.php
new file mode 100644
index 00000000000..01a5e906c88
--- /dev/null
+++ b/htdocs/includes/DebugBar/Bridge/SwiftMailer/SwiftMailCollector.php
@@ -0,0 +1,92 @@
+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'
+ );
+ }
+}
diff --git a/htdocs/includes/DebugBar/Bridge/Twig/TraceableTwigEnvironment.php b/htdocs/includes/DebugBar/Bridge/Twig/TraceableTwigEnvironment.php
new file mode 100644
index 00000000000..0d4c957f5b2
--- /dev/null
+++ b/htdocs/includes/DebugBar/Bridge/Twig/TraceableTwigEnvironment.php
@@ -0,0 +1,417 @@
+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);
+ }
+}
diff --git a/htdocs/includes/DebugBar/Bridge/Twig/TraceableTwigTemplate.php b/htdocs/includes/DebugBar/Bridge/Twig/TraceableTwigTemplate.php
new file mode 100644
index 00000000000..dab53d15e1f
--- /dev/null
+++ b/htdocs/includes/DebugBar/Bridge/Twig/TraceableTwigTemplate.php
@@ -0,0 +1,131 @@
+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();
+ }
+}
diff --git a/htdocs/includes/DebugBar/Bridge/Twig/TwigCollector.php b/htdocs/includes/DebugBar/Bridge/Twig/TwigCollector.php
new file mode 100644
index 00000000000..fde5a3f85ba
--- /dev/null
+++ b/htdocs/includes/DebugBar/Bridge/Twig/TwigCollector.php
@@ -0,0 +1,87 @@
+
+ * $env = new TraceableTwigEnvironment(new Twig_Environment($loader));
+ * $debugbar->addCollector(new TwigCollector($env));
+ *
+ */
+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'
+ );
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataCollector/AggregatedCollector.php b/htdocs/includes/DebugBar/DataCollector/AggregatedCollector.php
new file mode 100644
index 00000000000..6c8e711ef76
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataCollector/AggregatedCollector.php
@@ -0,0 +1,166 @@
+
+ * $aggcollector = new AggregateCollector('foobar');
+ * $aggcollector->addCollector(new MessagesCollector('msg1'));
+ * $aggcollector->addCollector(new MessagesCollector('msg2'));
+ * $aggcollector['msg1']->addMessage('hello world');
+ *
+ */
+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");
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataCollector/AssetProvider.php b/htdocs/includes/DebugBar/DataCollector/AssetProvider.php
new file mode 100644
index 00000000000..6910f737e17
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataCollector/AssetProvider.php
@@ -0,0 +1,28 @@
+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" => "{}"
+ )
+ );
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataCollector/DataCollector.php b/htdocs/includes/DebugBar/DataCollector/DataCollector.php
new file mode 100644
index 00000000000..081f66e6872
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataCollector/DataCollector.php
@@ -0,0 +1,90 @@
+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);
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataCollector/DataCollectorInterface.php b/htdocs/includes/DebugBar/DataCollector/DataCollectorInterface.php
new file mode 100644
index 00000000000..b7f234cbff9
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataCollector/DataCollectorInterface.php
@@ -0,0 +1,31 @@
+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'
+ )
+ );
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataCollector/LocalizationCollector.php b/htdocs/includes/DebugBar/DataCollector/LocalizationCollector.php
new file mode 100644
index 00000000000..98b9e6334b2
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataCollector/LocalizationCollector.php
@@ -0,0 +1,64 @@
+ $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',
+ )
+ );
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataCollector/MemoryCollector.php b/htdocs/includes/DebugBar/DataCollector/MemoryCollector.php
new file mode 100644
index 00000000000..1a27c40bf3a
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataCollector/MemoryCollector.php
@@ -0,0 +1,63 @@
+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'"
+ )
+ );
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataCollector/MessagesAggregateInterface.php b/htdocs/includes/DebugBar/DataCollector/MessagesAggregateInterface.php
new file mode 100644
index 00000000000..1e2d4d2d3f2
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataCollector/MessagesAggregateInterface.php
@@ -0,0 +1,21 @@
+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"
+ )
+ );
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataCollector/PDO/PDOCollector.php b/htdocs/includes/DebugBar/DataCollector/PDO/PDOCollector.php
new file mode 100644
index 00000000000..37e7b020b1f
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataCollector/PDO/PDOCollector.php
@@ -0,0 +1,182 @@
+';
+
+ /**
+ * @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'
+ );
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataCollector/PDO/TraceablePDO.php b/htdocs/includes/DebugBar/DataCollector/PDO/TraceablePDO.php
new file mode 100644
index 00000000000..5f7688b978e
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataCollector/PDO/TraceablePDO.php
@@ -0,0 +1,196 @@
+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);
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataCollector/PDO/TraceablePDOStatement.php b/htdocs/includes/DebugBar/DataCollector/PDO/TraceablePDOStatement.php
new file mode 100644
index 00000000000..b903b10c361
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataCollector/PDO/TraceablePDOStatement.php
@@ -0,0 +1,74 @@
+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;
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataCollector/PDO/TracedStatement.php b/htdocs/includes/DebugBar/DataCollector/PDO/TracedStatement.php
new file mode 100644
index 00000000000..dc51cee1764
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataCollector/PDO/TracedStatement.php
@@ -0,0 +1,240 @@
+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() : '';
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataCollector/PhpInfoCollector.php b/htdocs/includes/DebugBar/DataCollector/PhpInfoCollector.php
new file mode 100644
index 00000000000..57e9e45f47f
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataCollector/PhpInfoCollector.php
@@ -0,0 +1,51 @@
+ PHP_VERSION,
+ 'interface' => PHP_SAPI
+ );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getWidgets()
+ {
+ return array(
+ "php_version" => array(
+ "icon" => "code",
+ "tooltip" => "Version",
+ "map" => "php.version",
+ "default" => ""
+ ),
+ );
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataCollector/Renderable.php b/htdocs/includes/DebugBar/DataCollector/Renderable.php
new file mode 100644
index 00000000000..1a6a833f5d1
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataCollector/Renderable.php
@@ -0,0 +1,25 @@
+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" => "{}"
+ )
+ );
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataCollector/TimeDataCollector.php b/htdocs/includes/DebugBar/DataCollector/TimeDataCollector.php
new file mode 100644
index 00000000000..19e5a63ced1
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataCollector/TimeDataCollector.php
@@ -0,0 +1,228 @@
+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" => "{}"
+ )
+ );
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataFormatter/DataFormatter.php b/htdocs/includes/DebugBar/DataFormatter/DataFormatter.php
new file mode 100644
index 00000000000..73269459838
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataFormatter/DataFormatter.php
@@ -0,0 +1,169 @@
+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 );
+ }
+ }
+}
diff --git a/htdocs/includes/DebugBar/DataFormatter/DataFormatterInterface.php b/htdocs/includes/DebugBar/DataFormatter/DataFormatterInterface.php
new file mode 100644
index 00000000000..cb7b426e70b
--- /dev/null
+++ b/htdocs/includes/DebugBar/DataFormatter/DataFormatterInterface.php
@@ -0,0 +1,42 @@
+
+ * $debugbar = new DebugBar();
+ * $debugbar->addCollector(new DataCollector\MessagesCollector());
+ * $debugbar['messages']->addMessage("foobar");
+ *
+ */
+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");
+ }
+}
diff --git a/htdocs/includes/DebugBar/DebugBarException.php b/htdocs/includes/DebugBar/DebugBarException.php
new file mode 100644
index 00000000000..32ffe255cdc
--- /dev/null
+++ b/htdocs/includes/DebugBar/DebugBarException.php
@@ -0,0 +1,16 @@
+ '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('' . "\n", $file);
+ }
+
+ foreach ($jsFiles as $file) {
+ $html .= sprintf('' . "\n", $file);
+ }
+
+ if ($this->enableJqueryNoConflict) {
+ $html .= '' . "\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 "\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;
+ }
+}
diff --git a/htdocs/includes/DebugBar/OpenHandler.php b/htdocs/includes/DebugBar/OpenHandler.php
new file mode 100644
index 00000000000..cbabea268ae
--- /dev/null
+++ b/htdocs/includes/DebugBar/OpenHandler.php
@@ -0,0 +1,107 @@
+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);
+ }
+}
diff --git a/htdocs/includes/DebugBar/PhpHttpDriver.php b/htdocs/includes/DebugBar/PhpHttpDriver.php
new file mode 100644
index 00000000000..af07060d279
--- /dev/null
+++ b/htdocs/includes/DebugBar/PhpHttpDriver.php
@@ -0,0 +1,49 @@
+ $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]);
+ }
+}
diff --git a/htdocs/includes/DebugBar/RequestIdGenerator.php b/htdocs/includes/DebugBar/RequestIdGenerator.php
new file mode 100644
index 00000000000..934f407ff8f
--- /dev/null
+++ b/htdocs/includes/DebugBar/RequestIdGenerator.php
@@ -0,0 +1,22 @@
+ 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;
+ }
diff --git a/htdocs/includes/DebugBar/Resources/debugbar.js b/htdocs/includes/DebugBar/Resources/debugbar.js
new file mode 100644
index 00000000000..faf567e37c1
--- /dev/null
+++ b/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 = $('').addClass(csscls('tab'));
+
+ this.$icon = $('').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', $('').addClass(csscls('text')).appendTo(this.$tab));
+
+ this.$badge = $('').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 = $('').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'], $('').addClass(csscls('text')).appendTo(this.$el));
+
+ this.$tooltip = $('').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 = $('
| Date | Method | URL | IP | Filter data |
|---|