diff options
author | Amenadiel <amenadiel@gmail.com> | 2016-09-07 21:41:52 +0300 |
---|---|---|
committer | Amenadiel <amenadiel@gmail.com> | 2016-09-07 21:41:52 +0300 |
commit | b44050b160c0986ee7f8e69e4c08d41daf5ffcde (patch) | |
tree | e442fd5cd642f3bf7b2aed080d99fafaa0e76bff | |
parent | 7f9b6c0512e67d5cd029779bbb70442e4e495c4b (diff) | |
parent | 4a01919ac895fe67fd91fa768044b25c3d846d49 (diff) |
Merge branch 'release/6.0.0-alpha3'v6.0.0-alpha3
171 files changed, 26789 insertions, 9415 deletions
diff --git a/composer.json b/composer.json index a053d060..c161868c 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "huasofoundries/phppgadmin", - "version": "6.0.0-alpha2", + "version": "6.0.0-alpha3", "description": "Like phpmyadmin but for postgres", "type": "project", "license": "MIT", @@ -13,9 +13,10 @@ "psr-4": { "PHPPgAdmin\\": "src/classes", "PHPPgAdmin\\Controller\\": "src/controllers", - "PHPPgAdmin\\Database\\": "src/classes/database", - "PHPPgAdmin\\XHtml\\": "src/classes/xhtml", - "PHPPgAdmin\\Decorators\\": "src/classes/decorators" + + "PHPPgAdmin\\Database\\": "src/database", + "PHPPgAdmin\\XHtml\\": "src/xhtml", + "PHPPgAdmin\\Decorators\\": "src/decorators" } }, "require": { diff --git a/composer.lock b/composer.lock index cc5fff32..744b7315 100644 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "95f57a2ebe79ac38abaf9ddf5aa10968", - "content-hash": "4c894ea7295f7f51cce76ca342f6b6b2", + "hash": "b5caa7fbaa65851bbec4d8d6bfc0dc9e", + "content-hash": "a561ee70b411e722ceb6ff4a20b9d564", "packages": [ { "name": "adodb/adodb-php", - "version": "v5.20.5", + "version": "v5.20.6", "source": { "type": "git", "url": "https://github.com/ADOdb/ADOdb.git", - "reference": "7ce997d4c5957a1a96e7ab483ad088b15f02b191" + "reference": "66b60ff95a969d7d79d385a25779e7d375a8040d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ADOdb/ADOdb/zipball/7ce997d4c5957a1a96e7ab483ad088b15f02b191", - "reference": "7ce997d4c5957a1a96e7ab483ad088b15f02b191", + "url": "https://api.github.com/repos/ADOdb/ADOdb/zipball/66b60ff95a969d7d79d385a25779e7d375a8040d", + "reference": "66b60ff95a969d7d79d385a25779e7d375a8040d", "shasum": "" }, "require": { @@ -51,7 +51,7 @@ } ], "description": "ADOdb is a PHP database abstraction layer library", - "homepage": "http://adodb.sourceforge.net/", + "homepage": "http://adodb.org/", "keywords": [ "abstraction", "database", @@ -59,7 +59,7 @@ "library", "php" ], - "time": "2016-08-10 11:08:40" + "time": "2016-08-31 14:21:42" }, { "name": "container-interop/container-interop", @@ -451,16 +451,16 @@ }, { "name": "twig/twig", - "version": "v1.24.1", + "version": "v1.24.2", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "3566d311a92aae4deec6e48682dc5a4528c4a512" + "reference": "33093f6e310e6976baeac7b14f3a6ec02f2d79b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/3566d311a92aae4deec6e48682dc5a4528c4a512", - "reference": "3566d311a92aae4deec6e48682dc5a4528c4a512", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/33093f6e310e6976baeac7b14f3a6ec02f2d79b7", + "reference": "33093f6e310e6976baeac7b14f3a6ec02f2d79b7", "shasum": "" }, "require": { @@ -508,7 +508,7 @@ "keywords": [ "templating" ], - "time": "2016-05-30 09:11:59" + "time": "2016-09-01 17:50:53" } ], "packages-dev": [], diff --git a/help/PostgresDoc95.php b/help/PostgresDoc95.php new file mode 100644 index 00000000..acf2745f --- /dev/null +++ b/help/PostgresDoc95.php @@ -0,0 +1,12 @@ +<?php + +/** + * Help links for PostgreSQL 9.5 documentation + * + */ + +include BASE_PATH . '/help/PostgresDoc94.php'; + +$this->help_base = sprintf($GLOBALS['conf']['help_base'], '9.5'); + +$this->help_page['pg.matview'] = 'sql-creatematerializedview.html'; diff --git a/images/datatables/sort_asc.png b/images/datatables/sort_asc.png Binary files differnew file mode 100644 index 00000000..e1ba61a8 --- /dev/null +++ b/images/datatables/sort_asc.png diff --git a/images/datatables/sort_asc_disabled.png b/images/datatables/sort_asc_disabled.png Binary files differnew file mode 100644 index 00000000..fb11dfe2 --- /dev/null +++ b/images/datatables/sort_asc_disabled.png diff --git a/images/datatables/sort_both.png b/images/datatables/sort_both.png Binary files differnew file mode 100644 index 00000000..af5bc7c5 --- /dev/null +++ b/images/datatables/sort_both.png diff --git a/images/datatables/sort_desc.png b/images/datatables/sort_desc.png Binary files differnew file mode 100644 index 00000000..0e156deb --- /dev/null +++ b/images/datatables/sort_desc.png diff --git a/images/datatables/sort_desc_disabled.png b/images/datatables/sort_desc_disabled.png Binary files differnew file mode 100644 index 00000000..c9fdd8a1 --- /dev/null +++ b/images/datatables/sort_desc_disabled.png diff --git a/images/themes/default/MViews.png b/images/themes/default/MViews.png Binary files differnew file mode 100644 index 00000000..40e65a96 --- /dev/null +++ b/images/themes/default/MViews.png @@ -7,7 +7,7 @@ */ // Include application functions -$_no_db_connection = true; + require_once './src/lib.inc.php'; $app->post('/redirect[/{subject}]', function ($request, $response, $args) use ($msg) { @@ -42,7 +42,7 @@ $app->post('/redirect[/{subject}]', function ($request, $response, $args) use ($ $misc->printHeader($this->lang['strdatabases']); $misc->printBody(); - return $all_db_controller->doDefault(); + $all_db_controller->doDefault(); $misc->setReloadBrowser(true); $misc->printFooter(); @@ -54,9 +54,8 @@ $app->post('/redirect[/{subject}]', function ($request, $response, $args) use ($ if (!isset($_server_info['username'])) { - include BASE_PATH . '/src/views/login.php'; - - $body->write(doLoginForm($this, $msg)); + $login_controller = new \PHPPgAdmin\Controller\LoginController($this); + $body->write($login_controller->doLoginForm($msg)); } } @@ -76,10 +75,11 @@ $app->get('/redirect[/{subject}]', function ($request, $response, $args) use ($m $body = $response->getBody(); if (!isset($_server_info['username'])) { - include BASE_PATH . '/src/views/login.php'; - $body->write(doLoginForm($this, $msg)); + $this->misc->setNoDBConnection(true); + $login_controller = new \PHPPgAdmin\Controller\LoginController($this); + + $body->write($login_controller->doLoginForm($msg)); - //\Kint::dump($request->getParams()); return $response; } else { @@ -87,19 +87,17 @@ $app->get('/redirect[/{subject}]', function ($request, $response, $args) use ($m $include_file = $url['url']; + \PC::debug($url, 'url'); + // Load query vars into superglobal arrays if (isset($url['urlvars'])) { - - /*echo '<pre>'; - print_r($url['urlvars']); - */ $urlvars = []; foreach ($url['urlvars'] as $key => $urlvar) { if (strpos($key, '?') !== FALSE) { $key = explode('?', $key)[1]; } - $urlvars[$key] = value($urlvar, $_REQUEST); + $urlvars[$key] = \PHPPgAdmin\Decorators\Decorator::get_sanitized_value($urlvar, $_REQUEST); } $_REQUEST = array_merge($_REQUEST, $urlvars); @@ -108,9 +106,6 @@ $app->get('/redirect[/{subject}]', function ($request, $response, $args) use ($m $actionurl = \PHPPgAdmin\Decorators\Decorator::actionurl($include_file, $_GET); - //PC::debug($url['url'], 'redirect.php will include'); - //PC::debug($actionurl->value($_GET), '$actionurl'); - if (is_readable($include_file)) { include $include_file; } else { @@ -122,149 +117,21 @@ $app->get('/redirect[/{subject}]', function ($request, $response, $args) use ($m } }); -$app->get('/sqledit[/{action}]', function ($request, $response, $args) use ($msg) { - - $action = (isset($args['action'])) ? $args['action'] : 'sql'; - - include './src/views/sqledit.php'; - $body = $response->getBody(); - - switch ($action) { - case 'find': - - $header_html = $this->view->fetch('sqledit_header.twig', ['title' => $this->lang['strfind']]); - $body->write($header_html); - $body->write(doFind($this)); - - break; - case 'sql': - default: - - $header_html = $this->view->fetch('sqledit_header.twig', ['title' => $this->lang['strsql']]); - $body->write($header_html); - $body->write(doDefault($this)); - - break; - } - - $footer_html = $this->view->fetch('sqledit.twig'); - $body->write($footer_html); - - $this->misc->setWindowName('sqledit'); - return $response; - -}); - -$app->get('/tree/browser', function ($request, $response, $args) use ($msg) { - - $viewVars = $this->lang; - $viewVars['appName'] = $this->get('settings')['appName']; - $viewVars['icon'] = [ - 'blank' => $this->misc->icon('blank'), - 'I' => $this->misc->icon('I'), - 'L' => $this->misc->icon('L'), - 'Lminus' => $this->misc->icon('Lminus'), - 'Loading' => $this->misc->icon('Loading'), - 'Lplus' => $this->misc->icon('Lplus'), - 'ObjectNotFound' => $this->misc->icon('ObjectNotFound'), - 'Refresh' => $this->misc->icon('Refresh'), - 'Servers' => $this->misc->icon('Servers'), - 'T' => $this->misc->icon('T'), - 'Tminus' => $this->misc->icon('Tminus'), - 'Tplus' => $this->misc->icon('Tplus'), - - ]; - - $viewVars['cols'] = $cols; - $viewVars['rtl'] = $rtl; - - $this->view->render($response, 'browser.twig', $viewVars); - -}); - -$app->get('/tree/{node}[/{action}]', function ($request, $response, $args) use ($msg) { - - $newResponse = $response - ->withHeader('Content-type', 'text/xml') - ->withHeader('Cache-Control', 'no-cache'); - - $phpscript = './src/tree/' . $args['node'] . '.php'; - - if (is_readable($phpscript)) { - include $phpscript; - if (isset($args['action']) && $args['action'] == 'subtree') { - doSubTree($this); - } else { - doTree($this); - } - } - - return $newResponse; - -}); - -$app->get('/src/views/servers[/{action}]', function ($request, $response, $args) use ($msg) { - - $action = (isset($args['action'])) ? $args['action'] : ''; - - include './src/views/servers.php'; - - $body = $response->getBody(); - - $header_html = $this->misc->printHeader($this->lang['strservers'], null, false); - $body->write($header_html); - - $body_html = $this->misc->printBody(false); - $body->write($body_html); - - $trail_html = $this->misc->printTrail('root', false); - $body->write($trail_html); - - switch ($action) { - case 'logout': - $body->write(doLogout($this)); - - break; - default: - $body->write(doDefault($this, $msg)); - break; - } - - $footer_html = $this->misc->printFooter(false); - $body->write($footer_html); - return $response; - -}); - $app->get('/', function ($request, $response, $args) use ($msg) { - $rtl = (strcasecmp($this->lang['applangdir'], 'rtl') == 0); - $cols = $rtl ? '*,' . $this->conf['left_width'] : $this->conf['left_width'] . ',*'; - $viewVars = $this->lang; $viewVars['appName'] = $this->get('settings')['appName']; - $viewVars['cols'] = $cols; - $viewVars['rtl'] = $rtl; - - return $this->view->render($response, 'home.twig', $viewVars); - -}); - -$app->get('/src/views/intro', function ($request, $response, $args) use ($msg) { - include './src/views/intro.php'; - - $body = $response->getBody(); - $body->write(doDefault($this)); - - return $response; + $viewVars['rtl'] = (strcasecmp($this->lang['applangdir'], 'rtl') == 0); -}); + if ($viewVars['rtl']) { + $viewVars['cols'] = '*,' . $this->conf['left_width']; + $template = 'home_rtl.twig'; + } else { + $viewVars['cols'] = $this->conf['left_width'] . ',*'; + $template = 'home.twig'; + } -$app->get('/views/{script}', function ($request, $response, $args) use ($msg) { - $body = $response->getBody(); - $body->write('Not found ' . $args['script']); - \PC::debug($args, 'args'); - return $response; + return $this->view->render($response, $template, $viewVars); }); diff --git a/js/ac_insert_row.js b/js/ac_insert_row.js index 8fabf28f..f18c2621 100644 --- a/js/ac_insert_row.js +++ b/js/ac_insert_row.js @@ -74,13 +74,13 @@ function openlist(e) { }; jQuery.ajax({ - url: 'ajax-ac-insert.php?server=' + server, + url: '/srv/views/ajax-ac-insert.php?server=' + server, type: 'post', data: datas, dataType: 'html', cache: false, contentType: 'application/x-www-form-urlencoded', - success: function(ret) { + success: function (ret) { jQuery.ppa.i = 0; jQuery.ppa.fkbg.show(); with(jQuery.ppa.fklist) { @@ -162,11 +162,11 @@ function autocomplete(event) { /* bind actions on values lines: hover for style change, click for select */ with(jQuery('tr.acline')) { - live('mouseover', function() { + live('mouseover', function () { selectVal(jQuery('table.ac_values tr').index(this)); }); - live('click', function() { + live('click', function () { var a = jQuery(this).find('td > a.fkval'); for (i = 0; i < a.length; i++) { @@ -177,8 +177,7 @@ with(jQuery('tr.acline')) { } - -jQuery(document).ready(function() { +jQuery(document).ready(function () { /* register some global value in the ppa namespace */ jQuery.ppa = { fklist: jQuery('#fklist'), @@ -188,7 +187,7 @@ jQuery(document).ready(function() { o: 0 // offset when navigating prev/next }; - jQuery.ppa.fklist.on('click', '#fkprev', function() { + jQuery.ppa.fklist.on('click', '#fkprev', function () { jQuery.ppa.o -= 11; /* get the field that is the previous html elt from the #fklist * and trigger its focus to refresh the list AND actualy @@ -197,7 +196,7 @@ jQuery(document).ready(function() { }); - jQuery.ppa.fklist.on('click', '#fknext', function() { + jQuery.ppa.fklist.on('click', '#fknext', function () { jQuery.ppa.o += 11; /* get the field that is the previous html elt from the #fklist * and trigger its focus to refresh the list AND actualy @@ -206,20 +205,20 @@ jQuery(document).ready(function() { }); /* close the list when clicking outside of it */ - jQuery.ppa.fkbg.click(function(e) { + jQuery.ppa.fkbg.click(function (e) { hideAc(); }); /* do not submit the form when selecting a value by pressing enter */ jQuery.ppa.attrs - .keydown(function(e) { + .keydown(function (e) { if (e.keyCode == 13 && jQuery.ppa.fklist[0].style.display == 'block') return false; }); /* enable/disable auto-complete according to the checkbox */ triggerAc( - jQuery('#no_ac').click(function() { + jQuery('#no_ac').click(function () { triggerAc(this.checked); })[0].checked ); diff --git a/js/database.js b/js/database.js index f70f7078..3ec12411 100644 --- a/js/database.js +++ b/js/database.js @@ -1,55 +1,58 @@ -$(document).ready(function() { +$(document).ready(function () { var timeid = query = null; var controlLink = $('#control'); - var errmsg = $('<p class="errmsg">'+Database.errmsg+'</p>') + + var errmsg = $('<p class="errmsg">' + Database.errmsg + '</p>') .insertBefore(controlLink) .hide(); - var loading = $('<img class="loading" alt="[loading]" src="'+ Database.load_icon +'" />') + + var loading = $('<img class="loading" alt="[loading]" src="' + Database.load_icon + '" />') .insertAfter(controlLink) .hide(); + function refreshTable() { if (Database.ajax_time_refresh > 0) { loading.show(); query = $.ajax({ type: 'GET', dataType: 'html', - data: {server: Database.server, database: Database.dbname, action: Database.action}, - url: 'database.php', + data: { + server: Database.server, + database: Database.dbname, + action: Database.action + }, + url: '/src/views/database.php', cache: false, contentType: 'application/x-www-form-urlencoded', - success: function(html) { + success: function (html) { $('#data_block').html(html); timeid = window.setTimeout(refreshTable, Database.ajax_time_refresh) }, - error: function() { + error: function () { controlLink.click(); - errmsg.show(); + //errmsg.show(); }, complete: function () { - loading.hide(); + //loading.hide(); } }); } } controlLink.toggle( - function() { - $(errmsg).hide(); + function () { + //$(errmsg).hide(); timeid = window.setTimeout(refreshTable, Database.ajax_time_refresh); - controlLink.html('<img src="'+ Database.str_stop.icon +'" alt="" /> ' - + Database.str_stop.text + ' ' - ); + controlLink.html('<img src="' + Database.str_stop.icon + '" alt="" /> ' + Database.str_stop.text + ' '); }, - function() { - $(errmsg).hide(); - $(loading).hide(); + function () { + //$(errmsg).hide(); + //$(loading).hide(); window.clearInterval(timeid); if (query) query.abort(); - controlLink.html('<img src="'+ Database.str_start.icon +'" alt="" /> ' - + Database.str_start.text - ); + controlLink.html('<img src="' + Database.str_start.icon + '" alt="" /> ' + Database.str_start.text); } ); @@ -58,7 +61,7 @@ $(document).ready(function() { .attr('src', Database.str_start.icon) .attr('src', Database.str_stop.icon) .show(); - + /* start refreshing */ controlLink.click(); }); diff --git a/js/datatables.min.js b/js/datatables.min.js new file mode 100644 index 00000000..bd282fca --- /dev/null +++ b/js/datatables.min.js @@ -0,0 +1,14787 @@ +/*! DataTables 1.10.12 + * ©2008-2015 SpryMedia Ltd - datatables.net/license + */ + +/** + * @summary DataTables + * @description Paginate, search and order HTML tables + * @version 1.10.12 + * @file jquery.dataTables.js + * @author SpryMedia Ltd (www.sprymedia.co.uk) + * @contact www.sprymedia.co.uk/contact + * @copyright Copyright 2008-2015 SpryMedia Ltd. + * + * This source file is free software, available under the following license: + * MIT license - http://datatables.net/license + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + * + * For details please refer to: http://www.datatables.net + */ + +/*jslint evil: true, undef: true, browser: true */ +/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ + +(function (factory) { + "use strict"; + + if (typeof define === 'function' && define.amd) { + // AMD + define(['jquery'], function ($) { + return factory($, window, document); + }); + } else if (typeof exports === 'object') { + // CommonJS + module.exports = function (root, $) { + if (!root) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if (!$) { + $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window + require('jquery') : + require('jquery')(root); + } + + return factory($, root, root.document); + }; + } else { + // Browser + factory(jQuery, window, document); + } + } + (function ($, window, document, undefined) { + "use strict"; + + /** + * DataTables is a plug-in for the jQuery Javascript library. It is a highly + * flexible tool, based upon the foundations of progressive enhancement, + * which will add advanced interaction controls to any HTML table. For a + * full list of features please refer to + * [DataTables.net](href="http://datatables.net). + * + * Note that the `DataTable` object is not a global variable but is aliased + * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may + * be accessed. + * + * @class + * @param {object} [init={}] Configuration object for DataTables. Options + * are defined by {@link DataTable.defaults} + * @requires jQuery 1.7+ + * + * @example + * // Basic initialisation + * $(document).ready( function { + * $('#example').dataTable(); + * } ); + * + * @example + * // Initialisation with configuration options - in this case, disable + * // pagination and sorting. + * $(document).ready( function { + * $('#example').dataTable( { + * "paginate": false, + * "sort": false + * } ); + * } ); + */ + var DataTable = function (options) { + /** + * Perform a jQuery selector action on the table's TR elements (from the tbody) and + * return the resulting jQuery object. + * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on + * @param {object} [oOpts] Optional parameters for modifying the rows to be included + * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter + * criterion ("applied") or all TR elements (i.e. no filter). + * @param {string} [oOpts.order=current] Order of the TR elements in the processed array. + * Can be either 'current', whereby the current sorting of the table is used, or + * 'original' whereby the original order the data was read into the table is used. + * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page + * ("current") or not ("all"). If 'current' is given, then order is assumed to be + * 'current' and filter is 'applied', regardless of what they might be given as. + * @returns {object} jQuery object, filtered by the given selector. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Highlight every second row + * oTable.$('tr:odd').css('backgroundColor', 'blue'); + * } ); + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Filter to rows with 'Webkit' in them, add a background colour and then + * // remove the filter, thus highlighting the 'Webkit' rows only. + * oTable.fnFilter('Webkit'); + * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue'); + * oTable.fnFilter(''); + * } ); + */ + this.$ = function (sSelector, oOpts) { + return this.api(true).$(sSelector, oOpts); + }; + + + /** + * Almost identical to $ in operation, but in this case returns the data for the matched + * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes + * rather than any descendants, so the data can be obtained for the row/cell. If matching + * rows are found, the data returned is the original data array/object that was used to + * create the row (or a generated array if from a DOM source). + * + * This method is often useful in-combination with $ where both functions are given the + * same parameters and the array indexes will match identically. + * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on + * @param {object} [oOpts] Optional parameters for modifying the rows to be included + * @param {string} [oOpts.filter=none] Select elements that meet the current filter + * criterion ("applied") or all elements (i.e. no filter). + * @param {string} [oOpts.order=current] Order of the data in the processed array. + * Can be either 'current', whereby the current sorting of the table is used, or + * 'original' whereby the original order the data was read into the table is used. + * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page + * ("current") or not ("all"). If 'current' is given, then order is assumed to be + * 'current' and filter is 'applied', regardless of what they might be given as. + * @returns {array} Data for the matched elements. If any elements, as a result of the + * selector, were not TR, TD or TH elements in the DataTable, they will have a null + * entry in the array. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Get the data from the first row in the table + * var data = oTable._('tr:first'); + * + * // Do something useful with the data + * alert( "First cell is: "+data[0] ); + * } ); + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Filter to 'Webkit' and get all data for + * oTable.fnFilter('Webkit'); + * var data = oTable._('tr', {"search": "applied"}); + * + * // Do something with the data + * alert( data.length+" rows matched the search" ); + * } ); + */ + this._ = function (sSelector, oOpts) { + return this.api(true).rows(sSelector, oOpts).data(); + }; + + + /** + * Create a DataTables Api instance, with the currently selected tables for + * the Api's context. + * @param {boolean} [traditional=false] Set the API instance's context to be + * only the table referred to by the `DataTable.ext.iApiIndex` option, as was + * used in the API presented by DataTables 1.9- (i.e. the traditional mode), + * or if all tables captured in the jQuery object should be used. + * @return {DataTables.Api} + */ + this.api = function (traditional) { + return traditional ? + new _Api( + _fnSettingsFromNode(this[_ext.iApiIndex]) + ) : + new _Api(this); + }; + + + /** + * Add a single new row or multiple rows of data to the table. Please note + * that this is suitable for client-side processing only - if you are using + * server-side processing (i.e. "bServerSide": true), then to add data, you + * must add it to the data source, i.e. the server-side, through an Ajax call. + * @param {array|object} data The data to be added to the table. This can be: + * <ul> + * <li>1D array of data - add a single row with the data provided</li> + * <li>2D array of arrays - add multiple rows in a single call</li> + * <li>object - data object when using <i>mData</i></li> + * <li>array of objects - multiple data objects when using <i>mData</i></li> + * </ul> + * @param {bool} [redraw=true] redraw the table or not + * @returns {array} An array of integers, representing the list of indexes in + * <i>aoData</i> ({@link DataTable.models.oSettings}) that have been added to + * the table. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * // Global var for counter + * var giCount = 2; + * + * $(document).ready(function() { + * $('#example').dataTable(); + * } ); + * + * function fnClickAddRow() { + * $('#example').dataTable().fnAddData( [ + * giCount+".1", + * giCount+".2", + * giCount+".3", + * giCount+".4" ] + * ); + * + * giCount++; + * } + */ + this.fnAddData = function (data, redraw) { + var api = this.api(true); + + /* Check if we want to add multiple rows or not */ + var rows = $.isArray(data) && ($.isArray(data[0]) || $.isPlainObject(data[0])) ? + api.rows.add(data) : + api.row.add(data); + + if (redraw === undefined || redraw) { + api.draw(); + } + + return rows.flatten().toArray(); + }; + + + /** + * This function will make DataTables recalculate the column sizes, based on the data + * contained in the table and the sizes applied to the columns (in the DOM, CSS or + * through the sWidth parameter). This can be useful when the width of the table's + * parent element changes (for example a window resize). + * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable( { + * "sScrollY": "200px", + * "bPaginate": false + * } ); + * + * $(window).bind('resize', function () { + * oTable.fnAdjustColumnSizing(); + * } ); + * } ); + */ + this.fnAdjustColumnSizing = function (bRedraw) { + var api = this.api(true).columns.adjust(); + var settings = api.settings()[0]; + var scroll = settings.oScroll; + + if (bRedraw === undefined || bRedraw) { + api.draw(false); + } else if (scroll.sX !== "" || scroll.sY !== "") { + /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */ + _fnScrollDraw(settings); + } + }; + + + /** + * Quickly and simply clear a table + * @param {bool} [bRedraw=true] redraw the table or not + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...) + * oTable.fnClearTable(); + * } ); + */ + this.fnClearTable = function (bRedraw) { + var api = this.api(true).clear(); + + if (bRedraw === undefined || bRedraw) { + api.draw(); + } + }; + + + /** + * The exact opposite of 'opening' a row, this function will close any rows which + * are currently 'open'. + * @param {node} nTr the table row to 'close' + * @returns {int} 0 on success, or 1 if failed (can't find the row) + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable; + * + * // 'open' an information row when a row is clicked on + * $('#example tbody tr').click( function () { + * if ( oTable.fnIsOpen(this) ) { + * oTable.fnClose( this ); + * } else { + * oTable.fnOpen( this, "Temporary row opened", "info_row" ); + * } + * } ); + * + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnClose = function (nTr) { + this.api(true).row(nTr).child.hide(); + }; + + + /** + * Remove a row for the table + * @param {mixed} target The index of the row from aoData to be deleted, or + * the TR element you want to delete + * @param {function|null} [callBack] Callback function + * @param {bool} [redraw=true] Redraw the table or not + * @returns {array} The row that was deleted + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Immediately remove the first row + * oTable.fnDeleteRow( 0 ); + * } ); + */ + this.fnDeleteRow = function (target, callback, redraw) { + var api = this.api(true); + var rows = api.rows(target); + var settings = rows.settings()[0]; + var data = settings.aoData[rows[0][0]]; + + rows.remove(); + + if (callback) { + callback.call(this, settings, data); + } + + if (redraw === undefined || redraw) { + api.draw(); + } + + return data; + }; + + + /** + * Restore the table to it's original state in the DOM by removing all of DataTables + * enhancements, alterations to the DOM structure of the table and event listeners. + * @param {boolean} [remove=false] Completely remove the table from the DOM + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * // This example is fairly pointless in reality, but shows how fnDestroy can be used + * var oTable = $('#example').dataTable(); + * oTable.fnDestroy(); + * } ); + */ + this.fnDestroy = function (remove) { + this.api(true).destroy(remove); + }; + + + /** + * Redraw the table + * @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Re-draw the table - you wouldn't want to do it here, but it's an example :-) + * oTable.fnDraw(); + * } ); + */ + this.fnDraw = function (complete) { + // Note that this isn't an exact match to the old call to _fnDraw - it takes + // into account the new data, but can hold position. + this.api(true).draw(complete); + }; + + + /** + * Filter the input based on data + * @param {string} sInput String to filter the table on + * @param {int|null} [iColumn] Column to limit filtering to + * @param {bool} [bRegex=false] Treat as regular expression or not + * @param {bool} [bSmart=true] Perform smart filtering or not + * @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es) + * @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false) + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Sometime later - filter... + * oTable.fnFilter( 'test string' ); + * } ); + */ + this.fnFilter = function (sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive) { + var api = this.api(true); + + if (iColumn === null || iColumn === undefined) { + api.search(sInput, bRegex, bSmart, bCaseInsensitive); + } else { + api.column(iColumn).search(sInput, bRegex, bSmart, bCaseInsensitive); + } + + api.draw(); + }; + + + /** + * Get the data for the whole table, an individual row or an individual cell based on the + * provided parameters. + * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as + * a TR node then the data source for the whole row will be returned. If given as a + * TD/TH cell node then iCol will be automatically calculated and the data for the + * cell returned. If given as an integer, then this is treated as the aoData internal + * data index for the row (see fnGetPosition) and the data for that row used. + * @param {int} [col] Optional column index that you want the data of. + * @returns {array|object|string} If mRow is undefined, then the data for all rows is + * returned. If mRow is defined, just data for that row, and is iCol is + * defined, only data for the designated cell is returned. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * // Row data + * $(document).ready(function() { + * oTable = $('#example').dataTable(); + * + * oTable.$('tr').click( function () { + * var data = oTable.fnGetData( this ); + * // ... do something with the array / object of data for the row + * } ); + * } ); + * + * @example + * // Individual cell data + * $(document).ready(function() { + * oTable = $('#example').dataTable(); + * + * oTable.$('td').click( function () { + * var sData = oTable.fnGetData( this ); + * alert( 'The cell clicked on had the value of '+sData ); + * } ); + * } ); + */ + this.fnGetData = function (src, col) { + var api = this.api(true); + + if (src !== undefined) { + var type = src.nodeName ? src.nodeName.toLowerCase() : ''; + + return col !== undefined || type == 'td' || type == 'th' ? + api.cell(src, col).data() : + api.row(src).data() || null; + } + + return api.data().toArray(); + }; + + + /** + * Get an array of the TR nodes that are used in the table's body. Note that you will + * typically want to use the '$' API method in preference to this as it is more + * flexible. + * @param {int} [iRow] Optional row index for the TR element you want + * @returns {array|node} If iRow is undefined, returns an array of all TR elements + * in the table's body, or iRow is defined, just the TR element requested. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Get the nodes from the table + * var nNodes = oTable.fnGetNodes( ); + * } ); + */ + this.fnGetNodes = function (iRow) { + var api = this.api(true); + + return iRow !== undefined ? + api.row(iRow).node() : + api.rows().nodes().flatten().toArray(); + }; + + + /** + * Get the array indexes of a particular cell from it's DOM element + * and column index including hidden columns + * @param {node} node this can either be a TR, TD or TH in the table's body + * @returns {int} If nNode is given as a TR, then a single index is returned, or + * if given as a cell, an array of [row index, column index (visible), + * column index (all)] is given. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * $('#example tbody td').click( function () { + * // Get the position of the current data from the node + * var aPos = oTable.fnGetPosition( this ); + * + * // Get the data array for this row + * var aData = oTable.fnGetData( aPos[0] ); + * + * // Update the data array and return the value + * aData[ aPos[1] ] = 'clicked'; + * this.innerHTML = 'clicked'; + * } ); + * + * // Init DataTables + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnGetPosition = function (node) { + var api = this.api(true); + var nodeName = node.nodeName.toUpperCase(); + + if (nodeName == 'TR') { + return api.row(node).index(); + } else if (nodeName == 'TD' || nodeName == 'TH') { + var cell = api.cell(node).index(); + + return [ + cell.row, + cell.columnVisible, + cell.column + ]; + } + return null; + }; + + + /** + * Check to see if a row is 'open' or not. + * @param {node} nTr the table row to check + * @returns {boolean} true if the row is currently open, false otherwise + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable; + * + * // 'open' an information row when a row is clicked on + * $('#example tbody tr').click( function () { + * if ( oTable.fnIsOpen(this) ) { + * oTable.fnClose( this ); + * } else { + * oTable.fnOpen( this, "Temporary row opened", "info_row" ); + * } + * } ); + * + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnIsOpen = function (nTr) { + return this.api(true).row(nTr).child.isShown(); + }; + + + /** + * This function will place a new row directly after a row which is currently + * on display on the page, with the HTML contents that is passed into the + * function. This can be used, for example, to ask for confirmation that a + * particular record should be deleted. + * @param {node} nTr The table row to 'open' + * @param {string|node|jQuery} mHtml The HTML to put into the row + * @param {string} sClass Class to give the new TD cell + * @returns {node} The row opened. Note that if the table row passed in as the + * first parameter, is not found in the table, this method will silently + * return. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable; + * + * // 'open' an information row when a row is clicked on + * $('#example tbody tr').click( function () { + * if ( oTable.fnIsOpen(this) ) { + * oTable.fnClose( this ); + * } else { + * oTable.fnOpen( this, "Temporary row opened", "info_row" ); + * } + * } ); + * + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnOpen = function (nTr, mHtml, sClass) { + return this.api(true) + .row(nTr) + .child(mHtml, sClass) + .show() + .child()[0]; + }; + + + /** + * Change the pagination - provides the internal logic for pagination in a simple API + * function. With this function you can have a DataTables table go to the next, + * previous, first or last pages. + * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last" + * or page number to jump to (integer), note that page 0 is the first page. + * @param {bool} [bRedraw=true] Redraw the table or not + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * oTable.fnPageChange( 'next' ); + * } ); + */ + this.fnPageChange = function (mAction, bRedraw) { + var api = this.api(true).page(mAction); + + if (bRedraw === undefined || bRedraw) { + api.draw(false); + } + }; + + + /** + * Show a particular column + * @param {int} iCol The column whose display should be changed + * @param {bool} bShow Show (true) or hide (false) the column + * @param {bool} [bRedraw=true] Redraw the table or not + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Hide the second column after initialisation + * oTable.fnSetColumnVis( 1, false ); + * } ); + */ + this.fnSetColumnVis = function (iCol, bShow, bRedraw) { + var api = this.api(true).column(iCol).visible(bShow); + + if (bRedraw === undefined || bRedraw) { + api.columns.adjust().draw(); + } + }; + + + /** + * Get the settings for a particular table for external manipulation + * @returns {object} DataTables settings object. See + * {@link DataTable.models.oSettings} + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * var oSettings = oTable.fnSettings(); + * + * // Show an example parameter from the settings + * alert( oSettings._iDisplayStart ); + * } ); + */ + this.fnSettings = function () { + return _fnSettingsFromNode(this[_ext.iApiIndex]); + }; + + + /** + * Sort the table by a particular column + * @param {int} iCol the data index to sort on. Note that this will not match the + * 'display index' if you have hidden data entries + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Sort immediately with columns 0 and 1 + * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] ); + * } ); + */ + this.fnSort = function (aaSort) { + this.api(true).order(aaSort).draw(); + }; + + + /** + * Attach a sort listener to an element for a given column + * @param {node} nNode the element to attach the sort listener to + * @param {int} iColumn the column that a click on this node will sort on + * @param {function} [fnCallback] callback function when sort is run + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Sort on column 1, when 'sorter' is clicked on + * oTable.fnSortListener( document.getElementById('sorter'), 1 ); + * } ); + */ + this.fnSortListener = function (nNode, iColumn, fnCallback) { + this.api(true).order.listener(nNode, iColumn, fnCallback); + }; + + + /** + * Update a table cell or row - this method will accept either a single value to + * update the cell with, an array of values with one element for each column or + * an object in the same format as the original data source. The function is + * self-referencing in order to make the multi column updates easier. + * @param {object|array|string} mData Data to update the cell/row with + * @param {node|int} mRow TR element you want to update or the aoData index + * @param {int} [iColumn] The column to update, give as null or undefined to + * update a whole row. + * @param {bool} [bRedraw=true] Redraw the table or not + * @param {bool} [bAction=true] Perform pre-draw actions or not + * @returns {int} 0 on success, 1 on error + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell + * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row + * } ); + */ + this.fnUpdate = function (mData, mRow, iColumn, bRedraw, bAction) { + var api = this.api(true); + + if (iColumn === undefined || iColumn === null) { + api.row(mRow).data(mData); + } else { + api.cell(mRow, iColumn).data(mData); + } + + if (bAction === undefined || bAction) { + api.columns.adjust(); + } + + if (bRedraw === undefined || bRedraw) { + api.draw(); + } + return 0; + }; + + + /** + * Provide a common method for plug-ins to check the version of DataTables being used, in order + * to ensure compatibility. + * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the + * formats "X" and "X.Y" are also acceptable. + * @returns {boolean} true if this version of DataTables is greater or equal to the required + * version, or false if this version of DataTales is not suitable + * @method + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * alert( oTable.fnVersionCheck( '1.9.0' ) ); + * } ); + */ + this.fnVersionCheck = _ext.fnVersionCheck; + + + var _that = this; + var emptyInit = options === undefined; + var len = this.length; + + if (emptyInit) { + options = {}; + } + + this.oApi = this.internal = _ext.internal; + + // Extend with old style plug-in API methods + for (var fn in DataTable.ext.internal) { + if (fn) { + this[fn] = _fnExternApiFunc(fn); + } + } + + this.each(function () { + // For each initialisation we want to give it a clean initialisation + // object that can be bashed around + var o = {}; + var oInit = len > 1 ? // optimisation for single table case + _fnExtend(o, options, true) : + options; + + /*global oInit,_that,emptyInit*/ + var i = 0, + iLen, j, jLen, k, kLen; + var sId = this.getAttribute('id'); + var bInitHandedOff = false; + var defaults = DataTable.defaults; + var $this = $(this); + + + /* Sanity check */ + if (this.nodeName.toLowerCase() != 'table') { + _fnLog(null, 0, 'Non-table node initialisation (' + this.nodeName + ')', 2); + return; + } + + /* Backwards compatibility for the defaults */ + _fnCompatOpts(defaults); + _fnCompatCols(defaults.column); + + /* Convert the camel-case defaults to Hungarian */ + _fnCamelToHungarian(defaults, defaults, true); + _fnCamelToHungarian(defaults.column, defaults.column, true); + + /* Setting up the initialisation object */ + _fnCamelToHungarian(defaults, $.extend(oInit, $this.data())); + + + /* Check to see if we are re-initialising a table */ + var allSettings = DataTable.settings; + for (i = 0, iLen = allSettings.length; i < iLen; i++) { + var s = allSettings[i]; + + /* Base check on table node */ + if (s.nTable == this || s.nTHead.parentNode == this || (s.nTFoot && s.nTFoot.parentNode == this)) { + var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve; + var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy; + + if (emptyInit || bRetrieve) { + return s.oInstance; + } else if (bDestroy) { + s.oInstance.fnDestroy(); + break; + } else { + _fnLog(s, 0, 'Cannot reinitialise DataTable', 3); + return; + } + } + + /* If the element we are initialising has the same ID as a table which was previously + * initialised, but the table nodes don't match (from before) then we destroy the old + * instance by simply deleting it. This is under the assumption that the table has been + * destroyed by other methods. Anyone using non-id selectors will need to do this manually + */ + if (s.sTableId == this.id) { + allSettings.splice(i, 1); + break; + } + } + + /* Ensure the table has an ID - required for accessibility */ + if (sId === null || sId === "") { + sId = "DataTables_Table_" + (DataTable.ext._unique++); + this.id = sId; + } + + /* Create the settings object for this table and set some of the default parameters */ + var oSettings = $.extend(true, {}, DataTable.models.oSettings, { + "sDestroyWidth": $this[0].style.width, + "sInstance": sId, + "sTableId": sId + }); + oSettings.nTable = this; + oSettings.oApi = _that.internal; + oSettings.oInit = oInit; + + allSettings.push(oSettings); + + // Need to add the instance after the instance after the settings object has been added + // to the settings array, so we can self reference the table instance if more than one + oSettings.oInstance = (_that.length === 1) ? _that : $this.dataTable(); + + // Backwards compatibility, before we apply all the defaults + _fnCompatOpts(oInit); + + if (oInit.oLanguage) { + _fnLanguageCompat(oInit.oLanguage); + } + + // If the length menu is given, but the init display length is not, use the length menu + if (oInit.aLengthMenu && !oInit.iDisplayLength) { + oInit.iDisplayLength = $.isArray(oInit.aLengthMenu[0]) ? + oInit.aLengthMenu[0][0] : oInit.aLengthMenu[0]; + } + + // Apply the defaults and init options to make a single init object will all + // options defined from defaults and instance options. + oInit = _fnExtend($.extend(true, {}, defaults), oInit); + + + // Map the initialisation options onto the settings object + _fnMap(oSettings.oFeatures, oInit, [ + "bPaginate", + "bLengthChange", + "bFilter", + "bSort", + "bSortMulti", + "bInfo", + "bProcessing", + "bAutoWidth", + "bSortClasses", + "bServerSide", + "bDeferRender" + ]); + _fnMap(oSettings, oInit, [ + "asStripeClasses", + "ajax", + "fnServerData", + "fnFormatNumber", + "sServerMethod", + "aaSorting", + "aaSortingFixed", + "aLengthMenu", + "sPaginationType", + "sAjaxSource", + "sAjaxDataProp", + "iStateDuration", + "sDom", + "bSortCellsTop", + "iTabIndex", + "fnStateLoadCallback", + "fnStateSaveCallback", + "renderer", + "searchDelay", + "rowId", ["iCookieDuration", "iStateDuration"], // backwards compat + ["oSearch", "oPreviousSearch"], + ["aoSearchCols", "aoPreSearchCols"], + ["iDisplayLength", "_iDisplayLength"], + ["bJQueryUI", "bJUI"] + ]); + _fnMap(oSettings.oScroll, oInit, [ + ["sScrollX", "sX"], + ["sScrollXInner", "sXInner"], + ["sScrollY", "sY"], + ["bScrollCollapse", "bCollapse"] + ]); + _fnMap(oSettings.oLanguage, oInit, "fnInfoCallback"); + + /* Callback functions which are array driven */ + _fnCallbackReg(oSettings, 'aoDrawCallback', oInit.fnDrawCallback, 'user'); + _fnCallbackReg(oSettings, 'aoServerParams', oInit.fnServerParams, 'user'); + _fnCallbackReg(oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams, 'user'); + _fnCallbackReg(oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams, 'user'); + _fnCallbackReg(oSettings, 'aoStateLoaded', oInit.fnStateLoaded, 'user'); + _fnCallbackReg(oSettings, 'aoRowCallback', oInit.fnRowCallback, 'user'); + _fnCallbackReg(oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow, 'user'); + _fnCallbackReg(oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback, 'user'); + _fnCallbackReg(oSettings, 'aoFooterCallback', oInit.fnFooterCallback, 'user'); + _fnCallbackReg(oSettings, 'aoInitComplete', oInit.fnInitComplete, 'user'); + _fnCallbackReg(oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback, 'user'); + + oSettings.rowIdFn = _fnGetObjectDataFn(oInit.rowId); + + /* Browser support detection */ + _fnBrowserDetect(oSettings); + + var oClasses = oSettings.oClasses; + + // @todo Remove in 1.11 + if (oInit.bJQueryUI) { + /* Use the JUI classes object for display. You could clone the oStdClasses object if + * you want to have multiple tables with multiple independent classes + */ + $.extend(oClasses, DataTable.ext.oJUIClasses, oInit.oClasses); + + if (oInit.sDom === defaults.sDom && defaults.sDom === "lfrtip") { + /* Set the DOM to use a layout suitable for jQuery UI's theming */ + oSettings.sDom = '<"H"lfr>t<"F"ip>'; + } + + if (!oSettings.renderer) { + oSettings.renderer = 'jqueryui'; + } else if ($.isPlainObject(oSettings.renderer) && !oSettings.renderer.header) { + oSettings.renderer.header = 'jqueryui'; + } + } else { + $.extend(oClasses, DataTable.ext.classes, oInit.oClasses); + } + $this.addClass(oClasses.sTable); + + + if (oSettings.iInitDisplayStart === undefined) { + /* Display start point, taking into account the save saving */ + oSettings.iInitDisplayStart = oInit.iDisplayStart; + oSettings._iDisplayStart = oInit.iDisplayStart; + } + + if (oInit.iDeferLoading !== null) { + oSettings.bDeferLoading = true; + var tmp = $.isArray(oInit.iDeferLoading); + oSettings._iRecordsDisplay = tmp ? oInit.iDeferLoading[0] : oInit.iDeferLoading; + oSettings._iRecordsTotal = tmp ? oInit.iDeferLoading[1] : oInit.iDeferLoading; + } + + /* Language definitions */ + var oLanguage = oSettings.oLanguage; + $.extend(true, oLanguage, oInit.oLanguage); + + if (oLanguage.sUrl !== "") { + /* Get the language definitions from a file - because this Ajax call makes the language + * get async to the remainder of this function we use bInitHandedOff to indicate that + * _fnInitialise will be fired by the returned Ajax handler, rather than the constructor + */ + $.ajax({ + dataType: 'json', + url: oLanguage.sUrl, + success: function (json) { + _fnLanguageCompat(json); + _fnCamelToHungarian(defaults.oLanguage, json); + $.extend(true, oLanguage, json); + _fnInitialise(oSettings); + }, + error: function () { + // Error occurred loading language file, continue on as best we can + _fnInitialise(oSettings); + } + }); + bInitHandedOff = true; + } + + /* + * Stripes + */ + if (oInit.asStripeClasses === null) { + oSettings.asStripeClasses = [ + oClasses.sStripeOdd, + oClasses.sStripeEven + ]; + } + + /* Remove row stripe classes if they are already on the table row */ + var stripeClasses = oSettings.asStripeClasses; + var rowOne = $this.children('tbody').find('tr').eq(0); + if ($.inArray(true, $.map(stripeClasses, function (el, i) { + return rowOne.hasClass(el); + })) !== -1) { + $('tbody tr', this).removeClass(stripeClasses.join(' ')); + oSettings.asDestroyStripes = stripeClasses.slice(); + } + + /* + * Columns + * See if we should load columns automatically or use defined ones + */ + var anThs = []; + var aoColumnsInit; + var nThead = this.getElementsByTagName('thead'); + if (nThead.length !== 0) { + _fnDetectHeader(oSettings.aoHeader, nThead[0]); + anThs = _fnGetUniqueThs(oSettings); + } + + /* If not given a column array, generate one with nulls */ + if (oInit.aoColumns === null) { + aoColumnsInit = []; + for (i = 0, iLen = anThs.length; i < iLen; i++) { + aoColumnsInit.push(null); + } + } else { + aoColumnsInit = oInit.aoColumns; + } + + /* Add the columns */ + for (i = 0, iLen = aoColumnsInit.length; i < iLen; i++) { + _fnAddColumn(oSettings, anThs ? anThs[i] : null); + } + + /* Apply the column definitions */ + _fnApplyColumnDefs(oSettings, oInit.aoColumnDefs, aoColumnsInit, function (iCol, oDef) { + _fnColumnOptions(oSettings, iCol, oDef); + }); + + /* HTML5 attribute detection - build an mData object automatically if the + * attributes are found + */ + if (rowOne.length) { + var a = function (cell, name) { + return cell.getAttribute('data-' + name) !== null ? name : null; + }; + + $(rowOne[0]).children('th, td').each(function (i, cell) { + var col = oSettings.aoColumns[i]; + + if (col.mData === i) { + var sort = a(cell, 'sort') || a(cell, 'order'); + var filter = a(cell, 'filter') || a(cell, 'search'); + + if (sort !== null || filter !== null) { + col.mData = { + _: i + '.display', + sort: sort !== null ? i + '.@data-' + sort : undefined, + type: sort !== null ? i + '.@data-' + sort : undefined, + filter: filter !== null ? i + '.@data-' + filter : undefined + }; + + _fnColumnOptions(oSettings, i); + } + } + }); + } + + var features = oSettings.oFeatures; + + /* Must be done after everything which can be overridden by the state saving! */ + if (oInit.bStateSave) { + features.bStateSave = true; + _fnLoadState(oSettings, oInit); + _fnCallbackReg(oSettings, 'aoDrawCallback', _fnSaveState, 'state_save'); + } + + + /* + * Sorting + * @todo For modularisation (1.11) this needs to do into a sort start up handler + */ + + // If aaSorting is not defined, then we use the first indicator in asSorting + // in case that has been altered, so the default sort reflects that option + if (oInit.aaSorting === undefined) { + var sorting = oSettings.aaSorting; + for (i = 0, iLen = sorting.length; i < iLen; i++) { + sorting[i][1] = oSettings.aoColumns[i].asSorting[0]; + } + } + + /* Do a first pass on the sorting classes (allows any size changes to be taken into + * account, and also will apply sorting disabled classes if disabled + */ + _fnSortingClasses(oSettings); + + if (features.bSort) { + _fnCallbackReg(oSettings, 'aoDrawCallback', function () { + if (oSettings.bSorted) { + var aSort = _fnSortFlatten(oSettings); + var sortedColumns = {}; + + $.each(aSort, function (i, val) { + sortedColumns[val.src] = val.dir; + }); + + _fnCallbackFire(oSettings, null, 'order', [oSettings, aSort, sortedColumns]); + _fnSortAria(oSettings); + } + }); + } + + _fnCallbackReg(oSettings, 'aoDrawCallback', function () { + if (oSettings.bSorted || _fnDataSource(oSettings) === 'ssp' || features.bDeferRender) { + _fnSortingClasses(oSettings); + } + }, 'sc'); + + + /* + * Final init + * Cache the header, body and footer as required, creating them if needed + */ + + // Work around for Webkit bug 83867 - store the caption-side before removing from doc + var captions = $this.children('caption').each(function () { + this._captionSide = $this.css('caption-side'); + }); + + var thead = $this.children('thead'); + if (thead.length === 0) { + thead = $('<thead/>').appendTo(this); + } + oSettings.nTHead = thead[0]; + + var tbody = $this.children('tbody'); + if (tbody.length === 0) { + tbody = $('<tbody/>').appendTo(this); + } + oSettings.nTBody = tbody[0]; + + var tfoot = $this.children('tfoot'); + if (tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "")) { + // If we are a scrolling table, and no footer has been given, then we need to create + // a tfoot element for the caption element to be appended to + tfoot = $('<tfoot/>').appendTo(this); + } + + if (tfoot.length === 0 || tfoot.children().length === 0) { + $this.addClass(oClasses.sNoFooter); + } else if (tfoot.length > 0) { + oSettings.nTFoot = tfoot[0]; + _fnDetectHeader(oSettings.aoFooter, oSettings.nTFoot); + } + + /* Check if there is data passing into the constructor */ + if (oInit.aaData) { + for (i = 0; i < oInit.aaData.length; i++) { + _fnAddData(oSettings, oInit.aaData[i]); + } + } else if (oSettings.bDeferLoading || _fnDataSource(oSettings) == 'dom') { + /* Grab the data from the page - only do this when deferred loading or no Ajax + * source since there is no point in reading the DOM data if we are then going + * to replace it with Ajax data + */ + _fnAddTr(oSettings, $(oSettings.nTBody).children('tr')); + } + + /* Copy the data index array */ + oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); + + /* Initialisation complete - table can be drawn */ + oSettings.bInitialised = true; + + /* Check if we need to initialise the table (it might not have been handed off to the + * language processor) + */ + if (bInitHandedOff === false) { + _fnInitialise(oSettings); + } + }); + _that = null; + + + return this; + }; + + + /* + * It is useful to have variables which are scoped locally so only the + * DataTables functions can access them and they don't leak into global space. + * At the same time these functions are often useful over multiple files in the + * core and API, so we list, or at least document, all variables which are used + * by DataTables as private variables here. This also ensures that there is no + * clashing of variable names and that they can easily referenced for reuse. + */ + + + // Defined else where + // _selector_run + // _selector_opts + // _selector_first + // _selector_row_indexes + + var _ext; // DataTable.ext + var _Api; // DataTable.Api + var _api_register; // DataTable.Api.register + var _api_registerPlural; // DataTable.Api.registerPlural + + var _re_dic = {}; + var _re_new_lines = /[\r\n]/g; + var _re_html = /<.*?>/g; + var _re_date_start = /^[\w\+\-]/; + var _re_date_end = /[\w\+\-]$/; + + // Escape regular expression special characters + var _re_escape_regex = new RegExp('(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-'].join('|\\') + ')', 'g'); + + // http://en.wikipedia.org/wiki/Foreign_exchange_market + // - \u20BD - Russian ruble. + // - \u20a9 - South Korean Won + // - \u20BA - Turkish Lira + // - \u20B9 - Indian Rupee + // - R - Brazil (R$) and South Africa + // - fr - Swiss Franc + // - kr - Swedish krona, Norwegian krone and Danish krone + // - \u2009 is thin space and \u202F is narrow no-break space, both used in many + // standards as thousands separators. + var _re_formatted_numeric = /[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi; + + + var _empty = function (d) { + return !d || d === true || d === '-' ? true : false; + }; + + + var _intVal = function (s) { + var integer = parseInt(s, 10); + return !isNaN(integer) && isFinite(s) ? integer : null; + }; + + // Convert from a formatted number with characters other than `.` as the + // decimal place, to a Javascript number + var _numToDecimal = function (num, decimalPoint) { + // Cache created regular expressions for speed as this function is called often + if (!_re_dic[decimalPoint]) { + _re_dic[decimalPoint] = new RegExp(_fnEscapeRegex(decimalPoint), 'g'); + } + return typeof num === 'string' && decimalPoint !== '.' ? + num.replace(/\./g, '').replace(_re_dic[decimalPoint], '.') : + num; + }; + + + var _isNumber = function (d, decimalPoint, formatted) { + var strType = typeof d === 'string'; + + // If empty return immediately so there must be a number if it is a + // formatted string (this stops the string "k", or "kr", etc being detected + // as a formatted number for currency + if (_empty(d)) { + return true; + } + + if (decimalPoint && strType) { + d = _numToDecimal(d, decimalPoint); + } + + if (formatted && strType) { + d = d.replace(_re_formatted_numeric, ''); + } + + return !isNaN(parseFloat(d)) && isFinite(d); + }; + + + // A string without HTML in it can be considered to be HTML still + var _isHtml = function (d) { + return _empty(d) || typeof d === 'string'; + }; + + + var _htmlNumeric = function (d, decimalPoint, formatted) { + if (_empty(d)) { + return true; + } + + var html = _isHtml(d); + return !html ? + null : + _isNumber(_stripHtml(d), decimalPoint, formatted) ? + true : + null; + }; + + + var _pluck = function (a, prop, prop2) { + var out = []; + var i = 0, + ien = a.length; + + // Could have the test in the loop for slightly smaller code, but speed + // is essential here + if (prop2 !== undefined) { + for (; i < ien; i++) { + if (a[i] && a[i][prop]) { + out.push(a[i][prop][prop2]); + } + } + } else { + for (; i < ien; i++) { + if (a[i]) { + out.push(a[i][prop]); + } + } + } + + return out; + }; + + + // Basically the same as _pluck, but rather than looping over `a` we use `order` + // as the indexes to pick from `a` + var _pluck_order = function (a, order, prop, prop2) { + var out = []; + var i = 0, + ien = order.length; + + // Could have the test in the loop for slightly smaller code, but speed + // is essential here + if (prop2 !== undefined) { + for (; i < ien; i++) { + if (a[order[i]][prop]) { + out.push(a[order[i]][prop][prop2]); + } + } + } else { + for (; i < ien; i++) { + out.push(a[order[i]][prop]); + } + } + + return out; + }; + + + var _range = function (len, start) { + var out = []; + var end; + + if (start === undefined) { + start = 0; + end = len; + } else { + end = start; + start = len; + } + + for (var i = start; i < end; i++) { + out.push(i); + } + + return out; + }; + + + var _removeEmpty = function (a) { + var out = []; + + for (var i = 0, ien = a.length; i < ien; i++) { + if (a[i]) { // careful - will remove all falsy values! + out.push(a[i]); + } + } + + return out; + }; + + + var _stripHtml = function (d) { + return d.replace(_re_html, ''); + }; + + + /** + * Find the unique elements in a source array. + * + * @param {array} src Source array + * @return {array} Array of unique items + * @ignore + */ + var _unique = function (src) { + // A faster unique method is to use object keys to identify used values, + // but this doesn't work with arrays or objects, which we must also + // consider. See jsperf.com/compare-array-unique-versions/4 for more + // information. + var + out = [], + val, + i, ien = src.length, + j, k = 0; + + again: for (i = 0; i < ien; i++) { + val = src[i]; + + for (j = 0; j < k; j++) { + if (out[j] === val) { + continue again; + } + } + + out.push(val); + k++; + } + + return out; + }; + + + /** + * DataTables utility methods + * + * This namespace provides helper methods that DataTables uses internally to + * create a DataTable, but which are not exclusively used only for DataTables. + * These methods can be used by extension authors to save the duplication of + * code. + * + * @namespace + */ + DataTable.util = { + /** + * Throttle the calls to a function. Arguments and context are maintained + * for the throttled function. + * + * @param {function} fn Function to be called + * @param {integer} freq Call frequency in mS + * @return {function} Wrapped function + */ + throttle: function (fn, freq) { + var + frequency = freq !== undefined ? freq : 200, + last, + timer; + + return function () { + var + that = this, + now = +new Date(), + args = arguments; + + if (last && now < last + frequency) { + clearTimeout(timer); + + timer = setTimeout(function () { + last = undefined; + fn.apply(that, args); + }, frequency); + } else { + last = now; + fn.apply(that, args); + } + }; + }, + + + /** + * Escape a string such that it can be used in a regular expression + * + * @param {string} val string to escape + * @returns {string} escaped string + */ + escapeRegex: function (val) { + return val.replace(_re_escape_regex, '\\$1'); + } + }; + + + /** + * Create a mapping object that allows camel case parameters to be looked up + * for their Hungarian counterparts. The mapping is stored in a private + * parameter called `_hungarianMap` which can be accessed on the source object. + * @param {object} o + * @memberof DataTable#oApi + */ + function _fnHungarianMap(o) { + var + hungarian = 'a aa ai ao as b fn i m o s ', + match, + newKey, + map = {}; + + $.each(o, function (key, val) { + match = key.match(/^([^A-Z]+?)([A-Z])/); + + if (match && hungarian.indexOf(match[1] + ' ') !== -1) { + newKey = key.replace(match[0], match[2].toLowerCase()); + map[newKey] = key; + + if (match[1] === 'o') { + _fnHungarianMap(o[key]); + } + } + }); + + o._hungarianMap = map; + } + + + /** + * Convert from camel case parameters to Hungarian, based on a Hungarian map + * created by _fnHungarianMap. + * @param {object} src The model object which holds all parameters that can be + * mapped. + * @param {object} user The object to convert from camel case to Hungarian. + * @param {boolean} force When set to `true`, properties which already have a + * Hungarian value in the `user` object will be overwritten. Otherwise they + * won't be. + * @memberof DataTable#oApi + */ + function _fnCamelToHungarian(src, user, force) { + if (!src._hungarianMap) { + _fnHungarianMap(src); + } + + var hungarianKey; + + $.each(user, function (key, val) { + hungarianKey = src._hungarianMap[key]; + + if (hungarianKey !== undefined && (force || user[hungarianKey] === undefined)) { + // For objects, we need to buzz down into the object to copy parameters + if (hungarianKey.charAt(0) === 'o') { + // Copy the camelCase options over to the hungarian + if (!user[hungarianKey]) { + user[hungarianKey] = {}; + } + $.extend(true, user[hungarianKey], user[key]); + + _fnCamelToHungarian(src[hungarianKey], user[hungarianKey], force); + } else { + user[hungarianKey] = user[key]; + } + } + }); + } + + + /** + * Language compatibility - when certain options are given, and others aren't, we + * need to duplicate the values over, in order to provide backwards compatibility + * with older language files. + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnLanguageCompat(lang) { + var defaults = DataTable.defaults.oLanguage; + var zeroRecords = lang.sZeroRecords; + + /* Backwards compatibility - if there is no sEmptyTable given, then use the same as + * sZeroRecords - assuming that is given. + */ + if (!lang.sEmptyTable && zeroRecords && + defaults.sEmptyTable === "No data available in table") { + _fnMap(lang, lang, 'sZeroRecords', 'sEmptyTable'); + } + + /* Likewise with loading records */ + if (!lang.sLoadingRecords && zeroRecords && + defaults.sLoadingRecords === "Loading...") { + _fnMap(lang, lang, 'sZeroRecords', 'sLoadingRecords'); + } + + // Old parameter name of the thousands separator mapped onto the new + if (lang.sInfoThousands) { + lang.sThousands = lang.sInfoThousands; + } + + var decimal = lang.sDecimal; + if (decimal) { + _addNumericSort(decimal); + } + } + + + /** + * Map one parameter onto another + * @param {object} o Object to map + * @param {*} knew The new parameter name + * @param {*} old The old parameter name + */ + var _fnCompatMap = function (o, knew, old) { + if (o[knew] !== undefined) { + o[old] = o[knew]; + } + }; + + + /** + * Provide backwards compatibility for the main DT options. Note that the new + * options are mapped onto the old parameters, so this is an external interface + * change only. + * @param {object} init Object to map + */ + function _fnCompatOpts(init) { + _fnCompatMap(init, 'ordering', 'bSort'); + _fnCompatMap(init, 'orderMulti', 'bSortMulti'); + _fnCompatMap(init, 'orderClasses', 'bSortClasses'); + _fnCompatMap(init, 'orderCellsTop', 'bSortCellsTop'); + _fnCompatMap(init, 'order', 'aaSorting'); + _fnCompatMap(init, 'orderFixed', 'aaSortingFixed'); + _fnCompatMap(init, 'paging', 'bPaginate'); + _fnCompatMap(init, 'pagingType', 'sPaginationType'); + _fnCompatMap(init, 'pageLength', 'iDisplayLength'); + _fnCompatMap(init, 'searching', 'bFilter'); + + // Boolean initialisation of x-scrolling + if (typeof init.sScrollX === 'boolean') { + init.sScrollX = init.sScrollX ? '100%' : ''; + } + if (typeof init.scrollX === 'boolean') { + init.scrollX = init.scrollX ? '100%' : ''; + } + + // Column search objects are in an array, so it needs to be converted + // element by element + var searchCols = init.aoSearchCols; + + if (searchCols) { + for (var i = 0, ien = searchCols.length; i < ien; i++) { + if (searchCols[i]) { + _fnCamelToHungarian(DataTable.models.oSearch, searchCols[i]); + } + } + } + } + + + /** + * Provide backwards compatibility for column options. Note that the new options + * are mapped onto the old parameters, so this is an external interface change + * only. + * @param {object} init Object to map + */ + function _fnCompatCols(init) { + _fnCompatMap(init, 'orderable', 'bSortable'); + _fnCompatMap(init, 'orderData', 'aDataSort'); + _fnCompatMap(init, 'orderSequence', 'asSorting'); + _fnCompatMap(init, 'orderDataType', 'sortDataType'); + + // orderData can be given as an integer + var dataSort = init.aDataSort; + if (dataSort && !$.isArray(dataSort)) { + init.aDataSort = [dataSort]; + } + } + + + /** + * Browser feature detection for capabilities, quirks + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnBrowserDetect(settings) { + // We don't need to do this every time DataTables is constructed, the values + // calculated are specific to the browser and OS configuration which we + // don't expect to change between initialisations + if (!DataTable.__browser) { + var browser = {}; + DataTable.__browser = browser; + + // Scrolling feature / quirks detection + var n = $('<div/>') + .css({ + position: 'fixed', + top: 0, + left: 0, + height: 1, + width: 1, + overflow: 'hidden' + }) + .append( + $('<div/>') + .css({ + position: 'absolute', + top: 1, + left: 1, + width: 100, + overflow: 'scroll' + }) + .append( + $('<div/>') + .css({ + width: '100%', + height: 10 + }) + ) + ) + .appendTo('body'); + + var outer = n.children(); + var inner = outer.children(); + + // Numbers below, in order, are: + // inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth + // + // IE6 XP: 100 100 100 83 + // IE7 Vista: 100 100 100 83 + // IE 8+ Windows: 83 83 100 83 + // Evergreen Windows: 83 83 100 83 + // Evergreen Mac with scrollbars: 85 85 100 85 + // Evergreen Mac without scrollbars: 100 100 100 100 + + // Get scrollbar width + browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth; + + // IE6/7 will oversize a width 100% element inside a scrolling element, to + // include the width of the scrollbar, while other browsers ensure the inner + // element is contained without forcing scrolling + browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100; + + // In rtl text layout, some browsers (most, but not all) will place the + // scrollbar on the left, rather than the right. + browser.bScrollbarLeft = Math.round(inner.offset().left) !== 1; + + // IE8- don't provide height and width for getBoundingClientRect + browser.bBounding = n[0].getBoundingClientRect().width ? true : false; + + n.remove(); + } + + $.extend(settings.oBrowser, DataTable.__browser); + settings.oScroll.iBarWidth = DataTable.__browser.barWidth; + } + + + /** + * Array.prototype reduce[Right] method, used for browsers which don't support + * JS 1.6. Done this way to reduce code size, since we iterate either way + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnReduce(that, fn, init, start, end, inc) { + var + i = start, + value, + isSet = false; + + if (init !== undefined) { + value = init; + isSet = true; + } + + while (i !== end) { + if (!that.hasOwnProperty(i)) { + continue; + } + + value = isSet ? + fn(value, that[i], i, that) : + that[i]; + + isSet = true; + i += inc; + } + + return value; + } + + /** + * Add a column to the list used for the table with default values + * @param {object} oSettings dataTables settings object + * @param {node} nTh The th element for this column + * @memberof DataTable#oApi + */ + function _fnAddColumn(oSettings, nTh) { + // Add column to aoColumns array + var oDefaults = DataTable.defaults.column; + var iCol = oSettings.aoColumns.length; + var oCol = $.extend({}, DataTable.models.oColumn, oDefaults, { + "nTh": nTh ? nTh : document.createElement('th'), + "sTitle": oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '', + "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], + "mData": oDefaults.mData ? oDefaults.mData : iCol, + idx: iCol + }); + oSettings.aoColumns.push(oCol); + + // Add search object for column specific search. Note that the `searchCols[ iCol ]` + // passed into extend can be undefined. This allows the user to give a default + // with only some of the parameters defined, and also not give a default + var searchCols = oSettings.aoPreSearchCols; + searchCols[iCol] = $.extend({}, DataTable.models.oSearch, searchCols[iCol]); + + // Use the default column options function to initialise classes etc + _fnColumnOptions(oSettings, iCol, $(nTh).data()); + } + + + /** + * Apply options for a column + * @param {object} oSettings dataTables settings object + * @param {int} iCol column index to consider + * @param {object} oOptions object with sType, bVisible and bSearchable etc + * @memberof DataTable#oApi + */ + function _fnColumnOptions(oSettings, iCol, oOptions) { + var oCol = oSettings.aoColumns[iCol]; + var oClasses = oSettings.oClasses; + var th = $(oCol.nTh); + + // Try to get width information from the DOM. We can't get it from CSS + // as we'd need to parse the CSS stylesheet. `width` option can override + if (!oCol.sWidthOrig) { + // Width attribute + oCol.sWidthOrig = th.attr('width') || null; + + // Style attribute + var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/); + if (t) { + oCol.sWidthOrig = t[1]; + } + } + + /* User specified column options */ + if (oOptions !== undefined && oOptions !== null) { + // Backwards compatibility + _fnCompatCols(oOptions); + + // Map camel case parameters to their Hungarian counterparts + _fnCamelToHungarian(DataTable.defaults.column, oOptions); + + /* Backwards compatibility for mDataProp */ + if (oOptions.mDataProp !== undefined && !oOptions.mData) { + oOptions.mData = oOptions.mDataProp; + } + + if (oOptions.sType) { + oCol._sManualType = oOptions.sType; + } + + // `class` is a reserved word in Javascript, so we need to provide + // the ability to use a valid name for the camel case input + if (oOptions.className && !oOptions.sClass) { + oOptions.sClass = oOptions.className; + } + + $.extend(oCol, oOptions); + _fnMap(oCol, oOptions, "sWidth", "sWidthOrig"); + + /* iDataSort to be applied (backwards compatibility), but aDataSort will take + * priority if defined + */ + if (oOptions.iDataSort !== undefined) { + oCol.aDataSort = [oOptions.iDataSort]; + } + _fnMap(oCol, oOptions, "aDataSort"); + } + + /* Cache the data get and set functions for speed */ + var mDataSrc = oCol.mData; + var mData = _fnGetObjectDataFn(mDataSrc); + var mRender = oCol.mRender ? _fnGetObjectDataFn(oCol.mRender) : null; + + var attrTest = function (src) { + return typeof src === 'string' && src.indexOf('@') !== -1; + }; + oCol._bAttrSrc = $.isPlainObject(mDataSrc) && ( + attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter) + ); + oCol._setter = null; + + oCol.fnGetData = function (rowData, type, meta) { + var innerData = mData(rowData, type, undefined, meta); + + return mRender && type ? + mRender(innerData, type, rowData, meta) : + innerData; + }; + oCol.fnSetData = function (rowData, val, meta) { + return _fnSetObjectDataFn(mDataSrc)(rowData, val, meta); + }; + + // Indicate if DataTables should read DOM data as an object or array + // Used in _fnGetRowElements + if (typeof mDataSrc !== 'number') { + oSettings._rowReadObject = true; + } + + /* Feature sorting overrides column specific when off */ + if (!oSettings.oFeatures.bSort) { + oCol.bSortable = false; + th.addClass(oClasses.sSortableNone); // Have to add class here as order event isn't called + } + + /* Check that the class assignment is correct for sorting */ + var bAsc = $.inArray('asc', oCol.asSorting) !== -1; + var bDesc = $.inArray('desc', oCol.asSorting) !== -1; + if (!oCol.bSortable || (!bAsc && !bDesc)) { + oCol.sSortingClass = oClasses.sSortableNone; + oCol.sSortingClassJUI = ""; + } else if (bAsc && !bDesc) { + oCol.sSortingClass = oClasses.sSortableAsc; + oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed; + } else if (!bAsc && bDesc) { + oCol.sSortingClass = oClasses.sSortableDesc; + oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed; + } else { + oCol.sSortingClass = oClasses.sSortable; + oCol.sSortingClassJUI = oClasses.sSortJUI; + } + } + + + /** + * Adjust the table column widths for new data. Note: you would probably want to + * do a redraw after calling this function! + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnAdjustColumnSizing(settings) { + /* Not interested in doing column width calculation if auto-width is disabled */ + if (settings.oFeatures.bAutoWidth !== false) { + var columns = settings.aoColumns; + + _fnCalculateColumnWidths(settings); + for (var i = 0, iLen = columns.length; i < iLen; i++) { + columns[i].nTh.style.width = columns[i].sWidth; + } + } + + var scroll = settings.oScroll; + if (scroll.sY !== '' || scroll.sX !== '') { + _fnScrollDraw(settings); + } + + _fnCallbackFire(settings, null, 'column-sizing', [settings]); + } + + + /** + * Covert the index of a visible column to the index in the data array (take account + * of hidden columns) + * @param {object} oSettings dataTables settings object + * @param {int} iMatch Visible column index to lookup + * @returns {int} i the data index + * @memberof DataTable#oApi + */ + function _fnVisibleToColumnIndex(oSettings, iMatch) { + var aiVis = _fnGetColumns(oSettings, 'bVisible'); + + return typeof aiVis[iMatch] === 'number' ? + aiVis[iMatch] : + null; + } + + + /** + * Covert the index of an index in the data array and convert it to the visible + * column index (take account of hidden columns) + * @param {int} iMatch Column index to lookup + * @param {object} oSettings dataTables settings object + * @returns {int} i the data index + * @memberof DataTable#oApi + */ + function _fnColumnIndexToVisible(oSettings, iMatch) { + var aiVis = _fnGetColumns(oSettings, 'bVisible'); + var iPos = $.inArray(iMatch, aiVis); + + return iPos !== -1 ? iPos : null; + } + + + /** + * Get the number of visible columns + * @param {object} oSettings dataTables settings object + * @returns {int} i the number of visible columns + * @memberof DataTable#oApi + */ + function _fnVisbleColumns(oSettings) { + var vis = 0; + + // No reduce in IE8, use a loop for now + $.each(oSettings.aoColumns, function (i, col) { + if (col.bVisible && $(col.nTh).css('display') !== 'none') { + vis++; + } + }); + + return vis; + } + + + /** + * Get an array of column indexes that match a given property + * @param {object} oSettings dataTables settings object + * @param {string} sParam Parameter in aoColumns to look for - typically + * bVisible or bSearchable + * @returns {array} Array of indexes with matched properties + * @memberof DataTable#oApi + */ + function _fnGetColumns(oSettings, sParam) { + var a = []; + + $.map(oSettings.aoColumns, function (val, i) { + if (val[sParam]) { + a.push(i); + } + }); + + return a; + } + + + /** + * Calculate the 'type' of a column + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnColumnTypes(settings) { + var columns = settings.aoColumns; + var data = settings.aoData; + var types = DataTable.ext.type.detect; + var i, ien, j, jen, k, ken; + var col, cell, detectedType, cache; + + // For each column, spin over the + for (i = 0, ien = columns.length; i < ien; i++) { + col = columns[i]; + cache = []; + + if (!col.sType && col._sManualType) { + col.sType = col._sManualType; + } else if (!col.sType) { + for (j = 0, jen = types.length; j < jen; j++) { + for (k = 0, ken = data.length; k < ken; k++) { + // Use a cache array so we only need to get the type data + // from the formatter once (when using multiple detectors) + if (cache[k] === undefined) { + cache[k] = _fnGetCellData(settings, k, i, 'type'); + } + + detectedType = types[j](cache[k], settings); + + // If null, then this type can't apply to this column, so + // rather than testing all cells, break out. There is an + // exception for the last type which is `html`. We need to + // scan all rows since it is possible to mix string and HTML + // types + if (!detectedType && j !== types.length - 1) { + break; + } + + // Only a single match is needed for html type since it is + // bottom of the pile and very similar to string + if (detectedType === 'html') { + break; + } + } + + // Type is valid for all data points in the column - use this + // type + if (detectedType) { + col.sType = detectedType; + break; + } + } + + // Fall back - if no type was detected, always use string + if (!col.sType) { + col.sType = 'string'; + } + } + } + } + + + /** + * Take the column definitions and static columns arrays and calculate how + * they relate to column indexes. The callback function will then apply the + * definition found for a column to a suitable configuration object. + * @param {object} oSettings dataTables settings object + * @param {array} aoColDefs The aoColumnDefs array that is to be applied + * @param {array} aoCols The aoColumns array that defines columns individually + * @param {function} fn Callback function - takes two parameters, the calculated + * column index and the definition for that column. + * @memberof DataTable#oApi + */ + function _fnApplyColumnDefs(oSettings, aoColDefs, aoCols, fn) { + var i, iLen, j, jLen, k, kLen, def; + var columns = oSettings.aoColumns; + + // Column definitions with aTargets + if (aoColDefs) { + /* Loop over the definitions array - loop in reverse so first instance has priority */ + for (i = aoColDefs.length - 1; i >= 0; i--) { + def = aoColDefs[i]; + + /* Each definition can target multiple columns, as it is an array */ + var aTargets = def.targets !== undefined ? + def.targets : + def.aTargets; + + if (!$.isArray(aTargets)) { + aTargets = [aTargets]; + } + + for (j = 0, jLen = aTargets.length; j < jLen; j++) { + if (typeof aTargets[j] === 'number' && aTargets[j] >= 0) { + /* Add columns that we don't yet know about */ + while (columns.length <= aTargets[j]) { + _fnAddColumn(oSettings); + } + + /* Integer, basic index */ + fn(aTargets[j], def); + } else if (typeof aTargets[j] === 'number' && aTargets[j] < 0) { + /* Negative integer, right to left column counting */ + fn(columns.length + aTargets[j], def); + } else if (typeof aTargets[j] === 'string') { + /* Class name matching on TH element */ + for (k = 0, kLen = columns.length; k < kLen; k++) { + if (aTargets[j] == "_all" || + $(columns[k].nTh).hasClass(aTargets[j])) { + fn(k, def); + } + } + } + } + } + } + + // Statically defined columns array + if (aoCols) { + for (i = 0, iLen = aoCols.length; i < iLen; i++) { + fn(i, aoCols[i]); + } + } + } + + /** + * Add a data array to the table, creating DOM node etc. This is the parallel to + * _fnGatherData, but for adding rows from a Javascript source, rather than a + * DOM source. + * @param {object} oSettings dataTables settings object + * @param {array} aData data array to be added + * @param {node} [nTr] TR element to add to the table - optional. If not given, + * DataTables will create a row automatically + * @param {array} [anTds] Array of TD|TH elements for the row - must be given + * if nTr is. + * @returns {int} >=0 if successful (index of new aoData entry), -1 if failed + * @memberof DataTable#oApi + */ + function _fnAddData(oSettings, aDataIn, nTr, anTds) { + /* Create the object for storing information about this new row */ + var iRow = oSettings.aoData.length; + var oData = $.extend(true, {}, DataTable.models.oRow, { + src: nTr ? 'dom' : 'data', + idx: iRow + }); + + oData._aData = aDataIn; + oSettings.aoData.push(oData); + + /* Create the cells */ + var nTd, sThisType; + var columns = oSettings.aoColumns; + + // Invalidate the column types as the new data needs to be revalidated + for (var i = 0, iLen = columns.length; i < iLen; i++) { + columns[i].sType = null; + } + + /* Add to the display array */ + oSettings.aiDisplayMaster.push(iRow); + + var id = oSettings.rowIdFn(aDataIn); + if (id !== undefined) { + oSettings.aIds[id] = oData; + } + + /* Create the DOM information, or register it if already present */ + if (nTr || !oSettings.oFeatures.bDeferRender) { + _fnCreateTr(oSettings, iRow, nTr, anTds); + } + + return iRow; + } + + + /** + * Add one or more TR elements to the table. Generally we'd expect to + * use this for reading data from a DOM sourced table, but it could be + * used for an TR element. Note that if a TR is given, it is used (i.e. + * it is not cloned). + * @param {object} settings dataTables settings object + * @param {array|node|jQuery} trs The TR element(s) to add to the table + * @returns {array} Array of indexes for the added rows + * @memberof DataTable#oApi + */ + function _fnAddTr(settings, trs) { + var row; + + // Allow an individual node to be passed in + if (!(trs instanceof $)) { + trs = $(trs); + } + + return trs.map(function (i, el) { + row = _fnGetRowElements(settings, el); + return _fnAddData(settings, row.data, el, row.cells); + }); + } + + + /** + * Take a TR element and convert it to an index in aoData + * @param {object} oSettings dataTables settings object + * @param {node} n the TR element to find + * @returns {int} index if the node is found, null if not + * @memberof DataTable#oApi + */ + function _fnNodeToDataIndex(oSettings, n) { + return (n._DT_RowIndex !== undefined) ? n._DT_RowIndex : null; + } + + + /** + * Take a TD element and convert it into a column data index (not the visible index) + * @param {object} oSettings dataTables settings object + * @param {int} iRow The row number the TD/TH can be found in + * @param {node} n The TD/TH element to find + * @returns {int} index if the node is found, -1 if not + * @memberof DataTable#oApi + */ + function _fnNodeToColumnIndex(oSettings, iRow, n) { + return $.inArray(n, oSettings.aoData[iRow].anCells); + } + + + /** + * Get the data for a given cell from the internal cache, taking into account data mapping + * @param {object} settings dataTables settings object + * @param {int} rowIdx aoData row id + * @param {int} colIdx Column index + * @param {string} type data get type ('display', 'type' 'filter' 'sort') + * @returns {*} Cell data + * @memberof DataTable#oApi + */ + function _fnGetCellData(settings, rowIdx, colIdx, type) { + var draw = settings.iDraw; + var col = settings.aoColumns[colIdx]; + var rowData = settings.aoData[rowIdx]._aData; + var defaultContent = col.sDefaultContent; + var cellData = col.fnGetData(rowData, type, { + settings: settings, + row: rowIdx, + col: colIdx + }); + + if (cellData === undefined) { + if (settings.iDrawError != draw && defaultContent === null) { + _fnLog(settings, 0, "Requested unknown parameter " + + (typeof col.mData == 'function' ? '{function}' : "'" + col.mData + "'") + + " for row " + rowIdx + ", column " + colIdx, 4); + settings.iDrawError = draw; + } + return defaultContent; + } + + // When the data source is null and a specific data type is requested (i.e. + // not the original data), we can use default column data + if ((cellData === rowData || cellData === null) && defaultContent !== null && type !== undefined) { + cellData = defaultContent; + } else if (typeof cellData === 'function') { + // If the data source is a function, then we run it and use the return, + // executing in the scope of the data object (for instances) + return cellData.call(rowData); + } + + if (cellData === null && type == 'display') { + return ''; + } + return cellData; + } + + + /** + * Set the value for a specific cell, into the internal data cache + * @param {object} settings dataTables settings object + * @param {int} rowIdx aoData row id + * @param {int} colIdx Column index + * @param {*} val Value to set + * @memberof DataTable#oApi + */ + function _fnSetCellData(settings, rowIdx, colIdx, val) { + var col = settings.aoColumns[colIdx]; + var rowData = settings.aoData[rowIdx]._aData; + + col.fnSetData(rowData, val, { + settings: settings, + row: rowIdx, + col: colIdx + }); + } + + + // Private variable that is used to match action syntax in the data property object + var __reArray = /\[.*?\]$/; + var __reFn = /\(\)$/; + + /** + * Split string on periods, taking into account escaped periods + * @param {string} str String to split + * @return {array} Split string + */ + function _fnSplitObjNotation(str) { + return $.map(str.match(/(\\.|[^\.])+/g) || [''], function (s) { + return s.replace(/\\./g, '.'); + }); + } + + + /** + * Return a function that can be used to get data from a source object, taking + * into account the ability to use nested objects as a source + * @param {string|int|function} mSource The data source for the object + * @returns {function} Data get function + * @memberof DataTable#oApi + */ + function _fnGetObjectDataFn(mSource) { + if ($.isPlainObject(mSource)) { + /* Build an object of get functions, and wrap them in a single call */ + var o = {}; + $.each(mSource, function (key, val) { + if (val) { + o[key] = _fnGetObjectDataFn(val); + } + }); + + return function (data, type, row, meta) { + var t = o[type] || o._; + return t !== undefined ? + t(data, type, row, meta) : + data; + }; + } else if (mSource === null) { + /* Give an empty string for rendering / sorting etc */ + return function (data) { // type, row and meta also passed, but not used + return data; + }; + } else if (typeof mSource === 'function') { + return function (data, type, row, meta) { + return mSource(data, type, row, meta); + }; + } else if (typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || + mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1)) { + /* If there is a . in the source string then the data source is in a + * nested object so we loop over the data for each level to get the next + * level down. On each loop we test for undefined, and if found immediately + * return. This allows entire objects to be missing and sDefaultContent to + * be used if defined, rather than throwing an error + */ + var fetchData = function (data, type, src) { + var arrayNotation, funcNotation, out, innerSrc; + + if (src !== "") { + var a = _fnSplitObjNotation(src); + + for (var i = 0, iLen = a.length; i < iLen; i++) { + // Check if we are dealing with special notation + arrayNotation = a[i].match(__reArray); + funcNotation = a[i].match(__reFn); + + if (arrayNotation) { + // Array notation + a[i] = a[i].replace(__reArray, ''); + + // Condition allows simply [] to be passed in + if (a[i] !== "") { + data = data[a[i]]; + } + out = []; + + // Get the remainder of the nested object to get + a.splice(0, i + 1); + innerSrc = a.join('.'); + + // Traverse each entry in the array getting the properties requested + if ($.isArray(data)) { + for (var j = 0, jLen = data.length; j < jLen; j++) { + out.push(fetchData(data[j], type, innerSrc)); + } + } + + // If a string is given in between the array notation indicators, that + // is used to join the strings together, otherwise an array is returned + var join = arrayNotation[0].substring(1, arrayNotation[0].length - 1); + data = (join === "") ? out : out.join(join); + + // The inner call to fetchData has already traversed through the remainder + // of the source requested, so we exit from the loop + break; + } else if (funcNotation) { + // Function call + a[i] = a[i].replace(__reFn, ''); + data = data[a[i]](); + continue; + } + + if (data === null || data[a[i]] === undefined) { + return undefined; + } + data = data[a[i]]; + } + } + + return data; + }; + + return function (data, type) { // row and meta also passed, but not used + return fetchData(data, type, mSource); + }; + } else { + /* Array or flat object mapping */ + return function (data, type) { // row and meta also passed, but not used + return data[mSource]; + }; + } + } + + + /** + * Return a function that can be used to set data from a source object, taking + * into account the ability to use nested objects as a source + * @param {string|int|function} mSource The data source for the object + * @returns {function} Data set function + * @memberof DataTable#oApi + */ + function _fnSetObjectDataFn(mSource) { + if ($.isPlainObject(mSource)) { + /* Unlike get, only the underscore (global) option is used for for + * setting data since we don't know the type here. This is why an object + * option is not documented for `mData` (which is read/write), but it is + * for `mRender` which is read only. + */ + return _fnSetObjectDataFn(mSource._); + } else if (mSource === null) { + /* Nothing to do when the data source is null */ + return function () {}; + } else if (typeof mSource === 'function') { + return function (data, val, meta) { + mSource(data, 'set', val, meta); + }; + } else if (typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || + mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1)) { + /* Like the get, we need to get data from a nested object */ + var setData = function (data, val, src) { + var a = _fnSplitObjNotation(src), + b; + var aLast = a[a.length - 1]; + var arrayNotation, funcNotation, o, innerSrc; + + for (var i = 0, iLen = a.length - 1; i < iLen; i++) { + // Check if we are dealing with an array notation request + arrayNotation = a[i].match(__reArray); + funcNotation = a[i].match(__reFn); + + if (arrayNotation) { + a[i] = a[i].replace(__reArray, ''); + data[a[i]] = []; + + // Get the remainder of the nested object to set so we can recurse + b = a.slice(); + b.splice(0, i + 1); + innerSrc = b.join('.'); + + // Traverse each entry in the array setting the properties requested + if ($.isArray(val)) { + for (var j = 0, jLen = val.length; j < jLen; j++) { + o = {}; + setData(o, val[j], innerSrc); + data[a[i]].push(o); + } + } else { + // We've been asked to save data to an array, but it + // isn't array data to be saved. Best that can be done + // is to just save the value. + data[a[i]] = val; + } + + // The inner call to setData has already traversed through the remainder + // of the source and has set the data, thus we can exit here + return; + } else if (funcNotation) { + // Function call + a[i] = a[i].replace(__reFn, ''); + data = data[a[i]](val); + } + + // If the nested object doesn't currently exist - since we are + // trying to set the value - create it + if (data[a[i]] === null || data[a[i]] === undefined) { + data[a[i]] = {}; + } + data = data[a[i]]; + } + + // Last item in the input - i.e, the actual set + if (aLast.match(__reFn)) { + // Function call + data = data[aLast.replace(__reFn, '')](val); + } else { + // If array notation is used, we just want to strip it and use the property name + // and assign the value. If it isn't used, then we get the result we want anyway + data[aLast.replace(__reArray, '')] = val; + } + }; + + return function (data, val) { // meta is also passed in, but not used + return setData(data, val, mSource); + }; + } else { + /* Array or flat object mapping */ + return function (data, val) { // meta is also passed in, but not used + data[mSource] = val; + }; + } + } + + + /** + * Return an array with the full table data + * @param {object} oSettings dataTables settings object + * @returns array {array} aData Master data array + * @memberof DataTable#oApi + */ + function _fnGetDataMaster(settings) { + return _pluck(settings.aoData, '_aData'); + } + + + /** + * Nuke the table + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnClearTable(settings) { + settings.aoData.length = 0; + settings.aiDisplayMaster.length = 0; + settings.aiDisplay.length = 0; + settings.aIds = {}; + } + + + /** + * Take an array of integers (index array) and remove a target integer (value - not + * the key!) + * @param {array} a Index array to target + * @param {int} iTarget value to find + * @memberof DataTable#oApi + */ + function _fnDeleteIndex(a, iTarget, splice) { + var iTargetIndex = -1; + + for (var i = 0, iLen = a.length; i < iLen; i++) { + if (a[i] == iTarget) { + iTargetIndex = i; + } else if (a[i] > iTarget) { + a[i]--; + } + } + + if (iTargetIndex != -1 && splice === undefined) { + a.splice(iTargetIndex, 1); + } + } + + + /** + * Mark cached data as invalid such that a re-read of the data will occur when + * the cached data is next requested. Also update from the data source object. + * + * @param {object} settings DataTables settings object + * @param {int} rowIdx Row index to invalidate + * @param {string} [src] Source to invalidate from: undefined, 'auto', 'dom' + * or 'data' + * @param {int} [colIdx] Column index to invalidate. If undefined the whole + * row will be invalidated + * @memberof DataTable#oApi + * + * @todo For the modularisation of v1.11 this will need to become a callback, so + * the sort and filter methods can subscribe to it. That will required + * initialisation options for sorting, which is why it is not already baked in + */ + function _fnInvalidate(settings, rowIdx, src, colIdx) { + var row = settings.aoData[rowIdx]; + var i, ien; + var cellWrite = function (cell, col) { + // This is very frustrating, but in IE if you just write directly + // to innerHTML, and elements that are overwritten are GC'ed, + // even if there is a reference to them elsewhere + while (cell.childNodes.length) { + cell.removeChild(cell.firstChild); + } + + cell.innerHTML = _fnGetCellData(settings, rowIdx, col, 'display'); + }; + + // Are we reading last data from DOM or the data object? + if (src === 'dom' || ((!src || src === 'auto') && row.src === 'dom')) { + // Read the data from the DOM + row._aData = _fnGetRowElements( + settings, row, colIdx, colIdx === undefined ? undefined : row._aData + ) + .data; + } else { + // Reading from data object, update the DOM + var cells = row.anCells; + + if (cells) { + if (colIdx !== undefined) { + cellWrite(cells[colIdx], colIdx); + } else { + for (i = 0, ien = cells.length; i < ien; i++) { + cellWrite(cells[i], i); + } + } + } + } + + // For both row and cell invalidation, the cached data for sorting and + // filtering is nulled out + row._aSortData = null; + row._aFilterData = null; + + // Invalidate the type for a specific column (if given) or all columns since + // the data might have changed + var cols = settings.aoColumns; + if (colIdx !== undefined) { + cols[colIdx].sType = null; + } else { + for (i = 0, ien = cols.length; i < ien; i++) { + cols[i].sType = null; + } + + // Update DataTables special `DT_*` attributes for the row + _fnRowAttributes(settings, row); + } + } + + + /** + * Build a data source object from an HTML row, reading the contents of the + * cells that are in the row. + * + * @param {object} settings DataTables settings object + * @param {node|object} TR element from which to read data or existing row + * object from which to re-read the data from the cells + * @param {int} [colIdx] Optional column index + * @param {array|object} [d] Data source object. If `colIdx` is given then this + * parameter should also be given and will be used to write the data into. + * Only the column in question will be written + * @returns {object} Object with two parameters: `data` the data read, in + * document order, and `cells` and array of nodes (they can be useful to the + * caller, so rather than needing a second traversal to get them, just return + * them from here). + * @memberof DataTable#oApi + */ + function _fnGetRowElements(settings, row, colIdx, d) { + var + tds = [], + td = row.firstChild, + name, col, o, i = 0, + contents, + columns = settings.aoColumns, + objectRead = settings._rowReadObject; + + // Allow the data object to be passed in, or construct + d = d !== undefined ? + d : + objectRead ? {} : []; + + var attr = function (str, td) { + if (typeof str === 'string') { + var idx = str.indexOf('@'); + + if (idx !== -1) { + var attr = str.substring(idx + 1); + var setter = _fnSetObjectDataFn(str); + setter(d, td.getAttribute(attr)); + } + } + }; + + // Read data from a cell and store into the data object + var cellProcess = function (cell) { + if (colIdx === undefined || colIdx === i) { + col = columns[i]; + contents = $.trim(cell.innerHTML); + + if (col && col._bAttrSrc) { + var setter = _fnSetObjectDataFn(col.mData._); + setter(d, contents); + + attr(col.mData.sort, cell); + attr(col.mData.type, cell); + attr(col.mData.filter, cell); + } else { + // Depending on the `data` option for the columns the data can + // be read to either an object or an array. + if (objectRead) { + if (!col._setter) { + // Cache the setter function + col._setter = _fnSetObjectDataFn(col.mData); + } + col._setter(d, contents); + } else { + d[i] = contents; + } + } + } + + i++; + }; + + if (td) { + // `tr` element was passed in + while (td) { + name = td.nodeName.toUpperCase(); + + if (name == "TD" || name == "TH") { + cellProcess(td); + tds.push(td); + } + + td = td.nextSibling; + } + } else { + // Existing row object passed in + tds = row.anCells; + + for (var j = 0, jen = tds.length; j < jen; j++) { + cellProcess(tds[j]); + } + } + + // Read the ID from the DOM if present + var rowNode = row.firstChild ? row : row.nTr; + + if (rowNode) { + var id = rowNode.getAttribute('id'); + + if (id) { + _fnSetObjectDataFn(settings.rowId)(d, id); + } + } + + return { + data: d, + cells: tds + }; + } + /** + * Create a new TR element (and it's TD children) for a row + * @param {object} oSettings dataTables settings object + * @param {int} iRow Row to consider + * @param {node} [nTrIn] TR element to add to the table - optional. If not given, + * DataTables will create a row automatically + * @param {array} [anTds] Array of TD|TH elements for the row - must be given + * if nTr is. + * @memberof DataTable#oApi + */ + function _fnCreateTr(oSettings, iRow, nTrIn, anTds) { + var + row = oSettings.aoData[iRow], + rowData = row._aData, + cells = [], + nTr, nTd, oCol, + i, iLen; + + if (row.nTr === null) { + nTr = nTrIn || document.createElement('tr'); + + row.nTr = nTr; + row.anCells = cells; + + /* Use a private property on the node to allow reserve mapping from the node + * to the aoData array for fast look up + */ + nTr._DT_RowIndex = iRow; + + /* Special parameters can be given by the data source to be used on the row */ + _fnRowAttributes(oSettings, row); + + /* Process each column */ + for (i = 0, iLen = oSettings.aoColumns.length; i < iLen; i++) { + oCol = oSettings.aoColumns[i]; + + nTd = nTrIn ? anTds[i] : document.createElement(oCol.sCellType); + nTd._DT_CellIndex = { + row: iRow, + column: i + }; + + cells.push(nTd); + + // Need to create the HTML if new, or if a rendering function is defined + if ((!nTrIn || oCol.mRender || oCol.mData !== i) && + (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i + '.display') + ) { + nTd.innerHTML = _fnGetCellData(oSettings, iRow, i, 'display'); + } + + /* Add user defined class */ + if (oCol.sClass) { + nTd.className += ' ' + oCol.sClass; + } + + // Visibility - add or remove as required + if (oCol.bVisible && !nTrIn) { + nTr.appendChild(nTd); + } else if (!oCol.bVisible && nTrIn) { + nTd.parentNode.removeChild(nTd); + } + + if (oCol.fnCreatedCell) { + oCol.fnCreatedCell.call(oSettings.oInstance, + nTd, _fnGetCellData(oSettings, iRow, i), rowData, iRow, i + ); + } + } + + _fnCallbackFire(oSettings, 'aoRowCreatedCallback', null, [nTr, rowData, iRow]); + } + + // Remove once webkit bug 131819 and Chromium bug 365619 have been resolved + // and deployed + row.nTr.setAttribute('role', 'row'); + } + + + /** + * Add attributes to a row based on the special `DT_*` parameters in a data + * source object. + * @param {object} settings DataTables settings object + * @param {object} DataTables row object for the row to be modified + * @memberof DataTable#oApi + */ + function _fnRowAttributes(settings, row) { + var tr = row.nTr; + var data = row._aData; + + if (tr) { + var id = settings.rowIdFn(data); + + if (id) { + tr.id = id; + } + + if (data.DT_RowClass) { + // Remove any classes added by DT_RowClass before + var a = data.DT_RowClass.split(' '); + row.__rowc = row.__rowc ? + _unique(row.__rowc.concat(a)) : + a; + + $(tr) + .removeClass(row.__rowc.join(' ')) + .addClass(data.DT_RowClass); + } + + if (data.DT_RowAttr) { + $(tr).attr(data.DT_RowAttr); + } + + if (data.DT_RowData) { + $(tr).data(data.DT_RowData); + } + } + } + + + /** + * Create the HTML header for the table + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnBuildHead(oSettings) { + var i, ien, cell, row, column; + var thead = oSettings.nTHead; + var tfoot = oSettings.nTFoot; + var createHeader = $('th, td', thead).length === 0; + var classes = oSettings.oClasses; + var columns = oSettings.aoColumns; + + if (createHeader) { + row = $('<tr/>').appendTo(thead); + } + + for (i = 0, ien = columns.length; i < ien; i++) { + column = columns[i]; + cell = $(column.nTh).addClass(column.sClass); + + if (createHeader) { + cell.appendTo(row); + } + + // 1.11 move into sorting + if (oSettings.oFeatures.bSort) { + cell.addClass(column.sSortingClass); + + if (column.bSortable !== false) { + cell + .attr('tabindex', oSettings.iTabIndex) + .attr('aria-controls', oSettings.sTableId); + + _fnSortAttachListener(oSettings, column.nTh, i); + } + } + + if (column.sTitle != cell[0].innerHTML) { + cell.html(column.sTitle); + } + + _fnRenderer(oSettings, 'header')( + oSettings, cell, column, classes + ); + } + + if (createHeader) { + _fnDetectHeader(oSettings.aoHeader, thead); + } + + /* ARIA role for the rows */ + $(thead).find('>tr').attr('role', 'row'); + + /* Deal with the footer - add classes if required */ + $(thead).find('>tr>th, >tr>td').addClass(classes.sHeaderTH); + $(tfoot).find('>tr>th, >tr>td').addClass(classes.sFooterTH); + + // Cache the footer cells. Note that we only take the cells from the first + // row in the footer. If there is more than one row the user wants to + // interact with, they need to use the table().foot() method. Note also this + // allows cells to be used for multiple columns using colspan + if (tfoot !== null) { + var cells = oSettings.aoFooter[0]; + + for (i = 0, ien = cells.length; i < ien; i++) { + column = columns[i]; + column.nTf = cells[i].cell; + + if (column.sClass) { + $(column.nTf).addClass(column.sClass); + } + } + } + } + + + /** + * Draw the header (or footer) element based on the column visibility states. The + * methodology here is to use the layout array from _fnDetectHeader, modified for + * the instantaneous column visibility, to construct the new layout. The grid is + * traversed over cell at a time in a rows x columns grid fashion, although each + * cell insert can cover multiple elements in the grid - which is tracks using the + * aApplied array. Cell inserts in the grid will only occur where there isn't + * already a cell in that position. + * @param {object} oSettings dataTables settings object + * @param array {objects} aoSource Layout array from _fnDetectHeader + * @param {boolean} [bIncludeHidden=false] If true then include the hidden columns in the calc, + * @memberof DataTable#oApi + */ + function _fnDrawHead(oSettings, aoSource, bIncludeHidden) { + var i, iLen, j, jLen, k, kLen, n, nLocalTr; + var aoLocal = []; + var aApplied = []; + var iColumns = oSettings.aoColumns.length; + var iRowspan, iColspan; + + if (!aoSource) { + return; + } + + if (bIncludeHidden === undefined) { + bIncludeHidden = false; + } + + /* Make a copy of the master layout array, but without the visible columns in it */ + for (i = 0, iLen = aoSource.length; i < iLen; i++) { + aoLocal[i] = aoSource[i].slice(); + aoLocal[i].nTr = aoSource[i].nTr; + + /* Remove any columns which are currently hidden */ + for (j = iColumns - 1; j >= 0; j--) { + if (!oSettings.aoColumns[j].bVisible && !bIncludeHidden) { + aoLocal[i].splice(j, 1); + } + } + + /* Prep the applied array - it needs an element for each row */ + aApplied.push([]); + } + + for (i = 0, iLen = aoLocal.length; i < iLen; i++) { + nLocalTr = aoLocal[i].nTr; + + /* All cells are going to be replaced, so empty out the row */ + if (nLocalTr) { + while ((n = nLocalTr.firstChild)) { + nLocalTr.removeChild(n); + } + } + + for (j = 0, jLen = aoLocal[i].length; j < jLen; j++) { + iRowspan = 1; + iColspan = 1; + + /* Check to see if there is already a cell (row/colspan) covering our target + * insert point. If there is, then there is nothing to do. + */ + if (aApplied[i][j] === undefined) { + nLocalTr.appendChild(aoLocal[i][j].cell); + aApplied[i][j] = 1; + + /* Expand the cell to cover as many rows as needed */ + while (aoLocal[i + iRowspan] !== undefined && + aoLocal[i][j].cell == aoLocal[i + iRowspan][j].cell) { + aApplied[i + iRowspan][j] = 1; + iRowspan++; + } + + /* Expand the cell to cover as many columns as needed */ + while (aoLocal[i][j + iColspan] !== undefined && + aoLocal[i][j].cell == aoLocal[i][j + iColspan].cell) { + /* Must update the applied array over the rows for the columns */ + for (k = 0; k < iRowspan; k++) { + aApplied[i + k][j + iColspan] = 1; + } + iColspan++; + } + + /* Do the actual expansion in the DOM */ + $(aoLocal[i][j].cell) + .attr('rowspan', iRowspan) + .attr('colspan', iColspan); + } + } + } + } + + + /** + * Insert the required TR nodes into the table for display + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnDraw(oSettings) { + /* Provide a pre-callback function which can be used to cancel the draw is false is returned */ + var aPreDraw = _fnCallbackFire(oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings]); + if ($.inArray(false, aPreDraw) !== -1) { + _fnProcessingDisplay(oSettings, false); + return; + } + + var i, iLen, n; + var anRows = []; + var iRowCount = 0; + var asStripeClasses = oSettings.asStripeClasses; + var iStripes = asStripeClasses.length; + var iOpenRows = oSettings.aoOpenRows.length; + var oLang = oSettings.oLanguage; + var iInitDisplayStart = oSettings.iInitDisplayStart; + var bServerSide = _fnDataSource(oSettings) == 'ssp'; + var aiDisplay = oSettings.aiDisplay; + + oSettings.bDrawing = true; + + /* Check and see if we have an initial draw position from state saving */ + if (iInitDisplayStart !== undefined && iInitDisplayStart !== -1) { + oSettings._iDisplayStart = bServerSide ? + iInitDisplayStart : + iInitDisplayStart >= oSettings.fnRecordsDisplay() ? + 0 : + iInitDisplayStart; + + oSettings.iInitDisplayStart = -1; + } + + var iDisplayStart = oSettings._iDisplayStart; + var iDisplayEnd = oSettings.fnDisplayEnd(); + + /* Server-side processing draw intercept */ + if (oSettings.bDeferLoading) { + oSettings.bDeferLoading = false; + oSettings.iDraw++; + _fnProcessingDisplay(oSettings, false); + } else if (!bServerSide) { + oSettings.iDraw++; + } else if (!oSettings.bDestroying && !_fnAjaxUpdate(oSettings)) { + return; + } + + if (aiDisplay.length !== 0) { + var iStart = bServerSide ? 0 : iDisplayStart; + var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd; + + for (var j = iStart; j < iEnd; j++) { + var iDataIndex = aiDisplay[j]; + var aoData = oSettings.aoData[iDataIndex]; + if (aoData.nTr === null) { + _fnCreateTr(oSettings, iDataIndex); + } + + var nRow = aoData.nTr; + + /* Remove the old striping classes and then add the new one */ + if (iStripes !== 0) { + var sStripe = asStripeClasses[iRowCount % iStripes]; + if (aoData._sRowStripe != sStripe) { + $(nRow).removeClass(aoData._sRowStripe).addClass(sStripe); + aoData._sRowStripe = sStripe; + } + } + + // Row callback functions - might want to manipulate the row + // iRowCount and j are not currently documented. Are they at all + // useful? + _fnCallbackFire(oSettings, 'aoRowCallback', null, [nRow, aoData._aData, iRowCount, j]); + + anRows.push(nRow); + iRowCount++; + } + } else { + /* Table is empty - create a row with an empty message in it */ + var sZero = oLang.sZeroRecords; + if (oSettings.iDraw == 1 && _fnDataSource(oSettings) == 'ajax') { + sZero = oLang.sLoadingRecords; + } else if (oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0) { + sZero = oLang.sEmptyTable; + } + + anRows[0] = $('<tr/>', { + 'class': iStripes ? asStripeClasses[0] : '' + }) + .append($('<td />', { + 'valign': 'top', + 'colSpan': _fnVisbleColumns(oSettings), + 'class': oSettings.oClasses.sRowEmpty + }).html(sZero))[0]; + } + + /* Header and footer callbacks */ + _fnCallbackFire(oSettings, 'aoHeaderCallback', 'header', [$(oSettings.nTHead).children('tr')[0], + _fnGetDataMaster(oSettings), iDisplayStart, iDisplayEnd, aiDisplay + ]); + + _fnCallbackFire(oSettings, 'aoFooterCallback', 'footer', [$(oSettings.nTFoot).children('tr')[0], + _fnGetDataMaster(oSettings), iDisplayStart, iDisplayEnd, aiDisplay + ]); + + var body = $(oSettings.nTBody); + + body.children().detach(); + body.append($(anRows)); + + /* Call all required callback functions for the end of a draw */ + _fnCallbackFire(oSettings, 'aoDrawCallback', 'draw', [oSettings]); + + /* Draw is complete, sorting and filtering must be as well */ + oSettings.bSorted = false; + oSettings.bFiltered = false; + oSettings.bDrawing = false; + } + + + /** + * Redraw the table - taking account of the various features which are enabled + * @param {object} oSettings dataTables settings object + * @param {boolean} [holdPosition] Keep the current paging position. By default + * the paging is reset to the first page + * @memberof DataTable#oApi + */ + function _fnReDraw(settings, holdPosition) { + var + features = settings.oFeatures, + sort = features.bSort, + filter = features.bFilter; + + if (sort) { + _fnSort(settings); + } + + if (filter) { + _fnFilterComplete(settings, settings.oPreviousSearch); + } else { + // No filtering, so we want to just use the display master + settings.aiDisplay = settings.aiDisplayMaster.slice(); + } + + if (holdPosition !== true) { + settings._iDisplayStart = 0; + } + + // Let any modules know about the draw hold position state (used by + // scrolling internally) + settings._drawHold = holdPosition; + + _fnDraw(settings); + + settings._drawHold = false; + } + + + /** + * Add the options to the page HTML for the table + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnAddOptionsHtml(oSettings) { + var classes = oSettings.oClasses; + var table = $(oSettings.nTable); + var holding = $('<div/>').insertBefore(table); // Holding element for speed + var features = oSettings.oFeatures; + + // All DataTables are wrapped in a div + var insert = $('<div/>', { + id: oSettings.sTableId + '_wrapper', + 'class': classes.sWrapper + (oSettings.nTFoot ? '' : ' ' + classes.sNoFooter) + }); + + oSettings.nHolding = holding[0]; + oSettings.nTableWrapper = insert[0]; + oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling; + + /* Loop over the user set positioning and place the elements as needed */ + var aDom = oSettings.sDom.split(''); + var featureNode, cOption, nNewNode, cNext, sAttr, j; + for (var i = 0; i < aDom.length; i++) { + featureNode = null; + cOption = aDom[i]; + + if (cOption == '<') { + /* New container div */ + nNewNode = $('<div/>')[0]; + + /* Check to see if we should append an id and/or a class name to the container */ + cNext = aDom[i + 1]; + if (cNext == "'" || cNext == '"') { + sAttr = ""; + j = 2; + while (aDom[i + j] != cNext) { + sAttr += aDom[i + j]; + j++; + } + + /* Replace jQuery UI constants @todo depreciated */ + if (sAttr == "H") { + sAttr = classes.sJUIHeader; + } else if (sAttr == "F") { + sAttr = classes.sJUIFooter; + } + + /* The attribute can be in the format of "#id.class", "#id" or "class" This logic + * breaks the string into parts and applies them as needed + */ + if (sAttr.indexOf('.') != -1) { + var aSplit = sAttr.split('.'); + nNewNode.id = aSplit[0].substr(1, aSplit[0].length - 1); + nNewNode.className = aSplit[1]; + } else if (sAttr.charAt(0) == "#") { + nNewNode.id = sAttr.substr(1, sAttr.length - 1); + } else { + nNewNode.className = sAttr; + } + + i += j; /* Move along the position array */ + } + + insert.append(nNewNode); + insert = $(nNewNode); + } else if (cOption == '>') { + /* End container div */ + insert = insert.parent(); + } + // @todo Move options into their own plugins? + else if (cOption == 'l' && features.bPaginate && features.bLengthChange) { + /* Length */ + featureNode = _fnFeatureHtmlLength(oSettings); + } else if (cOption == 'f' && features.bFilter) { + /* Filter */ + featureNode = _fnFeatureHtmlFilter(oSettings); + } else if (cOption == 'r' && features.bProcessing) { + /* pRocessing */ + featureNode = _fnFeatureHtmlProcessing(oSettings); + } else if (cOption == 't') { + /* Table */ + featureNode = _fnFeatureHtmlTable(oSettings); + } else if (cOption == 'i' && features.bInfo) { + /* Info */ + featureNode = _fnFeatureHtmlInfo(oSettings); + } else if (cOption == 'p' && features.bPaginate) { + /* Pagination */ + featureNode = _fnFeatureHtmlPaginate(oSettings); + } else if (DataTable.ext.feature.length !== 0) { + /* Plug-in features */ + var aoFeatures = DataTable.ext.feature; + for (var k = 0, kLen = aoFeatures.length; k < kLen; k++) { + if (cOption == aoFeatures[k].cFeature) { + featureNode = aoFeatures[k].fnInit(oSettings); + break; + } + } + } + + /* Add to the 2D features array */ + if (featureNode) { + var aanFeatures = oSettings.aanFeatures; + + if (!aanFeatures[cOption]) { + aanFeatures[cOption] = []; + } + + aanFeatures[cOption].push(featureNode); + insert.append(featureNode); + } + } + + /* Built our DOM structure - replace the holding div with what we want */ + holding.replaceWith(insert); + oSettings.nHolding = null; + } + + + /** + * Use the DOM source to create up an array of header cells. The idea here is to + * create a layout grid (array) of rows x columns, which contains a reference + * to the cell that that point in the grid (regardless of col/rowspan), such that + * any column / row could be removed and the new grid constructed + * @param array {object} aLayout Array to store the calculated layout in + * @param {node} nThead The header/footer element for the table + * @memberof DataTable#oApi + */ + function _fnDetectHeader(aLayout, nThead) { + var nTrs = $(nThead).children('tr'); + var nTr, nCell; + var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan; + var bUnique; + var fnShiftCol = function (a, i, j) { + var k = a[i]; + while (k[j]) { + j++; + } + return j; + }; + + aLayout.splice(0, aLayout.length); + + /* We know how many rows there are in the layout - so prep it */ + for (i = 0, iLen = nTrs.length; i < iLen; i++) { + aLayout.push([]); + } + + /* Calculate a layout array */ + for (i = 0, iLen = nTrs.length; i < iLen; i++) { + nTr = nTrs[i]; + iColumn = 0; + + /* For every cell in the row... */ + nCell = nTr.firstChild; + while (nCell) { + if (nCell.nodeName.toUpperCase() == "TD" || + nCell.nodeName.toUpperCase() == "TH") { + /* Get the col and rowspan attributes from the DOM and sanitise them */ + iColspan = nCell.getAttribute('colspan') * 1; + iRowspan = nCell.getAttribute('rowspan') * 1; + iColspan = (!iColspan || iColspan === 0 || iColspan === 1) ? 1 : iColspan; + iRowspan = (!iRowspan || iRowspan === 0 || iRowspan === 1) ? 1 : iRowspan; + + /* There might be colspan cells already in this row, so shift our target + * accordingly + */ + iColShifted = fnShiftCol(aLayout, i, iColumn); + + /* Cache calculation for unique columns */ + bUnique = iColspan === 1 ? true : false; + + /* If there is col / rowspan, copy the information into the layout grid */ + for (l = 0; l < iColspan; l++) { + for (k = 0; k < iRowspan; k++) { + aLayout[i + k][iColShifted + l] = { + "cell": nCell, + "unique": bUnique + }; + aLayout[i + k].nTr = nTr; + } + } + } + nCell = nCell.nextSibling; + } + } + } + + + /** + * Get an array of unique th elements, one for each column + * @param {object} oSettings dataTables settings object + * @param {node} nHeader automatically detect the layout from this node - optional + * @param {array} aLayout thead/tfoot layout from _fnDetectHeader - optional + * @returns array {node} aReturn list of unique th's + * @memberof DataTable#oApi + */ + function _fnGetUniqueThs(oSettings, nHeader, aLayout) { + var aReturn = []; + if (!aLayout) { + aLayout = oSettings.aoHeader; + if (nHeader) { + aLayout = []; + _fnDetectHeader(aLayout, nHeader); + } + } + + for (var i = 0, iLen = aLayout.length; i < iLen; i++) { + for (var j = 0, jLen = aLayout[i].length; j < jLen; j++) { + if (aLayout[i][j].unique && + (!aReturn[j] || !oSettings.bSortCellsTop)) { + aReturn[j] = aLayout[i][j].cell; + } + } + } + + return aReturn; + } + + /** + * Create an Ajax call based on the table's settings, taking into account that + * parameters can have multiple forms, and backwards compatibility. + * + * @param {object} oSettings dataTables settings object + * @param {array} data Data to send to the server, required by + * DataTables - may be augmented by developer callbacks + * @param {function} fn Callback function to run when data is obtained + */ + function _fnBuildAjax(oSettings, data, fn) { + // Compatibility with 1.9-, allow fnServerData and event to manipulate + _fnCallbackFire(oSettings, 'aoServerParams', 'serverParams', [data]); + + // Convert to object based for 1.10+ if using the old array scheme which can + // come from server-side processing or serverParams + if (data && $.isArray(data)) { + var tmp = {}; + var rbracket = /(.*?)\[\]$/; + + $.each(data, function (key, val) { + var match = val.name.match(rbracket); + + if (match) { + // Support for arrays + var name = match[0]; + + if (!tmp[name]) { + tmp[name] = []; + } + tmp[name].push(val.value); + } else { + tmp[val.name] = val.value; + } + }); + data = tmp; + } + + var ajaxData; + var ajax = oSettings.ajax; + var instance = oSettings.oInstance; + var callback = function (json) { + _fnCallbackFire(oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR]); + fn(json); + }; + + if ($.isPlainObject(ajax) && ajax.data) { + ajaxData = ajax.data; + + var newData = $.isFunction(ajaxData) ? + ajaxData(data, oSettings) : // fn can manipulate data or return + ajaxData; // an object object or array to merge + + // If the function returned something, use that alone + data = $.isFunction(ajaxData) && newData ? + newData : + $.extend(true, data, newData); + + // Remove the data property as we've resolved it already and don't want + // jQuery to do it again (it is restored at the end of the function) + delete ajax.data; + } + + var baseAjax = { + "data": data, + "success": function (json) { + var error = json.error || json.sError; + if (error) { + _fnLog(oSettings, 0, error); + } + + oSettings.json = json; + callback(json); + }, + "dataType": "json", + "cache": false, + "type": oSettings.sServerMethod, + "error": function (xhr, error, thrown) { + var ret = _fnCallbackFire(oSettings, null, 'xhr', [oSettings, null, oSettings.jqXHR]); + + if ($.inArray(true, ret) === -1) { + if (error == "parsererror") { + _fnLog(oSettings, 0, 'Invalid JSON response', 1); + } else if (xhr.readyState === 4) { + _fnLog(oSettings, 0, 'Ajax error', 7); + } + } + + _fnProcessingDisplay(oSettings, false); + } + }; + + // Store the data submitted for the API + oSettings.oAjaxData = data; + + // Allow plug-ins and external processes to modify the data + _fnCallbackFire(oSettings, null, 'preXhr', [oSettings, data]); + + if (oSettings.fnServerData) { + // DataTables 1.9- compatibility + oSettings.fnServerData.call(instance, + oSettings.sAjaxSource, + $.map(data, function (val, key) { // Need to convert back to 1.9 trad format + return { + name: key, + value: val + }; + }), + callback, + oSettings + ); + } else if (oSettings.sAjaxSource || typeof ajax === 'string') { + // DataTables 1.9- compatibility + oSettings.jqXHR = $.ajax($.extend(baseAjax, { + url: ajax || oSettings.sAjaxSource + })); + } else if ($.isFunction(ajax)) { + // Is a function - let the caller define what needs to be done + oSettings.jqXHR = ajax.call(instance, data, callback, oSettings); + } else { + // Object to extend the base settings + oSettings.jqXHR = $.ajax($.extend(baseAjax, ajax)); + + // Restore for next time around + ajax.data = ajaxData; + } + } + + + /** + * Update the table using an Ajax call + * @param {object} settings dataTables settings object + * @returns {boolean} Block the table drawing or not + * @memberof DataTable#oApi + */ + function _fnAjaxUpdate(settings) { + if (settings.bAjaxDataGet) { + settings.iDraw++; + _fnProcessingDisplay(settings, true); + + _fnBuildAjax( + settings, + _fnAjaxParameters(settings), + function (json) { + _fnAjaxUpdateDraw(settings, json); + } + ); + + return false; + } + return true; + } + + + /** + * Build up the parameters in an object needed for a server-side processing + * request. Note that this is basically done twice, is different ways - a modern + * method which is used by default in DataTables 1.10 which uses objects and + * arrays, or the 1.9- method with is name / value pairs. 1.9 method is used if + * the sAjaxSource option is used in the initialisation, or the legacyAjax + * option is set. + * @param {object} oSettings dataTables settings object + * @returns {bool} block the table drawing or not + * @memberof DataTable#oApi + */ + function _fnAjaxParameters(settings) { + var + columns = settings.aoColumns, + columnCount = columns.length, + features = settings.oFeatures, + preSearch = settings.oPreviousSearch, + preColSearch = settings.aoPreSearchCols, + i, data = [], + dataProp, column, columnSearch, + sort = _fnSortFlatten(settings), + displayStart = settings._iDisplayStart, + displayLength = features.bPaginate !== false ? + settings._iDisplayLength : + -1; + + var param = function (name, value) { + data.push({ + 'name': name, + 'value': value + }); + }; + + // DataTables 1.9- compatible method + param('sEcho', settings.iDraw); + param('iColumns', columnCount); + param('sColumns', _pluck(columns, 'sName').join(',')); + param('iDisplayStart', displayStart); + param('iDisplayLength', displayLength); + + // DataTables 1.10+ method + var d = { + draw: settings.iDraw, + columns: [], + order: [], + start: displayStart, + length: displayLength, + search: { + value: preSearch.sSearch, + regex: preSearch.bRegex + } + }; + + for (i = 0; i < columnCount; i++) { + column = columns[i]; + columnSearch = preColSearch[i]; + dataProp = typeof column.mData == "function" ? 'function' : column.mData; + + d.columns.push({ + data: dataProp, + name: column.sName, + searchable: column.bSearchable, + orderable: column.bSortable, + search: { + value: columnSearch.sSearch, + regex: columnSearch.bRegex + } + }); + + param("mDataProp_" + i, dataProp); + + if (features.bFilter) { + param('sSearch_' + i, columnSearch.sSearch); + param('bRegex_' + i, columnSearch.bRegex); + param('bSearchable_' + i, column.bSearchable); + } + + if (features.bSort) { + param('bSortable_' + i, column.bSortable); + } + } + + if (features.bFilter) { + param('sSearch', preSearch.sSearch); + param('bRegex', preSearch.bRegex); + } + + if (features.bSort) { + $.each(sort, function (i, val) { + d.order.push({ + column: val.col, + dir: val.dir + }); + + param('iSortCol_' + i, val.col); + param('sSortDir_' + i, val.dir); + }); + + param('iSortingCols', sort.length); + } + + // If the legacy.ajax parameter is null, then we automatically decide which + // form to use, based on sAjaxSource + var legacy = DataTable.ext.legacy.ajax; + if (legacy === null) { + return settings.sAjaxSource ? data : d; + } + + // Otherwise, if legacy has been specified then we use that to decide on the + // form + return legacy ? data : d; + } + + + /** + * Data the data from the server (nuking the old) and redraw the table + * @param {object} oSettings dataTables settings object + * @param {object} json json data return from the server. + * @param {string} json.sEcho Tracking flag for DataTables to match requests + * @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering + * @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering + * @param {array} json.aaData The data to display on this page + * @param {string} [json.sColumns] Column ordering (sName, comma separated) + * @memberof DataTable#oApi + */ + function _fnAjaxUpdateDraw(settings, json) { + // v1.10 uses camelCase variables, while 1.9 uses Hungarian notation. + // Support both + var compat = function (old, modern) { + return json[old] !== undefined ? json[old] : json[modern]; + }; + + var data = _fnAjaxDataSrc(settings, json); + var draw = compat('sEcho', 'draw'); + var recordsTotal = compat('iTotalRecords', 'recordsTotal'); + var recordsFiltered = compat('iTotalDisplayRecords', 'recordsFiltered'); + + if (draw) { + // Protect against out of sequence returns + if (draw * 1 < settings.iDraw) { + return; + } + settings.iDraw = draw * 1; + } + + _fnClearTable(settings); + settings._iRecordsTotal = parseInt(recordsTotal, 10); + settings._iRecordsDisplay = parseInt(recordsFiltered, 10); + + for (var i = 0, ien = data.length; i < ien; i++) { + _fnAddData(settings, data[i]); + } + settings.aiDisplay = settings.aiDisplayMaster.slice(); + + settings.bAjaxDataGet = false; + _fnDraw(settings); + + if (!settings._bInitComplete) { + _fnInitComplete(settings, json); + } + + settings.bAjaxDataGet = true; + _fnProcessingDisplay(settings, false); + } + + + /** + * Get the data from the JSON data source to use for drawing a table. Using + * `_fnGetObjectDataFn` allows the data to be sourced from a property of the + * source object, or from a processing function. + * @param {object} oSettings dataTables settings object + * @param {object} json Data source object / array from the server + * @return {array} Array of data to use + */ + function _fnAjaxDataSrc(oSettings, json) { + var dataSrc = $.isPlainObject(oSettings.ajax) && oSettings.ajax.dataSrc !== undefined ? + oSettings.ajax.dataSrc : + oSettings.sAjaxDataProp; // Compatibility with 1.9-. + + // Compatibility with 1.9-. In order to read from aaData, check if the + // default has been changed, if not, check for aaData + if (dataSrc === 'data') { + return json.aaData || json[dataSrc]; + } + + return dataSrc !== "" ? + _fnGetObjectDataFn(dataSrc)(json) : + json; + } + + /** + * Generate the node required for filtering text + * @returns {node} Filter control element + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnFeatureHtmlFilter(settings) { + var classes = settings.oClasses; + var tableId = settings.sTableId; + var language = settings.oLanguage; + var previousSearch = settings.oPreviousSearch; + var features = settings.aanFeatures; + var input = '<input type="search" class="' + classes.sFilterInput + '"/>'; + + var str = language.sSearch; + str = str.match(/_INPUT_/) ? + str.replace('_INPUT_', input) : + str + input; + + var filter = $('<div/>', { + 'id': !features.f ? tableId + '_filter' : null, + 'class': classes.sFilter + }) + .append($('<label/>').append(str)); + + var searchFn = function () { + /* Update all other filter input elements for the new display */ + var n = features.f; + var val = !this.value ? "" : this.value; // mental IE8 fix :-( + + /* Now do the filter */ + if (val != previousSearch.sSearch) { + _fnFilterComplete(settings, { + "sSearch": val, + "bRegex": previousSearch.bRegex, + "bSmart": previousSearch.bSmart, + "bCaseInsensitive": previousSearch.bCaseInsensitive + }); + + // Need to redraw, without resorting + settings._iDisplayStart = 0; + _fnDraw(settings); + } + }; + + var searchDelay = settings.searchDelay !== null ? + settings.searchDelay : + _fnDataSource(settings) === 'ssp' ? + 400 : + 0; + + var jqFilter = $('input', filter) + .val(previousSearch.sSearch) + .attr('placeholder', language.sSearchPlaceholder) + .bind( + 'keyup.DT search.DT input.DT paste.DT cut.DT', + searchDelay ? + _fnThrottle(searchFn, searchDelay) : + searchFn + ) + .bind('keypress.DT', function (e) { + /* Prevent form submission */ + if (e.keyCode == 13) { + return false; + } + }) + .attr('aria-controls', tableId); + + // Update the input elements whenever the table is filtered + $(settings.nTable).on('search.dt.DT', function (ev, s) { + if (settings === s) { + // IE9 throws an 'unknown error' if document.activeElement is used + // inside an iframe or frame... + try { + if (jqFilter[0] !== document.activeElement) { + jqFilter.val(previousSearch.sSearch); + } + } catch (e) {} + } + }); + + return filter[0]; + } + + + /** + * Filter the table using both the global filter and column based filtering + * @param {object} oSettings dataTables settings object + * @param {object} oSearch search information + * @param {int} [iForce] force a research of the master array (1) or not (undefined or 0) + * @memberof DataTable#oApi + */ + function _fnFilterComplete(oSettings, oInput, iForce) { + var oPrevSearch = oSettings.oPreviousSearch; + var aoPrevSearch = oSettings.aoPreSearchCols; + var fnSaveFilter = function (oFilter) { + /* Save the filtering values */ + oPrevSearch.sSearch = oFilter.sSearch; + oPrevSearch.bRegex = oFilter.bRegex; + oPrevSearch.bSmart = oFilter.bSmart; + oPrevSearch.bCaseInsensitive = oFilter.bCaseInsensitive; + }; + var fnRegex = function (o) { + // Backwards compatibility with the bEscapeRegex option + return o.bEscapeRegex !== undefined ? !o.bEscapeRegex : o.bRegex; + }; + + // Resolve any column types that are unknown due to addition or invalidation + // @todo As per sort - can this be moved into an event handler? + _fnColumnTypes(oSettings); + + /* In server-side processing all filtering is done by the server, so no point hanging around here */ + if (_fnDataSource(oSettings) != 'ssp') { + /* Global filter */ + _fnFilter(oSettings, oInput.sSearch, iForce, fnRegex(oInput), oInput.bSmart, oInput.bCaseInsensitive); + fnSaveFilter(oInput); + + /* Now do the individual column filter */ + for (var i = 0; i < aoPrevSearch.length; i++) { + _fnFilterColumn(oSettings, aoPrevSearch[i].sSearch, i, fnRegex(aoPrevSearch[i]), + aoPrevSearch[i].bSmart, aoPrevSearch[i].bCaseInsensitive); + } + + /* Custom filtering */ + _fnFilterCustom(oSettings); + } else { + fnSaveFilter(oInput); + } + + /* Tell the draw function we have been filtering */ + oSettings.bFiltered = true; + _fnCallbackFire(oSettings, null, 'search', [oSettings]); + } + + + /** + * Apply custom filtering functions + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnFilterCustom(settings) { + var filters = DataTable.ext.search; + var displayRows = settings.aiDisplay; + var row, rowIdx; + + for (var i = 0, ien = filters.length; i < ien; i++) { + var rows = []; + + // Loop over each row and see if it should be included + for (var j = 0, jen = displayRows.length; j < jen; j++) { + rowIdx = displayRows[j]; + row = settings.aoData[rowIdx]; + + if (filters[i](settings, row._aFilterData, rowIdx, row._aData, j)) { + rows.push(rowIdx); + } + } + + // So the array reference doesn't break set the results into the + // existing array + displayRows.length = 0; + $.merge(displayRows, rows); + } + } + + + /** + * Filter the table on a per-column basis + * @param {object} oSettings dataTables settings object + * @param {string} sInput string to filter on + * @param {int} iColumn column to filter + * @param {bool} bRegex treat search string as a regular expression or not + * @param {bool} bSmart use smart filtering or not + * @param {bool} bCaseInsensitive Do case insenstive matching or not + * @memberof DataTable#oApi + */ + function _fnFilterColumn(settings, searchStr, colIdx, regex, smart, caseInsensitive) { + if (searchStr === '') { + return; + } + + var data; + var display = settings.aiDisplay; + var rpSearch = _fnFilterCreateSearch(searchStr, regex, smart, caseInsensitive); + + for (var i = display.length - 1; i >= 0; i--) { + data = settings.aoData[display[i]]._aFilterData[colIdx]; + + if (!rpSearch.test(data)) { + display.splice(i, 1); + } + } + } + + + /** + * Filter the data table based on user input and draw the table + * @param {object} settings dataTables settings object + * @param {string} input string to filter on + * @param {int} force optional - force a research of the master array (1) or not (undefined or 0) + * @param {bool} regex treat as a regular expression or not + * @param {bool} smart perform smart filtering or not + * @param {bool} caseInsensitive Do case insenstive matching or not + * @memberof DataTable#oApi + */ + function _fnFilter(settings, input, force, regex, smart, caseInsensitive) { + var rpSearch = _fnFilterCreateSearch(input, regex, smart, caseInsensitive); + var prevSearch = settings.oPreviousSearch.sSearch; + var displayMaster = settings.aiDisplayMaster; + var display, invalidated, i; + + // Need to take account of custom filtering functions - always filter + if (DataTable.ext.search.length !== 0) { + force = true; + } + + // Check if any of the rows were invalidated + invalidated = _fnFilterData(settings); + + // If the input is blank - we just want the full data set + if (input.length <= 0) { + settings.aiDisplay = displayMaster.slice(); + } else { + // New search - start from the master array + if (invalidated || + force || + prevSearch.length > input.length || + input.indexOf(prevSearch) !== 0 || + settings.bSorted // On resort, the display master needs to be + // re-filtered since indexes will have changed + ) { + settings.aiDisplay = displayMaster.slice(); + } + + // Search the display array + display = settings.aiDisplay; + + for (i = display.length - 1; i >= 0; i--) { + if (!rpSearch.test(settings.aoData[display[i]]._sFilterRow)) { + display.splice(i, 1); + } + } + } + } + + + /** + * Build a regular expression object suitable for searching a table + * @param {string} sSearch string to search for + * @param {bool} bRegex treat as a regular expression or not + * @param {bool} bSmart perform smart filtering or not + * @param {bool} bCaseInsensitive Do case insensitive matching or not + * @returns {RegExp} constructed object + * @memberof DataTable#oApi + */ + function _fnFilterCreateSearch(search, regex, smart, caseInsensitive) { + search = regex ? + search : + _fnEscapeRegex(search); + + if (smart) { + /* For smart filtering we want to allow the search to work regardless of + * word order. We also want double quoted text to be preserved, so word + * order is important - a la google. So this is what we want to + * generate: + * + * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$ + */ + var a = $.map(search.match(/"[^"]+"|[^ ]+/g) || [''], function (word) { + if (word.charAt(0) === '"') { + var m = word.match(/^"(.*)"$/); + word = m ? m[1] : word; + } + + return word.replace('"', ''); + }); + + search = '^(?=.*?' + a.join(')(?=.*?') + ').*$'; + } + + return new RegExp(search, caseInsensitive ? 'i' : ''); + } + + + /** + * Escape a string such that it can be used in a regular expression + * @param {string} sVal string to escape + * @returns {string} escaped string + * @memberof DataTable#oApi + */ + var _fnEscapeRegex = DataTable.util.escapeRegex; + + var __filter_div = $('<div>')[0]; + var __filter_div_textContent = __filter_div.textContent !== undefined; + + // Update the filtering data for each row if needed (by invalidation or first run) + function _fnFilterData(settings) { + var columns = settings.aoColumns; + var column; + var i, j, ien, jen, filterData, cellData, row; + var fomatters = DataTable.ext.type.search; + var wasInvalidated = false; + + for (i = 0, ien = settings.aoData.length; i < ien; i++) { + row = settings.aoData[i]; + + if (!row._aFilterData) { + filterData = []; + + for (j = 0, jen = columns.length; j < jen; j++) { + column = columns[j]; + + if (column.bSearchable) { + cellData = _fnGetCellData(settings, i, j, 'filter'); + + if (fomatters[column.sType]) { + cellData = fomatters[column.sType](cellData); + } + + // Search in DataTables 1.10 is string based. In 1.11 this + // should be altered to also allow strict type checking. + if (cellData === null) { + cellData = ''; + } + + if (typeof cellData !== 'string' && cellData.toString) { + cellData = cellData.toString(); + } + } else { + cellData = ''; + } + + // If it looks like there is an HTML entity in the string, + // attempt to decode it so sorting works as expected. Note that + // we could use a single line of jQuery to do this, but the DOM + // method used here is much faster http://jsperf.com/html-decode + if (cellData.indexOf && cellData.indexOf('&') !== -1) { + __filter_div.innerHTML = cellData; + cellData = __filter_div_textContent ? + __filter_div.textContent : + __filter_div.innerText; + } + + if (cellData.replace) { + cellData = cellData.replace(/[\r\n]/g, ''); + } + + filterData.push(cellData); + } + + row._aFilterData = filterData; + row._sFilterRow = filterData.join(' '); + wasInvalidated = true; + } + } + + return wasInvalidated; + } + + + /** + * Convert from the internal Hungarian notation to camelCase for external + * interaction + * @param {object} obj Object to convert + * @returns {object} Inverted object + * @memberof DataTable#oApi + */ + function _fnSearchToCamel(obj) { + return { + search: obj.sSearch, + smart: obj.bSmart, + regex: obj.bRegex, + caseInsensitive: obj.bCaseInsensitive + }; + } + + + /** + * Convert from camelCase notation to the internal Hungarian. We could use the + * Hungarian convert function here, but this is cleaner + * @param {object} obj Object to convert + * @returns {object} Inverted object + * @memberof DataTable#oApi + */ + function _fnSearchToHung(obj) { + return { + sSearch: obj.search, + bSmart: obj.smart, + bRegex: obj.regex, + bCaseInsensitive: obj.caseInsensitive + }; + } + + /** + * Generate the node required for the info display + * @param {object} oSettings dataTables settings object + * @returns {node} Information element + * @memberof DataTable#oApi + */ + function _fnFeatureHtmlInfo(settings) { + var + tid = settings.sTableId, + nodes = settings.aanFeatures.i, + n = $('<div/>', { + 'class': settings.oClasses.sInfo, + 'id': !nodes ? tid + '_info' : null + }); + + if (!nodes) { + // Update display on each draw + settings.aoDrawCallback.push({ + "fn": _fnUpdateInfo, + "sName": "information" + }); + + n + .attr('role', 'status') + .attr('aria-live', 'polite'); + + // Table is described by our info div + $(settings.nTable).attr('aria-describedby', tid + '_info'); + } + + return n[0]; + } + + + /** + * Update the information elements in the display + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnUpdateInfo(settings) { + /* Show information about the table */ + var nodes = settings.aanFeatures.i; + if (nodes.length === 0) { + return; + } + + var + lang = settings.oLanguage, + start = settings._iDisplayStart + 1, + end = settings.fnDisplayEnd(), + max = settings.fnRecordsTotal(), + total = settings.fnRecordsDisplay(), + out = total ? + lang.sInfo : + lang.sInfoEmpty; + + if (total !== max) { + /* Record set after filtering */ + out += ' ' + lang.sInfoFiltered; + } + + // Convert the macros + out += lang.sInfoPostFix; + out = _fnInfoMacros(settings, out); + + var callback = lang.fnInfoCallback; + if (callback !== null) { + out = callback.call(settings.oInstance, + settings, start, end, max, total, out + ); + } + + $(nodes).html(out); + } + + + function _fnInfoMacros(settings, str) { + // When infinite scrolling, we are always starting at 1. _iDisplayStart is used only + // internally + var + formatter = settings.fnFormatNumber, + start = settings._iDisplayStart + 1, + len = settings._iDisplayLength, + vis = settings.fnRecordsDisplay(), + all = len === -1; + + return str. + replace(/_START_/g, formatter.call(settings, start)). + replace(/_END_/g, formatter.call(settings, settings.fnDisplayEnd())). + replace(/_MAX_/g, formatter.call(settings, settings.fnRecordsTotal())). + replace(/_TOTAL_/g, formatter.call(settings, vis)). + replace(/_PAGE_/g, formatter.call(settings, all ? 1 : Math.ceil(start / len))). + replace(/_PAGES_/g, formatter.call(settings, all ? 1 : Math.ceil(vis / len))); + } + + + /** + * Draw the table for the first time, adding all required features + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnInitialise(settings) { + var i, iLen, iAjaxStart = settings.iInitDisplayStart; + var columns = settings.aoColumns, + column; + var features = settings.oFeatures; + var deferLoading = settings.bDeferLoading; // value modified by the draw + + /* Ensure that the table data is fully initialised */ + if (!settings.bInitialised) { + setTimeout(function () { + _fnInitialise(settings); + }, 200); + return; + } + + /* Show the display HTML options */ + _fnAddOptionsHtml(settings); + + /* Build and draw the header / footer for the table */ + _fnBuildHead(settings); + _fnDrawHead(settings, settings.aoHeader); + _fnDrawHead(settings, settings.aoFooter); + + /* Okay to show that something is going on now */ + _fnProcessingDisplay(settings, true); + + /* Calculate sizes for columns */ + if (features.bAutoWidth) { + _fnCalculateColumnWidths(settings); + } + + for (i = 0, iLen = columns.length; i < iLen; i++) { + column = columns[i]; + + if (column.sWidth) { + column.nTh.style.width = _fnStringToCss(column.sWidth); + } + } + + _fnCallbackFire(settings, null, 'preInit', [settings]); + + // If there is default sorting required - let's do it. The sort function + // will do the drawing for us. Otherwise we draw the table regardless of the + // Ajax source - this allows the table to look initialised for Ajax sourcing + // data (show 'loading' message possibly) + _fnReDraw(settings); + + // Server-side processing init complete is done by _fnAjaxUpdateDraw + var dataSrc = _fnDataSource(settings); + if (dataSrc != 'ssp' || deferLoading) { + // if there is an ajax source load the data + if (dataSrc == 'ajax') { + _fnBuildAjax(settings, [], function (json) { + var aData = _fnAjaxDataSrc(settings, json); + + // Got the data - add it to the table + for (i = 0; i < aData.length; i++) { + _fnAddData(settings, aData[i]); + } + + // Reset the init display for cookie saving. We've already done + // a filter, and therefore cleared it before. So we need to make + // it appear 'fresh' + settings.iInitDisplayStart = iAjaxStart; + + _fnReDraw(settings); + + _fnProcessingDisplay(settings, false); + _fnInitComplete(settings, json); + }, settings); + } else { + _fnProcessingDisplay(settings, false); + _fnInitComplete(settings); + } + } + } + + + /** + * Draw the table for the first time, adding all required features + * @param {object} oSettings dataTables settings object + * @param {object} [json] JSON from the server that completed the table, if using Ajax source + * with client-side processing (optional) + * @memberof DataTable#oApi + */ + function _fnInitComplete(settings, json) { + settings._bInitComplete = true; + + // When data was added after the initialisation (data or Ajax) we need to + // calculate the column sizing + if (json || settings.oInit.aaData) { + _fnAdjustColumnSizing(settings); + } + + _fnCallbackFire(settings, null, 'plugin-init', [settings, json]); + _fnCallbackFire(settings, 'aoInitComplete', 'init', [settings, json]); + } + + + function _fnLengthChange(settings, val) { + var len = parseInt(val, 10); + settings._iDisplayLength = len; + + _fnLengthOverflow(settings); + + // Fire length change event + _fnCallbackFire(settings, null, 'length', [settings, len]); + } + + + /** + * Generate the node required for user display length changing + * @param {object} settings dataTables settings object + * @returns {node} Display length feature node + * @memberof DataTable#oApi + */ + function _fnFeatureHtmlLength(settings) { + var + classes = settings.oClasses, + tableId = settings.sTableId, + menu = settings.aLengthMenu, + d2 = $.isArray(menu[0]), + lengths = d2 ? menu[0] : menu, + language = d2 ? menu[1] : menu; + + var select = $('<select/>', { + 'name': tableId + '_length', + 'aria-controls': tableId, + 'class': classes.sLengthSelect + }); + + for (var i = 0, ien = lengths.length; i < ien; i++) { + select[0][i] = new Option(language[i], lengths[i]); + } + + var div = $('<div><label/></div>').addClass(classes.sLength); + if (!settings.aanFeatures.l) { + div[0].id = tableId + '_length'; + } + + div.children().append( + settings.oLanguage.sLengthMenu.replace('_MENU_', select[0].outerHTML) + ); + + // Can't use `select` variable as user might provide their own and the + // reference is broken by the use of outerHTML + $('select', div) + .val(settings._iDisplayLength) + .bind('change.DT', function (e) { + _fnLengthChange(settings, $(this).val()); + _fnDraw(settings); + }); + + // Update node value whenever anything changes the table's length + $(settings.nTable).bind('length.dt.DT', function (e, s, len) { + if (settings === s) { + $('select', div).val(len); + } + }); + + return div[0]; + } + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Note that most of the paging logic is done in + * DataTable.ext.pager + */ + + /** + * Generate the node required for default pagination + * @param {object} oSettings dataTables settings object + * @returns {node} Pagination feature node + * @memberof DataTable#oApi + */ + function _fnFeatureHtmlPaginate(settings) { + var + type = settings.sPaginationType, + plugin = DataTable.ext.pager[type], + modern = typeof plugin === 'function', + redraw = function (settings) { + _fnDraw(settings); + }, + node = $('<div/>').addClass(settings.oClasses.sPaging + type)[0], + features = settings.aanFeatures; + + if (!modern) { + plugin.fnInit(settings, node, redraw); + } + + /* Add a draw callback for the pagination on first instance, to update the paging display */ + if (!features.p) { + node.id = settings.sTableId + '_paginate'; + + settings.aoDrawCallback.push({ + "fn": function (settings) { + if (modern) { + var + start = settings._iDisplayStart, + len = settings._iDisplayLength, + visRecords = settings.fnRecordsDisplay(), + all = len === -1, + page = all ? 0 : Math.ceil(start / len), + pages = all ? 1 : Math.ceil(visRecords / len), + buttons = plugin(page, pages), + i, ien; + + for (i = 0, ien = features.p.length; i < ien; i++) { + _fnRenderer(settings, 'pageButton')( + settings, features.p[i], i, buttons, page, pages + ); + } + } else { + plugin.fnUpdate(settings, redraw); + } + }, + "sName": "pagination" + }); + } + + return node; + } + + + /** + * Alter the display settings to change the page + * @param {object} settings DataTables settings object + * @param {string|int} action Paging action to take: "first", "previous", + * "next" or "last" or page number to jump to (integer) + * @param [bool] redraw Automatically draw the update or not + * @returns {bool} true page has changed, false - no change + * @memberof DataTable#oApi + */ + function _fnPageChange(settings, action, redraw) { + var + start = settings._iDisplayStart, + len = settings._iDisplayLength, + records = settings.fnRecordsDisplay(); + + if (records === 0 || len === -1) { + start = 0; + } else if (typeof action === "number") { + start = action * len; + + if (start > records) { + start = 0; + } + } else if (action == "first") { + start = 0; + } else if (action == "previous") { + start = len >= 0 ? + start - len : + 0; + + if (start < 0) { + start = 0; + } + } else if (action == "next") { + if (start + len < records) { + start += len; + } + } else if (action == "last") { + start = Math.floor((records - 1) / len) * len; + } else { + _fnLog(settings, 0, "Unknown paging action: " + action, 5); + } + + var changed = settings._iDisplayStart !== start; + settings._iDisplayStart = start; + + if (changed) { + _fnCallbackFire(settings, null, 'page', [settings]); + + if (redraw) { + _fnDraw(settings); + } + } + + return changed; + } + + + /** + * Generate the node required for the processing node + * @param {object} settings dataTables settings object + * @returns {node} Processing element + * @memberof DataTable#oApi + */ + function _fnFeatureHtmlProcessing(settings) { + return $('<div/>', { + 'id': !settings.aanFeatures.r ? settings.sTableId + '_processing' : null, + 'class': settings.oClasses.sProcessing + }) + .html(settings.oLanguage.sProcessing) + .insertBefore(settings.nTable)[0]; + } + + + /** + * Display or hide the processing indicator + * @param {object} settings dataTables settings object + * @param {bool} show Show the processing indicator (true) or not (false) + * @memberof DataTable#oApi + */ + function _fnProcessingDisplay(settings, show) { + if (settings.oFeatures.bProcessing) { + $(settings.aanFeatures.r).css('display', show ? 'block' : 'none'); + } + + _fnCallbackFire(settings, null, 'processing', [settings, show]); + } + + /** + * Add any control elements for the table - specifically scrolling + * @param {object} settings dataTables settings object + * @returns {node} Node to add to the DOM + * @memberof DataTable#oApi + */ + function _fnFeatureHtmlTable(settings) { + var table = $(settings.nTable); + + // Add the ARIA grid role to the table + table.attr('role', 'grid'); + + // Scrolling from here on in + var scroll = settings.oScroll; + + if (scroll.sX === '' && scroll.sY === '') { + return settings.nTable; + } + + var scrollX = scroll.sX; + var scrollY = scroll.sY; + var classes = settings.oClasses; + var caption = table.children('caption'); + var captionSide = caption.length ? caption[0]._captionSide : null; + var headerClone = $(table[0].cloneNode(false)); + var footerClone = $(table[0].cloneNode(false)); + var footer = table.children('tfoot'); + var _div = '<div/>'; + var size = function (s) { + return !s ? null : _fnStringToCss(s); + }; + + if (!footer.length) { + footer = null; + } + + /* + * The HTML structure that we want to generate in this function is: + * div - scroller + * div - scroll head + * div - scroll head inner + * table - scroll head table + * thead - thead + * div - scroll body + * table - table (master table) + * thead - thead clone for sizing + * tbody - tbody + * div - scroll foot + * div - scroll foot inner + * table - scroll foot table + * tfoot - tfoot + */ + var scroller = $(_div, { + 'class': classes.sScrollWrapper + }) + .append( + $(_div, { + 'class': classes.sScrollHead + }) + .css({ + overflow: 'hidden', + position: 'relative', + border: 0, + width: scrollX ? size(scrollX) : '100%' + }) + .append( + $(_div, { + 'class': classes.sScrollHeadInner + }) + .css({ + 'box-sizing': 'content-box', + width: scroll.sXInner || '100%' + }) + .append( + headerClone + .removeAttr('id') + .css('margin-left', 0) + .append(captionSide === 'top' ? caption : null) + .append( + table.children('thead') + ) + ) + ) + ) + .append( + $(_div, { + 'class': classes.sScrollBody + }) + .css({ + position: 'relative', + overflow: 'auto', + width: size(scrollX) + }) + .append(table) + ); + + if (footer) { + scroller.append( + $(_div, { + 'class': classes.sScrollFoot + }) + .css({ + overflow: 'hidden', + border: 0, + width: scrollX ? size(scrollX) : '100%' + }) + .append( + $(_div, { + 'class': classes.sScrollFootInner + }) + .append( + footerClone + .removeAttr('id') + .css('margin-left', 0) + .append(captionSide === 'bottom' ? caption : null) + .append( + table.children('tfoot') + ) + ) + ) + ); + } + + var children = scroller.children(); + var scrollHead = children[0]; + var scrollBody = children[1]; + var scrollFoot = footer ? children[2] : null; + + // When the body is scrolled, then we also want to scroll the headers + if (scrollX) { + $(scrollBody).on('scroll.DT', function (e) { + var scrollLeft = this.scrollLeft; + + scrollHead.scrollLeft = scrollLeft; + + if (footer) { + scrollFoot.scrollLeft = scrollLeft; + } + }); + } + + $(scrollBody).css( + scrollY && scroll.bCollapse ? 'max-height' : 'height', + scrollY + ); + + settings.nScrollHead = scrollHead; + settings.nScrollBody = scrollBody; + settings.nScrollFoot = scrollFoot; + + // On redraw - align columns + settings.aoDrawCallback.push({ + "fn": _fnScrollDraw, + "sName": "scrolling" + }); + + return scroller[0]; + } + + + /** + * Update the header, footer and body tables for resizing - i.e. column + * alignment. + * + * Welcome to the most horrible function DataTables. The process that this + * function follows is basically: + * 1. Re-create the table inside the scrolling div + * 2. Take live measurements from the DOM + * 3. Apply the measurements to align the columns + * 4. Clean up + * + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnScrollDraw(settings) { + // Given that this is such a monster function, a lot of variables are use + // to try and keep the minimised size as small as possible + var + scroll = settings.oScroll, + scrollX = scroll.sX, + scrollXInner = scroll.sXInner, + scrollY = scroll.sY, + barWidth = scroll.iBarWidth, + divHeader = $(settings.nScrollHead), + divHeaderStyle = divHeader[0].style, + divHeaderInner = divHeader.children('div'), + divHeaderInnerStyle = divHeaderInner[0].style, + divHeaderTable = divHeaderInner.children('table'), + divBodyEl = settings.nScrollBody, + divBody = $(divBodyEl), + divBodyStyle = divBodyEl.style, + divFooter = $(settings.nScrollFoot), + divFooterInner = divFooter.children('div'), + divFooterTable = divFooterInner.children('table'), + header = $(settings.nTHead), + table = $(settings.nTable), + tableEl = table[0], + tableStyle = tableEl.style, + footer = settings.nTFoot ? $(settings.nTFoot) : null, + browser = settings.oBrowser, + ie67 = browser.bScrollOversize, + dtHeaderCells = _pluck(settings.aoColumns, 'nTh'), + headerTrgEls, footerTrgEls, + headerSrcEls, footerSrcEls, + headerCopy, footerCopy, + headerWidths = [], + footerWidths = [], + headerContent = [], + footerContent = [], + idx, correction, sanityWidth, + zeroOut = function (nSizer) { + var style = nSizer.style; + style.paddingTop = "0"; + style.paddingBottom = "0"; + style.borderTopWidth = "0"; + style.borderBottomWidth = "0"; + style.height = 0; + }; + + // If the scrollbar visibility has changed from the last draw, we need to + // adjust the column sizes as the table width will have changed to account + // for the scrollbar + var scrollBarVis = divBodyEl.scrollHeight > divBodyEl.clientHeight; + + if (settings.scrollBarVis !== scrollBarVis && settings.scrollBarVis !== undefined) { + settings.scrollBarVis = scrollBarVis; + _fnAdjustColumnSizing(settings); + return; // adjust column sizing will call this function again + } else { + settings.scrollBarVis = scrollBarVis; + } + + /* + * 1. Re-create the table inside the scrolling div + */ + + // Remove the old minimised thead and tfoot elements in the inner table + table.children('thead, tfoot').remove(); + + if (footer) { + footerCopy = footer.clone().prependTo(table); + footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized + footerSrcEls = footerCopy.find('tr'); + } + + // Clone the current header and footer elements and then place it into the inner table + headerCopy = header.clone().prependTo(table); + headerTrgEls = header.find('tr'); // original header is in its own table + headerSrcEls = headerCopy.find('tr'); + headerCopy.find('th, td').removeAttr('tabindex'); + + + /* + * 2. Take live measurements from the DOM - do not alter the DOM itself! + */ + + // Remove old sizing and apply the calculated column widths + // Get the unique column headers in the newly created (cloned) header. We want to apply the + // calculated sizes to this header + if (!scrollX) { + divBodyStyle.width = '100%'; + divHeader[0].style.width = '100%'; + } + + $.each(_fnGetUniqueThs(settings, headerCopy), function (i, el) { + idx = _fnVisibleToColumnIndex(settings, i); + el.style.width = settings.aoColumns[idx].sWidth; + }); + + if (footer) { + _fnApplyToChildren(function (n) { + n.style.width = ""; + }, footerSrcEls); + } + + // Size the table as a whole + sanityWidth = table.outerWidth(); + if (scrollX === "") { + // No x scrolling + tableStyle.width = "100%"; + + // IE7 will make the width of the table when 100% include the scrollbar + // - which is shouldn't. When there is a scrollbar we need to take this + // into account. + if (ie67 && (table.find('tbody').height() > divBodyEl.offsetHeight || + divBody.css('overflow-y') == "scroll")) { + tableStyle.width = _fnStringToCss(table.outerWidth() - barWidth); + } + + // Recalculate the sanity width + sanityWidth = table.outerWidth(); + } else if (scrollXInner !== "") { + // legacy x scroll inner has been given - use it + tableStyle.width = _fnStringToCss(scrollXInner); + + // Recalculate the sanity width + sanityWidth = table.outerWidth(); + } + + // Hidden header should have zero height, so remove padding and borders. Then + // set the width based on the real headers + + // Apply all styles in one pass + _fnApplyToChildren(zeroOut, headerSrcEls); + + // Read all widths in next pass + _fnApplyToChildren(function (nSizer) { + headerContent.push(nSizer.innerHTML); + headerWidths.push(_fnStringToCss($(nSizer).css('width'))); + }, headerSrcEls); + + // Apply all widths in final pass + _fnApplyToChildren(function (nToSize, i) { + // Only apply widths to the DataTables detected header cells - this + // prevents complex headers from having contradictory sizes applied + if ($.inArray(nToSize, dtHeaderCells) !== -1) { + nToSize.style.width = headerWidths[i]; + } + }, headerTrgEls); + + $(headerSrcEls).height(0); + + /* Same again with the footer if we have one */ + if (footer) { + _fnApplyToChildren(zeroOut, footerSrcEls); + + _fnApplyToChildren(function (nSizer) { + footerContent.push(nSizer.innerHTML); + footerWidths.push(_fnStringToCss($(nSizer).css('width'))); + }, footerSrcEls); + + _fnApplyToChildren(function (nToSize, i) { + nToSize.style.width = footerWidths[i]; + }, footerTrgEls); + + $(footerSrcEls).height(0); + } + + + /* + * 3. Apply the measurements + */ + + // "Hide" the header and footer that we used for the sizing. We need to keep + // the content of the cell so that the width applied to the header and body + // both match, but we want to hide it completely. We want to also fix their + // width to what they currently are + _fnApplyToChildren(function (nSizer, i) { + nSizer.innerHTML = '<div class="dataTables_sizing" style="height:0;overflow:hidden;">' + headerContent[i] + '</div>'; + nSizer.style.width = headerWidths[i]; + }, headerSrcEls); + + if (footer) { + _fnApplyToChildren(function (nSizer, i) { + nSizer.innerHTML = '<div class="dataTables_sizing" style="height:0;overflow:hidden;">' + footerContent[i] + '</div>'; + nSizer.style.width = footerWidths[i]; + }, footerSrcEls); + } + + // Sanity check that the table is of a sensible width. If not then we are going to get + // misalignment - try to prevent this by not allowing the table to shrink below its min width + if (table.outerWidth() < sanityWidth) { + // The min width depends upon if we have a vertical scrollbar visible or not */ + correction = ((divBodyEl.scrollHeight > divBodyEl.offsetHeight || + divBody.css('overflow-y') == "scroll")) ? + sanityWidth + barWidth : + sanityWidth; + + // IE6/7 are a law unto themselves... + if (ie67 && (divBodyEl.scrollHeight > + divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll")) { + tableStyle.width = _fnStringToCss(correction - barWidth); + } + + // And give the user a warning that we've stopped the table getting too small + if (scrollX === "" || scrollXInner !== "") { + _fnLog(settings, 1, 'Possible column misalignment', 6); + } + } else { + correction = '100%'; + } + + // Apply to the container elements + divBodyStyle.width = _fnStringToCss(correction); + divHeaderStyle.width = _fnStringToCss(correction); + + if (footer) { + settings.nScrollFoot.style.width = _fnStringToCss(correction); + } + + + /* + * 4. Clean up + */ + if (!scrollY) { + /* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting + * the scrollbar height from the visible display, rather than adding it on. We need to + * set the height in order to sort this. Don't want to do it in any other browsers. + */ + if (ie67) { + divBodyStyle.height = _fnStringToCss(tableEl.offsetHeight + barWidth); + } + } + + /* Finally set the width's of the header and footer tables */ + var iOuterWidth = table.outerWidth(); + divHeaderTable[0].style.width = _fnStringToCss(iOuterWidth); + divHeaderInnerStyle.width = _fnStringToCss(iOuterWidth); + + // Figure out if there are scrollbar present - if so then we need a the header and footer to + // provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar) + var bScrolling = table.height() > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll"; + var padding = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right'); + divHeaderInnerStyle[padding] = bScrolling ? barWidth + "px" : "0px"; + + if (footer) { + divFooterTable[0].style.width = _fnStringToCss(iOuterWidth); + divFooterInner[0].style.width = _fnStringToCss(iOuterWidth); + divFooterInner[0].style[padding] = bScrolling ? barWidth + "px" : "0px"; + } + + // Correct DOM ordering for colgroup - comes before the thead + table.children('colgroup').insertBefore(table.children('thead')); + + /* Adjust the position of the header in case we loose the y-scrollbar */ + divBody.scroll(); + + // If sorting or filtering has occurred, jump the scrolling back to the top + // only if we aren't holding the position + if ((settings.bSorted || settings.bFiltered) && !settings._drawHold) { + divBodyEl.scrollTop = 0; + } + } + + + /** + * Apply a given function to the display child nodes of an element array (typically + * TD children of TR rows + * @param {function} fn Method to apply to the objects + * @param array {nodes} an1 List of elements to look through for display children + * @param array {nodes} an2 Another list (identical structure to the first) - optional + * @memberof DataTable#oApi + */ + function _fnApplyToChildren(fn, an1, an2) { + var index = 0, + i = 0, + iLen = an1.length; + var nNode1, nNode2; + + while (i < iLen) { + nNode1 = an1[i].firstChild; + nNode2 = an2 ? an2[i].firstChild : null; + + while (nNode1) { + if (nNode1.nodeType === 1) { + if (an2) { + fn(nNode1, nNode2, index); + } else { + fn(nNode1, index); + } + + index++; + } + + nNode1 = nNode1.nextSibling; + nNode2 = an2 ? nNode2.nextSibling : null; + } + + i++; + } + } + + + var __re_html_remove = /<.*?>/g; + + + /** + * Calculate the width of columns for the table + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnCalculateColumnWidths(oSettings) { + var + table = oSettings.nTable, + columns = oSettings.aoColumns, + scroll = oSettings.oScroll, + scrollY = scroll.sY, + scrollX = scroll.sX, + scrollXInner = scroll.sXInner, + columnCount = columns.length, + visibleColumns = _fnGetColumns(oSettings, 'bVisible'), + headerCells = $('th', oSettings.nTHead), + tableWidthAttr = table.getAttribute('width'), // from DOM element + tableContainer = table.parentNode, + userInputs = false, + i, column, columnIdx, width, outerWidth, + browser = oSettings.oBrowser, + ie67 = browser.bScrollOversize; + + + var styleWidth = table.style.width; + if (styleWidth && styleWidth.indexOf('%') !== -1) { + tableWidthAttr = styleWidth; + } + + /* Convert any user input sizes into pixel sizes */ + for (i = 0; i < visibleColumns.length; i++) { + column = columns[visibleColumns[i]]; + + if (column.sWidth !== null) { + column.sWidth = _fnConvertToWidth(column.sWidthOrig, tableContainer); + + userInputs = true; + } + } + + /* If the number of columns in the DOM equals the number that we have to + * process in DataTables, then we can use the offsets that are created by + * the web- browser. No custom sizes can be set in order for this to happen, + * nor scrolling used + */ + if (ie67 || !userInputs && !scrollX && !scrollY && + columnCount == _fnVisbleColumns(oSettings) && + columnCount == headerCells.length + ) { + for (i = 0; i < columnCount; i++) { + var colIdx = _fnVisibleToColumnIndex(oSettings, i); + + if (colIdx !== null) { + columns[colIdx].sWidth = _fnStringToCss(headerCells.eq(i).width()); + } + } + } else { + // Otherwise construct a single row, worst case, table with the widest + // node in the data, assign any user defined widths, then insert it into + // the DOM and allow the browser to do all the hard work of calculating + // table widths + var tmpTable = $(table).clone() // don't use cloneNode - IE8 will remove events on the main table + .css('visibility', 'hidden') + .removeAttr('id'); + + // Clean up the table body + tmpTable.find('tbody tr').remove(); + var tr = $('<tr/>').appendTo(tmpTable.find('tbody')); + + // Clone the table header and footer - we can't use the header / footer + // from the cloned table, since if scrolling is active, the table's + // real header and footer are contained in different table tags + tmpTable.find('thead, tfoot').remove(); + tmpTable + .append($(oSettings.nTHead).clone()) + .append($(oSettings.nTFoot).clone()); + + // Remove any assigned widths from the footer (from scrolling) + tmpTable.find('tfoot th, tfoot td').css('width', ''); + + // Apply custom sizing to the cloned header + headerCells = _fnGetUniqueThs(oSettings, tmpTable.find('thead')[0]); + + for (i = 0; i < visibleColumns.length; i++) { + column = columns[visibleColumns[i]]; + if (!headerCells[i]) { + break; + } + headerCells[i].style.width = column.sWidthOrig !== null && column.sWidthOrig !== '' ? + _fnStringToCss(column.sWidthOrig) : + ''; + + // For scrollX we need to force the column width otherwise the + // browser will collapse it. If this width is smaller than the + // width the column requires, then it will have no effect + if (column.sWidthOrig && scrollX) { + $(headerCells[i]).append($('<div/>').css({ + width: column.sWidthOrig, + margin: 0, + padding: 0, + border: 0, + height: 1 + })); + } + } + + // Find the widest cell for each column and put it into the table + if (oSettings.aoData.length) { + for (i = 0; i < visibleColumns.length; i++) { + columnIdx = visibleColumns[i]; + column = columns[columnIdx]; + + $(_fnGetWidestNode(oSettings, columnIdx)) + .clone(false) + .append(column.sContentPadding) + .appendTo(tr); + } + } + + // Tidy the temporary table - remove name attributes so there aren't + // duplicated in the dom (radio elements for example) + $('[name]', tmpTable).removeAttr('name'); + + // Table has been built, attach to the document so we can work with it. + // A holding element is used, positioned at the top of the container + // with minimal height, so it has no effect on if the container scrolls + // or not. Otherwise it might trigger scrolling when it actually isn't + // needed + var holder = $('<div/>').css(scrollX || scrollY ? { + position: 'absolute', + top: 0, + left: 0, + height: 1, + right: 0, + overflow: 'hidden' + } : {}) + .append(tmpTable) + .appendTo(tableContainer); + + // When scrolling (X or Y) we want to set the width of the table as + // appropriate. However, when not scrolling leave the table width as it + // is. This results in slightly different, but I think correct behaviour + if (scrollX && scrollXInner) { + tmpTable.width(scrollXInner); + } else if (scrollX) { + tmpTable.css('width', 'auto'); + tmpTable.removeAttr('width'); + + // If there is no width attribute or style, then allow the table to + // collapse + if (tmpTable.width() < tableContainer.clientWidth && tableWidthAttr) { + tmpTable.width(tableContainer.clientWidth); + } + } else if (scrollY) { + tmpTable.width(tableContainer.clientWidth); + } else if (tableWidthAttr) { + tmpTable.width(tableWidthAttr); + } + + // Get the width of each column in the constructed table - we need to + // know the inner width (so it can be assigned to the other table's + // cells) and the outer width so we can calculate the full width of the + // table. This is safe since DataTables requires a unique cell for each + // column, but if ever a header can span multiple columns, this will + // need to be modified. + var total = 0; + for (i = 0; i < visibleColumns.length; i++) { + if (!headerCells[i]) { + break; + } + var cell = $(headerCells[i]); + var border = cell.outerWidth() - cell.width(); + + // Use getBounding... where possible (not IE8-) because it can give + // sub-pixel accuracy, which we then want to round up! + var bounding = browser.bBounding ? + Math.ceil(headerCells[i].getBoundingClientRect().width) : + cell.outerWidth(); + + // Total is tracked to remove any sub-pixel errors as the outerWidth + // of the table might not equal the total given here (IE!). + total += bounding; + + // Width for each column to use + columns[visibleColumns[i]].sWidth = _fnStringToCss(bounding - border); + } + + table.style.width = _fnStringToCss(total); + + // Finished with the table - ditch it + holder.remove(); + } + + // If there is a width attr, we want to attach an event listener which + // allows the table sizing to automatically adjust when the window is + // resized. Use the width attr rather than CSS, since we can't know if the + // CSS is a relative value or absolute - DOM read is always px. + if (tableWidthAttr) { + table.style.width = _fnStringToCss(tableWidthAttr); + } + + if ((tableWidthAttr || scrollX) && !oSettings._reszEvt) { + var bindResize = function () { + $(window).bind('resize.DT-' + oSettings.sInstance, _fnThrottle(function () { + _fnAdjustColumnSizing(oSettings); + })); + }; + + // IE6/7 will crash if we bind a resize event handler on page load. + // To be removed in 1.11 which drops IE6/7 support + if (ie67) { + setTimeout(bindResize, 1000); + } else { + bindResize(); + } + + oSettings._reszEvt = true; + } + } + + + /** + * Throttle the calls to a function. Arguments and context are maintained for + * the throttled function + * @param {function} fn Function to be called + * @param {int} [freq=200] call frequency in mS + * @returns {function} wrapped function + * @memberof DataTable#oApi + */ + var _fnThrottle = DataTable.util.throttle; + + + /** + * Convert a CSS unit width to pixels (e.g. 2em) + * @param {string} width width to be converted + * @param {node} parent parent to get the with for (required for relative widths) - optional + * @returns {int} width in pixels + * @memberof DataTable#oApi + */ + function _fnConvertToWidth(width, parent) { + if (!width) { + return 0; + } + + var n = $('<div/>') + .css('width', _fnStringToCss(width)) + .appendTo(parent || document.body); + + var val = n[0].offsetWidth; + n.remove(); + + return val; + } + + + /** + * Get the widest node + * @param {object} settings dataTables settings object + * @param {int} colIdx column of interest + * @returns {node} widest table node + * @memberof DataTable#oApi + */ + function _fnGetWidestNode(settings, colIdx) { + var idx = _fnGetMaxLenString(settings, colIdx); + if (idx < 0) { + return null; + } + + var data = settings.aoData[idx]; + return !data.nTr ? // Might not have been created when deferred rendering + $('<td/>').html(_fnGetCellData(settings, idx, colIdx, 'display'))[0] : + data.anCells[colIdx]; + } + + + /** + * Get the maximum strlen for each data column + * @param {object} settings dataTables settings object + * @param {int} colIdx column of interest + * @returns {string} max string length for each column + * @memberof DataTable#oApi + */ + function _fnGetMaxLenString(settings, colIdx) { + var s, max = -1, + maxIdx = -1; + + for (var i = 0, ien = settings.aoData.length; i < ien; i++) { + s = _fnGetCellData(settings, i, colIdx, 'display') + ''; + s = s.replace(__re_html_remove, ''); + s = s.replace(/ /g, ' '); + + if (s.length > max) { + max = s.length; + maxIdx = i; + } + } + + return maxIdx; + } + + + /** + * Append a CSS unit (only if required) to a string + * @param {string} value to css-ify + * @returns {string} value with css unit + * @memberof DataTable#oApi + */ + function _fnStringToCss(s) { + if (s === null) { + return '0px'; + } + + if (typeof s == 'number') { + return s < 0 ? + '0px' : + s + 'px'; + } + + // Check it has a unit character already + return s.match(/\d$/) ? + s + 'px' : + s; + } + + + function _fnSortFlatten(settings) { + var + i, iLen, k, kLen, + aSort = [], + aiOrig = [], + aoColumns = settings.aoColumns, + aDataSort, iCol, sType, srcCol, + fixed = settings.aaSortingFixed, + fixedObj = $.isPlainObject(fixed), + nestedSort = [], + add = function (a) { + if (a.length && !$.isArray(a[0])) { + // 1D array + nestedSort.push(a); + } else { + // 2D array + $.merge(nestedSort, a); + } + }; + + // Build the sort array, with pre-fix and post-fix options if they have been + // specified + if ($.isArray(fixed)) { + add(fixed); + } + + if (fixedObj && fixed.pre) { + add(fixed.pre); + } + + add(settings.aaSorting); + + if (fixedObj && fixed.post) { + add(fixed.post); + } + + for (i = 0; i < nestedSort.length; i++) { + srcCol = nestedSort[i][0]; + aDataSort = aoColumns[srcCol].aDataSort; + + for (k = 0, kLen = aDataSort.length; k < kLen; k++) { + iCol = aDataSort[k]; + sType = aoColumns[iCol].sType || 'string'; + + if (nestedSort[i]._idx === undefined) { + nestedSort[i]._idx = $.inArray(nestedSort[i][1], aoColumns[iCol].asSorting); + } + + aSort.push({ + src: srcCol, + col: iCol, + dir: nestedSort[i][1], + index: nestedSort[i]._idx, + type: sType, + formatter: DataTable.ext.type.order[sType + "-pre"] + }); + } + } + + return aSort; + } + + /** + * Change the order of the table + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + * @todo This really needs split up! + */ + function _fnSort(oSettings) { + var + i, ien, iLen, j, jLen, k, kLen, + sDataType, nTh, + aiOrig = [], + oExtSort = DataTable.ext.type.order, + aoData = oSettings.aoData, + aoColumns = oSettings.aoColumns, + aDataSort, data, iCol, sType, oSort, + formatters = 0, + sortCol, + displayMaster = oSettings.aiDisplayMaster, + aSort; + + // Resolve any column types that are unknown due to addition or invalidation + // @todo Can this be moved into a 'data-ready' handler which is called when + // data is going to be used in the table? + _fnColumnTypes(oSettings); + + aSort = _fnSortFlatten(oSettings); + + for (i = 0, ien = aSort.length; i < ien; i++) { + sortCol = aSort[i]; + + // Track if we can use the fast sort algorithm + if (sortCol.formatter) { + formatters++; + } + + // Load the data needed for the sort, for each cell + _fnSortData(oSettings, sortCol.col); + } + + /* No sorting required if server-side or no sorting array */ + if (_fnDataSource(oSettings) != 'ssp' && aSort.length !== 0) { + // Create a value - key array of the current row positions such that we can use their + // current position during the sort, if values match, in order to perform stable sorting + for (i = 0, iLen = displayMaster.length; i < iLen; i++) { + aiOrig[displayMaster[i]] = i; + } + + /* Do the sort - here we want multi-column sorting based on a given data source (column) + * and sorting function (from oSort) in a certain direction. It's reasonably complex to + * follow on it's own, but this is what we want (example two column sorting): + * fnLocalSorting = function(a,b){ + * var iTest; + * iTest = oSort['string-asc']('data11', 'data12'); + * if (iTest !== 0) + * return iTest; + * iTest = oSort['numeric-desc']('data21', 'data22'); + * if (iTest !== 0) + * return iTest; + * return oSort['numeric-asc']( aiOrig[a], aiOrig[b] ); + * } + * Basically we have a test for each sorting column, if the data in that column is equal, + * test the next column. If all columns match, then we use a numeric sort on the row + * positions in the original data array to provide a stable sort. + * + * Note - I know it seems excessive to have two sorting methods, but the first is around + * 15% faster, so the second is only maintained for backwards compatibility with sorting + * methods which do not have a pre-sort formatting function. + */ + if (formatters === aSort.length) { + // All sort types have formatting functions + displayMaster.sort(function (a, b) { + var + x, y, k, test, sort, + len = aSort.length, + dataA = aoData[a]._aSortData, + dataB = aoData[b]._aSortData; + + for (k = 0; k < len; k++) { + sort = aSort[k]; + + x = dataA[sort.col]; + y = dataB[sort.col]; + + test = x < y ? -1 : x > y ? 1 : 0; + if (test !== 0) { + return sort.dir === 'asc' ? test : -test; + } + } + + x = aiOrig[a]; + y = aiOrig[b]; + return x < y ? -1 : x > y ? 1 : 0; + }); + } else { + // Depreciated - remove in 1.11 (providing a plug-in option) + // Not all sort types have formatting methods, so we have to call their sorting + // methods. + displayMaster.sort(function (a, b) { + var + x, y, k, l, test, sort, fn, + len = aSort.length, + dataA = aoData[a]._aSortData, + dataB = aoData[b]._aSortData; + + for (k = 0; k < len; k++) { + sort = aSort[k]; + + x = dataA[sort.col]; + y = dataB[sort.col]; + + fn = oExtSort[sort.type + "-" + sort.dir] || oExtSort["string-" + sort.dir]; + test = fn(x, y); + if (test !== 0) { + return test; + } + } + + x = aiOrig[a]; + y = aiOrig[b]; + return x < y ? -1 : x > y ? 1 : 0; + }); + } + } + + /* Tell the draw function that we have sorted the data */ + oSettings.bSorted = true; + } + + + function _fnSortAria(settings) { + var label; + var nextSort; + var columns = settings.aoColumns; + var aSort = _fnSortFlatten(settings); + var oAria = settings.oLanguage.oAria; + + // ARIA attributes - need to loop all columns, to update all (removing old + // attributes as needed) + for (var i = 0, iLen = columns.length; i < iLen; i++) { + var col = columns[i]; + var asSorting = col.asSorting; + var sTitle = col.sTitle.replace(/<.*?>/g, ""); + var th = col.nTh; + + // IE7 is throwing an error when setting these properties with jQuery's + // attr() and removeAttr() methods... + th.removeAttribute('aria-sort'); + + /* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */ + if (col.bSortable) { + if (aSort.length > 0 && aSort[0].col == i) { + th.setAttribute('aria-sort', aSort[0].dir == "asc" ? "ascending" : "descending"); + nextSort = asSorting[aSort[0].index + 1] || asSorting[0]; + } else { + nextSort = asSorting[0]; + } + + label = sTitle + (nextSort === "asc" ? + oAria.sSortAscending : + oAria.sSortDescending + ); + } else { + label = sTitle; + } + + th.setAttribute('aria-label', label); + } + } + + + /** + * Function to run on user sort request + * @param {object} settings dataTables settings object + * @param {node} attachTo node to attach the handler to + * @param {int} colIdx column sorting index + * @param {boolean} [append=false] Append the requested sort to the existing + * sort if true (i.e. multi-column sort) + * @param {function} [callback] callback function + * @memberof DataTable#oApi + */ + function _fnSortListener(settings, colIdx, append, callback) { + var col = settings.aoColumns[colIdx]; + var sorting = settings.aaSorting; + var asSorting = col.asSorting; + var nextSortIdx; + var next = function (a, overflow) { + var idx = a._idx; + if (idx === undefined) { + idx = $.inArray(a[1], asSorting); + } + + return idx + 1 < asSorting.length ? + idx + 1 : + overflow ? + null : + 0; + }; + + // Convert to 2D array if needed + if (typeof sorting[0] === 'number') { + sorting = settings.aaSorting = [sorting]; + } + + // If appending the sort then we are multi-column sorting + if (append && settings.oFeatures.bSortMulti) { + // Are we already doing some kind of sort on this column? + var sortIdx = $.inArray(colIdx, _pluck(sorting, '0')); + + if (sortIdx !== -1) { + // Yes, modify the sort + nextSortIdx = next(sorting[sortIdx], true); + + if (nextSortIdx === null && sorting.length === 1) { + nextSortIdx = 0; // can't remove sorting completely + } + + if (nextSortIdx === null) { + sorting.splice(sortIdx, 1); + } else { + sorting[sortIdx][1] = asSorting[nextSortIdx]; + sorting[sortIdx]._idx = nextSortIdx; + } + } else { + // No sort on this column yet + sorting.push([colIdx, asSorting[0], 0]); + sorting[sorting.length - 1]._idx = 0; + } + } else if (sorting.length && sorting[0][0] == colIdx) { + // Single column - already sorting on this column, modify the sort + nextSortIdx = next(sorting[0]); + + sorting.length = 1; + sorting[0][1] = asSorting[nextSortIdx]; + sorting[0]._idx = nextSortIdx; + } else { + // Single column - sort only on this column + sorting.length = 0; + sorting.push([colIdx, asSorting[0]]); + sorting[0]._idx = 0; + } + + // Run the sort by calling a full redraw + _fnReDraw(settings); + + // callback used for async user interaction + if (typeof callback == 'function') { + callback(settings); + } + } + + + /** + * Attach a sort handler (click) to a node + * @param {object} settings dataTables settings object + * @param {node} attachTo node to attach the handler to + * @param {int} colIdx column sorting index + * @param {function} [callback] callback function + * @memberof DataTable#oApi + */ + function _fnSortAttachListener(settings, attachTo, colIdx, callback) { + var col = settings.aoColumns[colIdx]; + + _fnBindAction(attachTo, {}, function (e) { + /* If the column is not sortable - don't to anything */ + if (col.bSortable === false) { + return; + } + + // If processing is enabled use a timeout to allow the processing + // display to be shown - otherwise to it synchronously + if (settings.oFeatures.bProcessing) { + _fnProcessingDisplay(settings, true); + + setTimeout(function () { + _fnSortListener(settings, colIdx, e.shiftKey, callback); + + // In server-side processing, the draw callback will remove the + // processing display + if (_fnDataSource(settings) !== 'ssp') { + _fnProcessingDisplay(settings, false); + } + }, 0); + } else { + _fnSortListener(settings, colIdx, e.shiftKey, callback); + } + }); + } + + + /** + * Set the sorting classes on table's body, Note: it is safe to call this function + * when bSort and bSortClasses are false + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnSortingClasses(settings) { + var oldSort = settings.aLastSort; + var sortClass = settings.oClasses.sSortColumn; + var sort = _fnSortFlatten(settings); + var features = settings.oFeatures; + var i, ien, colIdx; + + if (features.bSort && features.bSortClasses) { + // Remove old sorting classes + for (i = 0, ien = oldSort.length; i < ien; i++) { + colIdx = oldSort[i].src; + + // Remove column sorting + $(_pluck(settings.aoData, 'anCells', colIdx)) + .removeClass(sortClass + (i < 2 ? i + 1 : 3)); + } + + // Add new column sorting + for (i = 0, ien = sort.length; i < ien; i++) { + colIdx = sort[i].src; + + $(_pluck(settings.aoData, 'anCells', colIdx)) + .addClass(sortClass + (i < 2 ? i + 1 : 3)); + } + } + + settings.aLastSort = sort; + } + + + // Get the data to sort a column, be it from cache, fresh (populating the + // cache), or from a sort formatter + function _fnSortData(settings, idx) { + // Custom sorting function - provided by the sort data type + var column = settings.aoColumns[idx]; + var customSort = DataTable.ext.order[column.sSortDataType]; + var customData; + + if (customSort) { + customData = customSort.call(settings.oInstance, settings, idx, + _fnColumnIndexToVisible(settings, idx) + ); + } + + // Use / populate cache + var row, cellData; + var formatter = DataTable.ext.type.order[column.sType + "-pre"]; + + for (var i = 0, ien = settings.aoData.length; i < ien; i++) { + row = settings.aoData[i]; + + if (!row._aSortData) { + row._aSortData = []; + } + + if (!row._aSortData[idx] || customSort) { + cellData = customSort ? + customData[i] : // If there was a custom sort function, use data from there + _fnGetCellData(settings, i, idx, 'sort'); + + row._aSortData[idx] = formatter ? + formatter(cellData) : + cellData; + } + } + } + + + /** + * Save the state of a table + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnSaveState(settings) { + if (!settings.oFeatures.bStateSave || settings.bDestroying) { + return; + } + + /* Store the interesting variables */ + var state = { + time: +new Date(), + start: settings._iDisplayStart, + length: settings._iDisplayLength, + order: $.extend(true, [], settings.aaSorting), + search: _fnSearchToCamel(settings.oPreviousSearch), + columns: $.map(settings.aoColumns, function (col, i) { + return { + visible: col.bVisible, + search: _fnSearchToCamel(settings.aoPreSearchCols[i]) + }; + }) + }; + + _fnCallbackFire(settings, "aoStateSaveParams", 'stateSaveParams', [settings, state]); + + settings.oSavedState = state; + settings.fnStateSaveCallback.call(settings.oInstance, settings, state); + } + + + /** + * Attempt to load a saved table state + * @param {object} oSettings dataTables settings object + * @param {object} oInit DataTables init object so we can override settings + * @memberof DataTable#oApi + */ + function _fnLoadState(settings, oInit) { + var i, ien; + var columns = settings.aoColumns; + + if (!settings.oFeatures.bStateSave) { + return; + } + + var state = settings.fnStateLoadCallback.call(settings.oInstance, settings); + if (!state || !state.time) { + return; + } + + /* Allow custom and plug-in manipulation functions to alter the saved data set and + * cancelling of loading by returning false + */ + var abStateLoad = _fnCallbackFire(settings, 'aoStateLoadParams', 'stateLoadParams', [settings, state]); + if ($.inArray(false, abStateLoad) !== -1) { + return; + } + + /* Reject old data */ + var duration = settings.iStateDuration; + if (duration > 0 && state.time < +new Date() - (duration * 1000)) { + return; + } + + // Number of columns have changed - all bets are off, no restore of settings + if (columns.length !== state.columns.length) { + return; + } + + // Store the saved state so it might be accessed at any time + settings.oLoadedState = $.extend(true, {}, state); + + // Restore key features - todo - for 1.11 this needs to be done by + // subscribed events + if (state.start !== undefined) { + settings._iDisplayStart = state.start; + settings.iInitDisplayStart = state.start; + } + if (state.length !== undefined) { + settings._iDisplayLength = state.length; + } + + // Order + if (state.order !== undefined) { + settings.aaSorting = []; + $.each(state.order, function (i, col) { + settings.aaSorting.push(col[0] >= columns.length ? [0, col[1]] : + col + ); + }); + } + + // Search + if (state.search !== undefined) { + $.extend(settings.oPreviousSearch, _fnSearchToHung(state.search)); + } + + // Columns + for (i = 0, ien = state.columns.length; i < ien; i++) { + var col = state.columns[i]; + + // Visibility + if (col.visible !== undefined) { + columns[i].bVisible = col.visible; + } + + // Search + if (col.search !== undefined) { + $.extend(settings.aoPreSearchCols[i], _fnSearchToHung(col.search)); + } + } + + _fnCallbackFire(settings, 'aoStateLoaded', 'stateLoaded', [settings, state]); + } + + + /** + * Return the settings object for a particular table + * @param {node} table table we are using as a dataTable + * @returns {object} Settings object - or null if not found + * @memberof DataTable#oApi + */ + function _fnSettingsFromNode(table) { + var settings = DataTable.settings; + var idx = $.inArray(table, _pluck(settings, 'nTable')); + + return idx !== -1 ? + settings[idx] : + null; + } + + + /** + * Log an error message + * @param {object} settings dataTables settings object + * @param {int} level log error messages, or display them to the user + * @param {string} msg error message + * @param {int} tn Technical note id to get more information about the error. + * @memberof DataTable#oApi + */ + function _fnLog(settings, level, msg, tn) { + msg = 'DataTables warning: ' + + (settings ? 'table id=' + settings.sTableId + ' - ' : '') + msg; + + if (tn) { + msg += '. For more information about this error, please see ' + + 'http://datatables.net/tn/' + tn; + } + + if (!level) { + // Backwards compatibility pre 1.10 + var ext = DataTable.ext; + var type = ext.sErrMode || ext.errMode; + + if (settings) { + _fnCallbackFire(settings, null, 'error', [settings, tn, msg]); + } + + if (type == 'alert') { + alert(msg); + } else if (type == 'throw') { + throw new Error(msg); + } else if (typeof type == 'function') { + type(settings, tn, msg); + } + } else if (window.console && console.log) { + console.log(msg); + } + } + + + /** + * See if a property is defined on one object, if so assign it to the other object + * @param {object} ret target object + * @param {object} src source object + * @param {string} name property + * @param {string} [mappedName] name to map too - optional, name used if not given + * @memberof DataTable#oApi + */ + function _fnMap(ret, src, name, mappedName) { + if ($.isArray(name)) { + $.each(name, function (i, val) { + if ($.isArray(val)) { + _fnMap(ret, src, val[0], val[1]); + } else { + _fnMap(ret, src, val); + } + }); + + return; + } + + if (mappedName === undefined) { + mappedName = name; + } + + if (src[name] !== undefined) { + ret[mappedName] = src[name]; + } + } + + + /** + * Extend objects - very similar to jQuery.extend, but deep copy objects, and + * shallow copy arrays. The reason we need to do this, is that we don't want to + * deep copy array init values (such as aaSorting) since the dev wouldn't be + * able to override them, but we do want to deep copy arrays. + * @param {object} out Object to extend + * @param {object} extender Object from which the properties will be applied to + * out + * @param {boolean} breakRefs If true, then arrays will be sliced to take an + * independent copy with the exception of the `data` or `aaData` parameters + * if they are present. This is so you can pass in a collection to + * DataTables and have that used as your data source without breaking the + * references + * @returns {object} out Reference, just for convenience - out === the return. + * @memberof DataTable#oApi + * @todo This doesn't take account of arrays inside the deep copied objects. + */ + function _fnExtend(out, extender, breakRefs) { + var val; + + for (var prop in extender) { + if (extender.hasOwnProperty(prop)) { + val = extender[prop]; + + if ($.isPlainObject(val)) { + if (!$.isPlainObject(out[prop])) { + out[prop] = {}; + } + $.extend(true, out[prop], val); + } else if (breakRefs && prop !== 'data' && prop !== 'aaData' && $.isArray(val)) { + out[prop] = val.slice(); + } else { + out[prop] = val; + } + } + } + + return out; + } + + + /** + * Bind an event handers to allow a click or return key to activate the callback. + * This is good for accessibility since a return on the keyboard will have the + * same effect as a click, if the element has focus. + * @param {element} n Element to bind the action to + * @param {object} oData Data object to pass to the triggered function + * @param {function} fn Callback function for when the event is triggered + * @memberof DataTable#oApi + */ + function _fnBindAction(n, oData, fn) { + $(n) + .bind('click.DT', oData, function (e) { + n.blur(); // Remove focus outline for mouse users + fn(e); + }) + .bind('keypress.DT', oData, function (e) { + if (e.which === 13) { + e.preventDefault(); + fn(e); + } + }) + .bind('selectstart.DT', function () { + /* Take the brutal approach to cancelling text selection */ + return false; + }); + } + + + /** + * Register a callback function. Easily allows a callback function to be added to + * an array store of callback functions that can then all be called together. + * @param {object} oSettings dataTables settings object + * @param {string} sStore Name of the array storage for the callbacks in oSettings + * @param {function} fn Function to be called back + * @param {string} sName Identifying name for the callback (i.e. a label) + * @memberof DataTable#oApi + */ + function _fnCallbackReg(oSettings, sStore, fn, sName) { + if (fn) { + oSettings[sStore].push({ + "fn": fn, + "sName": sName + }); + } + } + + + /** + * Fire callback functions and trigger events. Note that the loop over the + * callback array store is done backwards! Further note that you do not want to + * fire off triggers in time sensitive applications (for example cell creation) + * as its slow. + * @param {object} settings dataTables settings object + * @param {string} callbackArr Name of the array storage for the callbacks in + * oSettings + * @param {string} eventName Name of the jQuery custom event to trigger. If + * null no trigger is fired + * @param {array} args Array of arguments to pass to the callback function / + * trigger + * @memberof DataTable#oApi + */ + function _fnCallbackFire(settings, callbackArr, eventName, args) { + var ret = []; + + if (callbackArr) { + ret = $.map(settings[callbackArr].slice().reverse(), function (val, i) { + return val.fn.apply(settings.oInstance, args); + }); + } + + if (eventName !== null) { + var e = $.Event(eventName + '.dt'); + + $(settings.nTable).trigger(e, args); + + ret.push(e.result); + } + + return ret; + } + + + function _fnLengthOverflow(settings) { + var + start = settings._iDisplayStart, + end = settings.fnDisplayEnd(), + len = settings._iDisplayLength; + + /* If we have space to show extra rows (backing up from the end point - then do so */ + if (start >= end) { + start = end - len; + } + + // Keep the start record on the current page + start -= (start % len); + + if (len === -1 || start < 0) { + start = 0; + } + + settings._iDisplayStart = start; + } + + + function _fnRenderer(settings, type) { + var renderer = settings.renderer; + var host = DataTable.ext.renderer[type]; + + if ($.isPlainObject(renderer) && renderer[type]) { + // Specific renderer for this type. If available use it, otherwise use + // the default. + return host[renderer[type]] || host._; + } else if (typeof renderer === 'string') { + // Common renderer - if there is one available for this type use it, + // otherwise use the default + return host[renderer] || host._; + } + + // Use the default + return host._; + } + + + /** + * Detect the data source being used for the table. Used to simplify the code + * a little (ajax) and to make it compress a little smaller. + * + * @param {object} settings dataTables settings object + * @returns {string} Data source + * @memberof DataTable#oApi + */ + function _fnDataSource(settings) { + if (settings.oFeatures.bServerSide) { + return 'ssp'; + } else if (settings.ajax || settings.sAjaxSource) { + return 'ajax'; + } + return 'dom'; + } + + + /** + * Computed structure of the DataTables API, defined by the options passed to + * `DataTable.Api.register()` when building the API. + * + * The structure is built in order to speed creation and extension of the Api + * objects since the extensions are effectively pre-parsed. + * + * The array is an array of objects with the following structure, where this + * base array represents the Api prototype base: + * + * [ + * { + * name: 'data' -- string - Property name + * val: function () {}, -- function - Api method (or undefined if just an object + * methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result + * propExt: [ ... ] -- array - Array of Api object definitions to extend the property + * }, + * { + * name: 'row' + * val: {}, + * methodExt: [ ... ], + * propExt: [ + * { + * name: 'data' + * val: function () {}, + * methodExt: [ ... ], + * propExt: [ ... ] + * }, + * ... + * ] + * } + * ] + * + * @type {Array} + * @ignore + */ + var __apiStruct = []; + + + /** + * `Array.prototype` reference. + * + * @type object + * @ignore + */ + var __arrayProto = Array.prototype; + + + /** + * Abstraction for `context` parameter of the `Api` constructor to allow it to + * take several different forms for ease of use. + * + * Each of the input parameter types will be converted to a DataTables settings + * object where possible. + * + * @param {string|node|jQuery|object} mixed DataTable identifier. Can be one + * of: + * + * * `string` - jQuery selector. Any DataTables' matching the given selector + * with be found and used. + * * `node` - `TABLE` node which has already been formed into a DataTable. + * * `jQuery` - A jQuery object of `TABLE` nodes. + * * `object` - DataTables settings object + * * `DataTables.Api` - API instance + * @return {array|null} Matching DataTables settings objects. `null` or + * `undefined` is returned if no matching DataTable is found. + * @ignore + */ + var _toSettings = function (mixed) { + var idx, jq; + var settings = DataTable.settings; + var tables = $.map(settings, function (el, i) { + return el.nTable; + }); + + if (!mixed) { + return []; + } else if (mixed.nTable && mixed.oApi) { + // DataTables settings object + return [mixed]; + } else if (mixed.nodeName && mixed.nodeName.toLowerCase() === 'table') { + // Table node + idx = $.inArray(mixed, tables); + return idx !== -1 ? [settings[idx]] : null; + } else if (mixed && typeof mixed.settings === 'function') { + return mixed.settings().toArray(); + } else if (typeof mixed === 'string') { + // jQuery selector + jq = $(mixed); + } else if (mixed instanceof $) { + // jQuery object (also DataTables instance) + jq = mixed; + } + + if (jq) { + return jq.map(function (i) { + idx = $.inArray(this, tables); + return idx !== -1 ? settings[idx] : null; + }).toArray(); + } + }; + + + /** + * DataTables API class - used to control and interface with one or more + * DataTables enhanced tables. + * + * The API class is heavily based on jQuery, presenting a chainable interface + * that you can use to interact with tables. Each instance of the API class has + * a "context" - i.e. the tables that it will operate on. This could be a single + * table, all tables on a page or a sub-set thereof. + * + * Additionally the API is designed to allow you to easily work with the data in + * the tables, retrieving and manipulating it as required. This is done by + * presenting the API class as an array like interface. The contents of the + * array depend upon the actions requested by each method (for example + * `rows().nodes()` will return an array of nodes, while `rows().data()` will + * return an array of objects or arrays depending upon your table's + * configuration). The API object has a number of array like methods (`push`, + * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`, + * `unique` etc) to assist your working with the data held in a table. + * + * Most methods (those which return an Api instance) are chainable, which means + * the return from a method call also has all of the methods available that the + * top level object had. For example, these two calls are equivalent: + * + * // Not chained + * api.row.add( {...} ); + * api.draw(); + * + * // Chained + * api.row.add( {...} ).draw(); + * + * @class DataTable.Api + * @param {array|object|string|jQuery} context DataTable identifier. This is + * used to define which DataTables enhanced tables this API will operate on. + * Can be one of: + * + * * `string` - jQuery selector. Any DataTables' matching the given selector + * with be found and used. + * * `node` - `TABLE` node which has already been formed into a DataTable. + * * `jQuery` - A jQuery object of `TABLE` nodes. + * * `object` - DataTables settings object + * @param {array} [data] Data to initialise the Api instance with. + * + * @example + * // Direct initialisation during DataTables construction + * var api = $('#example').DataTable(); + * + * @example + * // Initialisation using a DataTables jQuery object + * var api = $('#example').dataTable().api(); + * + * @example + * // Initialisation as a constructor + * var api = new $.fn.DataTable.Api( 'table.dataTable' ); + */ + _Api = function (context, data) { + if (!(this instanceof _Api)) { + return new _Api(context, data); + } + + var settings = []; + var ctxSettings = function (o) { + var a = _toSettings(o); + if (a) { + settings = settings.concat(a); + } + }; + + if ($.isArray(context)) { + for (var i = 0, ien = context.length; i < ien; i++) { + ctxSettings(context[i]); + } + } else { + ctxSettings(context); + } + + // Remove duplicates + this.context = _unique(settings); + + // Initial data + if (data) { + $.merge(this, data); + } + + // selector + this.selector = { + rows: null, + cols: null, + opts: null + }; + + _Api.extend(this, this, __apiStruct); + }; + + DataTable.Api = _Api; + + // Don't destroy the existing prototype, just extend it. Required for jQuery 2's + // isPlainObject. + $.extend(_Api.prototype, { + any: function () { + return this.count() !== 0; + }, + + + concat: __arrayProto.concat, + + + context: [], // array of table settings objects + + + count: function () { + return this.flatten().length; + }, + + + each: function (fn) { + for (var i = 0, ien = this.length; i < ien; i++) { + fn.call(this, this[i], i, this); + } + + return this; + }, + + + eq: function (idx) { + var ctx = this.context; + + return ctx.length > idx ? + new _Api(ctx[idx], this[idx]) : + null; + }, + + + filter: function (fn) { + var a = []; + + if (__arrayProto.filter) { + a = __arrayProto.filter.call(this, fn, this); + } else { + // Compatibility for browsers without EMCA-252-5 (JS 1.6) + for (var i = 0, ien = this.length; i < ien; i++) { + if (fn.call(this, this[i], i, this)) { + a.push(this[i]); + } + } + } + + return new _Api(this.context, a); + }, + + + flatten: function () { + var a = []; + return new _Api(this.context, a.concat.apply(a, this.toArray())); + }, + + + join: __arrayProto.join, + + + indexOf: __arrayProto.indexOf || function (obj, start) { + for (var i = (start || 0), ien = this.length; i < ien; i++) { + if (this[i] === obj) { + return i; + } + } + return -1; + }, + + iterator: function (flatten, type, fn, alwaysNew) { + var + a = [], + ret, + i, ien, j, jen, + context = this.context, + rows, items, item, + selector = this.selector; + + // Argument shifting + if (typeof flatten === 'string') { + alwaysNew = fn; + fn = type; + type = flatten; + flatten = false; + } + + for (i = 0, ien = context.length; i < ien; i++) { + var apiInst = new _Api(context[i]); + + if (type === 'table') { + ret = fn.call(apiInst, context[i], i); + + if (ret !== undefined) { + a.push(ret); + } + } else if (type === 'columns' || type === 'rows') { + // this has same length as context - one entry for each table + ret = fn.call(apiInst, context[i], this[i], i); + + if (ret !== undefined) { + a.push(ret); + } + } else if (type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell') { + // columns and rows share the same structure. + // 'this' is an array of column indexes for each context + items = this[i]; + + if (type === 'column-rows') { + rows = _selector_row_indexes(context[i], selector.opts); + } + + for (j = 0, jen = items.length; j < jen; j++) { + item = items[j]; + + if (type === 'cell') { + ret = fn.call(apiInst, context[i], item.row, item.column, i, j); + } else { + ret = fn.call(apiInst, context[i], item, i, j, rows); + } + + if (ret !== undefined) { + a.push(ret); + } + } + } + } + + if (a.length || alwaysNew) { + var api = new _Api(context, flatten ? a.concat.apply([], a) : a); + var apiSelector = api.selector; + apiSelector.rows = selector.rows; + apiSelector.cols = selector.cols; + apiSelector.opts = selector.opts; + return api; + } + return this; + }, + + + lastIndexOf: __arrayProto.lastIndexOf || function (obj, start) { + // Bit cheeky... + return this.indexOf.apply(this.toArray.reverse(), arguments); + }, + + + length: 0, + + + map: function (fn) { + var a = []; + + if (__arrayProto.map) { + a = __arrayProto.map.call(this, fn, this); + } else { + // Compatibility for browsers without EMCA-252-5 (JS 1.6) + for (var i = 0, ien = this.length; i < ien; i++) { + a.push(fn.call(this, this[i], i)); + } + } + + return new _Api(this.context, a); + }, + + + pluck: function (prop) { + return this.map(function (el) { + return el[prop]; + }); + }, + + pop: __arrayProto.pop, + + + push: __arrayProto.push, + + + // Does not return an API instance + reduce: __arrayProto.reduce || function (fn, init) { + return _fnReduce(this, fn, init, 0, this.length, 1); + }, + + + reduceRight: __arrayProto.reduceRight || function (fn, init) { + return _fnReduce(this, fn, init, this.length - 1, -1, -1); + }, + + + reverse: __arrayProto.reverse, + + + // Object with rows, columns and opts + selector: null, + + + shift: __arrayProto.shift, + + + sort: __arrayProto.sort, // ? name - order? + + + splice: __arrayProto.splice, + + + toArray: function () { + return __arrayProto.slice.call(this); + }, + + + to$: function () { + return $(this); + }, + + + toJQuery: function () { + return $(this); + }, + + + unique: function () { + return new _Api(this.context, _unique(this)); + }, + + + unshift: __arrayProto.unshift + }); + + + _Api.extend = function (scope, obj, ext) { + // Only extend API instances and static properties of the API + if (!ext.length || !obj || (!(obj instanceof _Api) && !obj.__dt_wrapper)) { + return; + } + + var + i, ien, + j, jen, + struct, inner, + methodScoping = function (scope, fn, struc) { + return function () { + var ret = fn.apply(scope, arguments); + + // Method extension + _Api.extend(ret, ret, struc.methodExt); + return ret; + }; + }; + + for (i = 0, ien = ext.length; i < ien; i++) { + struct = ext[i]; + + // Value + obj[struct.name] = typeof struct.val === 'function' ? + methodScoping(scope, struct.val, struct) : + $.isPlainObject(struct.val) ? {} : + struct.val; + + obj[struct.name].__dt_wrapper = true; + + // Property extension + _Api.extend(scope, obj[struct.name], struct.propExt); + } + }; + + + // @todo - Is there need for an augment function? + // _Api.augment = function ( inst, name ) + // { + // // Find src object in the structure from the name + // var parts = name.split('.'); + + // _Api.extend( inst, obj ); + // }; + + + // [ + // { + // name: 'data' -- string - Property name + // val: function () {}, -- function - Api method (or undefined if just an object + // methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result + // propExt: [ ... ] -- array - Array of Api object definitions to extend the property + // }, + // { + // name: 'row' + // val: {}, + // methodExt: [ ... ], + // propExt: [ + // { + // name: 'data' + // val: function () {}, + // methodExt: [ ... ], + // propExt: [ ... ] + // }, + // ... + // ] + // } + // ] + + _Api.register = _api_register = function (name, val) { + if ($.isArray(name)) { + for (var j = 0, jen = name.length; j < jen; j++) { + _Api.register(name[j], val); + } + return; + } + + var + i, ien, + heir = name.split('.'), + struct = __apiStruct, + key, method; + + var find = function (src, name) { + for (var i = 0, ien = src.length; i < ien; i++) { + if (src[i].name === name) { + return src[i]; + } + } + return null; + }; + + for (i = 0, ien = heir.length; i < ien; i++) { + method = heir[i].indexOf('()') !== -1; + key = method ? + heir[i].replace('()', '') : + heir[i]; + + var src = find(struct, key); + if (!src) { + src = { + name: key, + val: {}, + methodExt: [], + propExt: [] + }; + struct.push(src); + } + + if (i === ien - 1) { + src.val = val; + } else { + struct = method ? + src.methodExt : + src.propExt; + } + } + }; + + + _Api.registerPlural = _api_registerPlural = function (pluralName, singularName, val) { + _Api.register(pluralName, val); + + _Api.register(singularName, function () { + var ret = val.apply(this, arguments); + + if (ret === this) { + // Returned item is the API instance that was passed in, return it + return this; + } else if (ret instanceof _Api) { + // New API instance returned, want the value from the first item + // in the returned array for the singular result. + return ret.length ? + $.isArray(ret[0]) ? + new _Api(ret.context, ret[0]) : // Array results are 'enhanced' + ret[0] : + undefined; + } + + // Non-API return - just fire it back + return ret; + }); + }; + + + /** + * Selector for HTML tables. Apply the given selector to the give array of + * DataTables settings objects. + * + * @param {string|integer} [selector] jQuery selector string or integer + * @param {array} Array of DataTables settings objects to be filtered + * @return {array} + * @ignore + */ + var __table_selector = function (selector, a) { + // Integer is used to pick out a table by index + if (typeof selector === 'number') { + return [a[selector]]; + } + + // Perform a jQuery selector on the table nodes + var nodes = $.map(a, function (el, i) { + return el.nTable; + }); + + return $(nodes) + .filter(selector) + .map(function (i) { + // Need to translate back from the table node to the settings + var idx = $.inArray(this, nodes); + return a[idx]; + }) + .toArray(); + }; + + + /** + * Context selector for the API's context (i.e. the tables the API instance + * refers to. + * + * @name DataTable.Api#tables + * @param {string|integer} [selector] Selector to pick which tables the iterator + * should operate on. If not given, all tables in the current context are + * used. This can be given as a jQuery selector (for example `':gt(0)'`) to + * select multiple tables or as an integer to select a single table. + * @returns {DataTable.Api} Returns a new API instance if a selector is given. + */ + _api_register('tables()', function (selector) { + // A new instance is created if there was a selector specified + return selector ? + new _Api(__table_selector(selector, this.context)) : + this; + }); + + + _api_register('table()', function (selector) { + var tables = this.tables(selector); + var ctx = tables.context; + + // Truncate to the first matched table + return ctx.length ? + new _Api(ctx[0]) : + tables; + }); + + + _api_registerPlural('tables().nodes()', 'table().node()', function () { + return this.iterator('table', function (ctx) { + return ctx.nTable; + }, 1); + }); + + + _api_registerPlural('tables().body()', 'table().body()', function () { + return this.iterator('table', function (ctx) { + return ctx.nTBody; + }, 1); + }); + + + _api_registerPlural('tables().header()', 'table().header()', function () { + return this.iterator('table', function (ctx) { + return ctx.nTHead; + }, 1); + }); + + + _api_registerPlural('tables().footer()', 'table().footer()', function () { + return this.iterator('table', function (ctx) { + return ctx.nTFoot; + }, 1); + }); + + + _api_registerPlural('tables().containers()', 'table().container()', function () { + return this.iterator('table', function (ctx) { + return ctx.nTableWrapper; + }, 1); + }); + + + /** + * Redraw the tables in the current context. + */ + _api_register('draw()', function (paging) { + return this.iterator('table', function (settings) { + if (paging === 'page') { + _fnDraw(settings); + } else { + if (typeof paging === 'string') { + paging = paging === 'full-hold' ? + false : + true; + } + + _fnReDraw(settings, paging === false); + } + }); + }); + + + /** + * Get the current page index. + * + * @return {integer} Current page index (zero based) + */ + /** + * Set the current page. + * + * Note that if you attempt to show a page which does not exist, DataTables will + * not throw an error, but rather reset the paging. + * + * @param {integer|string} action The paging action to take. This can be one of: + * * `integer` - The page index to jump to + * * `string` - An action to take: + * * `first` - Jump to first page. + * * `next` - Jump to the next page + * * `previous` - Jump to previous page + * * `last` - Jump to the last page. + * @returns {DataTables.Api} this + */ + _api_register('page()', function (action) { + if (action === undefined) { + return this.page.info().page; // not an expensive call + } + + // else, have an action to take on all tables + return this.iterator('table', function (settings) { + _fnPageChange(settings, action); + }); + }); + + + /** + * Paging information for the first table in the current context. + * + * If you require paging information for another table, use the `table()` method + * with a suitable selector. + * + * @return {object} Object with the following properties set: + * * `page` - Current page index (zero based - i.e. the first page is `0`) + * * `pages` - Total number of pages + * * `start` - Display index for the first record shown on the current page + * * `end` - Display index for the last record shown on the current page + * * `length` - Display length (number of records). Note that generally `start + * + length = end`, but this is not always true, for example if there are + * only 2 records to show on the final page, with a length of 10. + * * `recordsTotal` - Full data set length + * * `recordsDisplay` - Data set length once the current filtering criterion + * are applied. + */ + _api_register('page.info()', function (action) { + if (this.context.length === 0) { + return undefined; + } + + var + settings = this.context[0], + start = settings._iDisplayStart, + len = settings.oFeatures.bPaginate ? settings._iDisplayLength : -1, + visRecords = settings.fnRecordsDisplay(), + all = len === -1; + + return { + "page": all ? 0 : Math.floor(start / len), + "pages": all ? 1 : Math.ceil(visRecords / len), + "start": start, + "end": settings.fnDisplayEnd(), + "length": len, + "recordsTotal": settings.fnRecordsTotal(), + "recordsDisplay": visRecords, + "serverSide": _fnDataSource(settings) === 'ssp' + }; + }); + + + /** + * Get the current page length. + * + * @return {integer} Current page length. Note `-1` indicates that all records + * are to be shown. + */ + /** + * Set the current page length. + * + * @param {integer} Page length to set. Use `-1` to show all records. + * @returns {DataTables.Api} this + */ + _api_register('page.len()', function (len) { + // Note that we can't call this function 'length()' because `length` + // is a Javascript property of functions which defines how many arguments + // the function expects. + if (len === undefined) { + return this.context.length !== 0 ? + this.context[0]._iDisplayLength : + undefined; + } + + // else, set the page length + return this.iterator('table', function (settings) { + _fnLengthChange(settings, len); + }); + }); + + + var __reload = function (settings, holdPosition, callback) { + // Use the draw event to trigger a callback + if (callback) { + var api = new _Api(settings); + + api.one('draw', function () { + callback(api.ajax.json()); + }); + } + + if (_fnDataSource(settings) == 'ssp') { + _fnReDraw(settings, holdPosition); + } else { + _fnProcessingDisplay(settings, true); + + // Cancel an existing request + var xhr = settings.jqXHR; + if (xhr && xhr.readyState !== 4) { + xhr.abort(); + } + + // Trigger xhr + _fnBuildAjax(settings, [], function (json) { + _fnClearTable(settings); + + var data = _fnAjaxDataSrc(settings, json); + for (var i = 0, ien = data.length; i < ien; i++) { + _fnAddData(settings, data[i]); + } + + _fnReDraw(settings, holdPosition); + _fnProcessingDisplay(settings, false); + }); + } + }; + + + /** + * Get the JSON response from the last Ajax request that DataTables made to the + * server. Note that this returns the JSON from the first table in the current + * context. + * + * @return {object} JSON received from the server. + */ + _api_register('ajax.json()', function () { + var ctx = this.context; + + if (ctx.length > 0) { + return ctx[0].json; + } + + // else return undefined; + }); + + + /** + * Get the data submitted in the last Ajax request + */ + _api_register('ajax.params()', function () { + var ctx = this.context; + + if (ctx.length > 0) { + return ctx[0].oAjaxData; + } + + // else return undefined; + }); + + + /** + * Reload tables from the Ajax data source. Note that this function will + * automatically re-draw the table when the remote data has been loaded. + * + * @param {boolean} [reset=true] Reset (default) or hold the current paging + * position. A full re-sort and re-filter is performed when this method is + * called, which is why the pagination reset is the default action. + * @returns {DataTables.Api} this + */ + _api_register('ajax.reload()', function (callback, resetPaging) { + return this.iterator('table', function (settings) { + __reload(settings, resetPaging === false, callback); + }); + }); + + + /** + * Get the current Ajax URL. Note that this returns the URL from the first + * table in the current context. + * + * @return {string} Current Ajax source URL + */ + /** + * Set the Ajax URL. Note that this will set the URL for all tables in the + * current context. + * + * @param {string} url URL to set. + * @returns {DataTables.Api} this + */ + _api_register('ajax.url()', function (url) { + var ctx = this.context; + + if (url === undefined) { + // get + if (ctx.length === 0) { + return undefined; + } + ctx = ctx[0]; + + return ctx.ajax ? + $.isPlainObject(ctx.ajax) ? + ctx.ajax.url : + ctx.ajax : + ctx.sAjaxSource; + } + + // set + return this.iterator('table', function (settings) { + if ($.isPlainObject(settings.ajax)) { + settings.ajax.url = url; + } else { + settings.ajax = url; + } + // No need to consider sAjaxSource here since DataTables gives priority + // to `ajax` over `sAjaxSource`. So setting `ajax` here, renders any + // value of `sAjaxSource` redundant. + }); + }); + + + /** + * Load data from the newly set Ajax URL. Note that this method is only + * available when `ajax.url()` is used to set a URL. Additionally, this method + * has the same effect as calling `ajax.reload()` but is provided for + * convenience when setting a new URL. Like `ajax.reload()` it will + * automatically redraw the table once the remote data has been loaded. + * + * @returns {DataTables.Api} this + */ + _api_register('ajax.url().load()', function (callback, resetPaging) { + // Same as a reload, but makes sense to present it for easy access after a + // url change + return this.iterator('table', function (ctx) { + __reload(ctx, resetPaging === false, callback); + }); + }); + + + var _selector_run = function (type, selector, selectFn, settings, opts) { + var + out = [], + res, + a, i, ien, j, jen, + selectorType = typeof selector; + + // Can't just check for isArray here, as an API or jQuery instance might be + // given with their array like look + if (!selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined) { + selector = [selector]; + } + + for (i = 0, ien = selector.length; i < ien; i++) { + a = selector[i] && selector[i].split ? + selector[i].split(',') : [selector[i]]; + + for (j = 0, jen = a.length; j < jen; j++) { + res = selectFn(typeof a[j] === 'string' ? $.trim(a[j]) : a[j]); + + if (res && res.length) { + out = out.concat(res); + } + } + } + + // selector extensions + var ext = _ext.selector[type]; + if (ext.length) { + for (i = 0, ien = ext.length; i < ien; i++) { + out = ext[i](settings, opts, out); + } + } + + return _unique(out); + }; + + + var _selector_opts = function (opts) { + if (!opts) { + opts = {}; + } + + // Backwards compatibility for 1.9- which used the terminology filter rather + // than search + if (opts.filter && opts.search === undefined) { + opts.search = opts.filter; + } + + return $.extend({ + search: 'none', + order: 'current', + page: 'all' + }, opts); + }; + + + var _selector_first = function (inst) { + // Reduce the API instance to the first item found + for (var i = 0, ien = inst.length; i < ien; i++) { + if (inst[i].length > 0) { + // Assign the first element to the first item in the instance + // and truncate the instance and context + inst[0] = inst[i]; + inst[0].length = 1; + inst.length = 1; + inst.context = [inst.context[i]]; + + return inst; + } + } + + // Not found - return an empty instance + inst.length = 0; + return inst; + }; + + + var _selector_row_indexes = function (settings, opts) { + var + i, ien, tmp, a = [], + displayFiltered = settings.aiDisplay, + displayMaster = settings.aiDisplayMaster; + + var + search = opts.search, // none, applied, removed + order = opts.order, // applied, current, index (original - compatibility with 1.9) + page = opts.page; // all, current + + if (_fnDataSource(settings) == 'ssp') { + // In server-side processing mode, most options are irrelevant since + // rows not shown don't exist and the index order is the applied order + // Removed is a special case - for consistency just return an empty + // array + return search === 'removed' ? [] : + _range(0, displayMaster.length); + } else if (page == 'current') { + // Current page implies that order=current and fitler=applied, since it is + // fairly senseless otherwise, regardless of what order and search actually + // are + for (i = settings._iDisplayStart, ien = settings.fnDisplayEnd(); i < ien; i++) { + a.push(displayFiltered[i]); + } + } else if (order == 'current' || order == 'applied') { + a = search == 'none' ? + displayMaster.slice() : // no search + search == 'applied' ? + displayFiltered.slice() : // applied search + $.map(displayMaster, function (el, i) { // removed search + return $.inArray(el, displayFiltered) === -1 ? el : null; + }); + } else if (order == 'index' || order == 'original') { + for (i = 0, ien = settings.aoData.length; i < ien; i++) { + if (search == 'none') { + a.push(i); + } else { // applied | removed + tmp = $.inArray(i, displayFiltered); + + if ((tmp === -1 && search == 'removed') || + (tmp >= 0 && search == 'applied')) { + a.push(i); + } + } + } + } + + return a; + }; + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Rows + * + * {} - no selector - use all available rows + * {integer} - row aoData index + * {node} - TR node + * {string} - jQuery selector to apply to the TR elements + * {array} - jQuery array of nodes, or simply an array of TR nodes + * + */ + + + var __row_selector = function (settings, selector, opts) { + var run = function (sel) { + var selInt = _intVal(sel); + var i, ien; + + // Short cut - selector is a number and no options provided (default is + // all records, so no need to check if the index is in there, since it + // must be - dev error if the index doesn't exist). + if (selInt !== null && !opts) { + return [selInt]; + } + + var rows = _selector_row_indexes(settings, opts); + + if (selInt !== null && $.inArray(selInt, rows) !== -1) { + // Selector - integer + return [selInt]; + } else if (!sel) { + // Selector - none + return rows; + } + + // Selector - function + if (typeof sel === 'function') { + return $.map(rows, function (idx) { + var row = settings.aoData[idx]; + return sel(idx, row._aData, row.nTr) ? idx : null; + }); + } + + // Get nodes in the order from the `rows` array with null values removed + var nodes = _removeEmpty( + _pluck_order(settings.aoData, rows, 'nTr') + ); + + // Selector - node + if (sel.nodeName) { + if (sel._DT_RowIndex !== undefined) { + return [sel._DT_RowIndex]; // Property added by DT for fast lookup + } else if (sel._DT_CellIndex) { + return [sel._DT_CellIndex.row]; + } else { + var host = $(sel).closest('*[data-dt-row]'); + return host.length ? [host.data('dt-row')] : []; + } + } + + // ID selector. Want to always be able to select rows by id, regardless + // of if the tr element has been created or not, so can't rely upon + // jQuery here - hence a custom implementation. This does not match + // Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything, + // but to select it using a CSS selector engine (like Sizzle or + // querySelect) it would need to need to be escaped for some characters. + // DataTables simplifies this for row selectors since you can select + // only a row. A # indicates an id any anything that follows is the id - + // unescaped. + if (typeof sel === 'string' && sel.charAt(0) === '#') { + // get row index from id + var rowObj = settings.aIds[sel.replace(/^#/, '')]; + if (rowObj !== undefined) { + return [rowObj.idx]; + } + + // need to fall through to jQuery in case there is DOM id that + // matches + } + + // Selector - jQuery selector string, array of nodes or jQuery object/ + // As jQuery's .filter() allows jQuery objects to be passed in filter, + // it also allows arrays, so this will cope with all three options + return $(nodes) + .filter(sel) + .map(function () { + return this._DT_RowIndex; + }) + .toArray(); + }; + + return _selector_run('row', selector, run, settings, opts); + }; + + + _api_register('rows()', function (selector, opts) { + // argument shifting + if (selector === undefined) { + selector = ''; + } else if ($.isPlainObject(selector)) { + opts = selector; + selector = ''; + } + + opts = _selector_opts(opts); + + var inst = this.iterator('table', function (settings) { + return __row_selector(settings, selector, opts); + }, 1); + + // Want argument shifting here and in __row_selector? + inst.selector.rows = selector; + inst.selector.opts = opts; + + return inst; + }); + + _api_register('rows().nodes()', function () { + return this.iterator('row', function (settings, row) { + return settings.aoData[row].nTr || undefined; + }, 1); + }); + + _api_register('rows().data()', function () { + return this.iterator(true, 'rows', function (settings, rows) { + return _pluck_order(settings.aoData, rows, '_aData'); + }, 1); + }); + + _api_registerPlural('rows().cache()', 'row().cache()', function (type) { + return this.iterator('row', function (settings, row) { + var r = settings.aoData[row]; + return type === 'search' ? r._aFilterData : r._aSortData; + }, 1); + }); + + _api_registerPlural('rows().invalidate()', 'row().invalidate()', function (src) { + return this.iterator('row', function (settings, row) { + _fnInvalidate(settings, row, src); + }); + }); + + _api_registerPlural('rows().indexes()', 'row().index()', function () { + return this.iterator('row', function (settings, row) { + return row; + }, 1); + }); + + _api_registerPlural('rows().ids()', 'row().id()', function (hash) { + var a = []; + var context = this.context; + + // `iterator` will drop undefined values, but in this case we want them + for (var i = 0, ien = context.length; i < ien; i++) { + for (var j = 0, jen = this[i].length; j < jen; j++) { + var id = context[i].rowIdFn(context[i].aoData[this[i][j]]._aData); + a.push((hash === true ? '#' : '') + id); + } + } + + return new _Api(context, a); + }); + + _api_registerPlural('rows().remove()', 'row().remove()', function () { + var that = this; + + this.iterator('row', function (settings, row, thatIdx) { + var data = settings.aoData; + var rowData = data[row]; + var i, ien, j, jen; + var loopRow, loopCells; + + data.splice(row, 1); + + // Update the cached indexes + for (i = 0, ien = data.length; i < ien; i++) { + loopRow = data[i]; + loopCells = loopRow.anCells; + + // Rows + if (loopRow.nTr !== null) { + loopRow.nTr._DT_RowIndex = i; + } + + // Cells + if (loopCells !== null) { + for (j = 0, jen = loopCells.length; j < jen; j++) { + loopCells[j]._DT_CellIndex.row = i; + } + } + } + + // Delete from the display arrays + _fnDeleteIndex(settings.aiDisplayMaster, row); + _fnDeleteIndex(settings.aiDisplay, row); + _fnDeleteIndex(that[thatIdx], row, false); // maintain local indexes + + // Check for an 'overflow' they case for displaying the table + _fnLengthOverflow(settings); + + // Remove the row's ID reference if there is one + var id = settings.rowIdFn(rowData._aData); + if (id !== undefined) { + delete settings.aIds[id]; + } + }); + + this.iterator('table', function (settings) { + for (var i = 0, ien = settings.aoData.length; i < ien; i++) { + settings.aoData[i].idx = i; + } + }); + + return this; + }); + + + _api_register('rows.add()', function (rows) { + var newRows = this.iterator('table', function (settings) { + var row, i, ien; + var out = []; + + for (i = 0, ien = rows.length; i < ien; i++) { + row = rows[i]; + + if (row.nodeName && row.nodeName.toUpperCase() === 'TR') { + out.push(_fnAddTr(settings, row)[0]); + } else { + out.push(_fnAddData(settings, row)); + } + } + + return out; + }, 1); + + // Return an Api.rows() extended instance, so rows().nodes() etc can be used + var modRows = this.rows(-1); + modRows.pop(); + $.merge(modRows, newRows); + + return modRows; + }); + + + /** + * + */ + _api_register('row()', function (selector, opts) { + return _selector_first(this.rows(selector, opts)); + }); + + + _api_register('row().data()', function (data) { + var ctx = this.context; + + if (data === undefined) { + // Get + return ctx.length && this.length ? + ctx[0].aoData[this[0]]._aData : + undefined; + } + + // Set + ctx[0].aoData[this[0]]._aData = data; + + // Automatically invalidate + _fnInvalidate(ctx[0], this[0], 'data'); + + return this; + }); + + + _api_register('row().node()', function () { + var ctx = this.context; + + return ctx.length && this.length ? + ctx[0].aoData[this[0]].nTr || null : + null; + }); + + + _api_register('row.add()', function (row) { + // Allow a jQuery object to be passed in - only a single row is added from + // it though - the first element in the set + if (row instanceof $ && row.length) { + row = row[0]; + } + + var rows = this.iterator('table', function (settings) { + if (row.nodeName && row.nodeName.toUpperCase() === 'TR') { + return _fnAddTr(settings, row)[0]; + } + return _fnAddData(settings, row); + }); + + // Return an Api.rows() extended instance, with the newly added row selected + return this.row(rows[0]); + }); + + + var __details_add = function (ctx, row, data, klass) { + // Convert to array of TR elements + var rows = []; + var addRow = function (r, k) { + // Recursion to allow for arrays of jQuery objects + if ($.isArray(r) || r instanceof $) { + for (var i = 0, ien = r.length; i < ien; i++) { + addRow(r[i], k); + } + return; + } + + // If we get a TR element, then just add it directly - up to the dev + // to add the correct number of columns etc + if (r.nodeName && r.nodeName.toLowerCase() === 'tr') { + rows.push(r); + } else { + // Otherwise create a row with a wrapper + var created = $('<tr><td/></tr>').addClass(k); + $('td', created) + .addClass(k) + .html(r)[0].colSpan = _fnVisbleColumns(ctx); + + rows.push(created[0]); + } + }; + + addRow(data, klass); + + if (row._details) { + row._details.remove(); + } + + row._details = $(rows); + + // If the children were already shown, that state should be retained + if (row._detailsShow) { + row._details.insertAfter(row.nTr); + } + }; + + + var __details_remove = function (api, idx) { + var ctx = api.context; + + if (ctx.length) { + var row = ctx[0].aoData[idx !== undefined ? idx : api[0]]; + + if (row && row._details) { + row._details.remove(); + + row._detailsShow = undefined; + row._details = undefined; + } + } + }; + + + var __details_display = function (api, show) { + var ctx = api.context; + + if (ctx.length && api.length) { + var row = ctx[0].aoData[api[0]]; + + if (row._details) { + row._detailsShow = show; + + if (show) { + row._details.insertAfter(row.nTr); + } else { + row._details.detach(); + } + + __details_events(ctx[0]); + } + } + }; + + + var __details_events = function (settings) { + var api = new _Api(settings); + var namespace = '.dt.DT_details'; + var drawEvent = 'draw' + namespace; + var colvisEvent = 'column-visibility' + namespace; + var destroyEvent = 'destroy' + namespace; + var data = settings.aoData; + + api.off(drawEvent + ' ' + colvisEvent + ' ' + destroyEvent); + + if (_pluck(data, '_details').length > 0) { + // On each draw, insert the required elements into the document + api.on(drawEvent, function (e, ctx) { + if (settings !== ctx) { + return; + } + + api.rows({ + page: 'current' + }).eq(0).each(function (idx) { + // Internal data grab + var row = data[idx]; + + if (row._detailsShow) { + row._details.insertAfter(row.nTr); + } + }); + }); + + // Column visibility change - update the colspan + api.on(colvisEvent, function (e, ctx, idx, vis) { + if (settings !== ctx) { + return; + } + + // Update the colspan for the details rows (note, only if it already has + // a colspan) + var row, visible = _fnVisbleColumns(ctx); + + for (var i = 0, ien = data.length; i < ien; i++) { + row = data[i]; + + if (row._details) { + row._details.children('td[colspan]').attr('colspan', visible); + } + } + }); + + // Table destroyed - nuke any child rows + api.on(destroyEvent, function (e, ctx) { + if (settings !== ctx) { + return; + } + + for (var i = 0, ien = data.length; i < ien; i++) { + if (data[i]._details) { + __details_remove(api, i); + } + } + }); + } + }; + + // Strings for the method names to help minification + var _emp = ''; + var _child_obj = _emp + 'row().child'; + var _child_mth = _child_obj + '()'; + + // data can be: + // tr + // string + // jQuery or array of any of the above + _api_register(_child_mth, function (data, klass) { + var ctx = this.context; + + if (data === undefined) { + // get + return ctx.length && this.length ? + ctx[0].aoData[this[0]]._details : + undefined; + } else if (data === true) { + // show + this.child.show(); + } else if (data === false) { + // remove + __details_remove(this); + } else if (ctx.length && this.length) { + // set + __details_add(ctx[0], ctx[0].aoData[this[0]], data, klass); + } + + return this; + }); + + + _api_register([ + _child_obj + '.show()', + _child_mth + '.show()' // only when `child()` was called with parameters (without + ], function (show) { // it returns an object and this method is not executed) + __details_display(this, true); + return this; + }); + + + _api_register([ + _child_obj + '.hide()', + _child_mth + '.hide()' // only when `child()` was called with parameters (without + ], function () { // it returns an object and this method is not executed) + __details_display(this, false); + return this; + }); + + + _api_register([ + _child_obj + '.remove()', + _child_mth + '.remove()' // only when `child()` was called with parameters (without + ], function () { // it returns an object and this method is not executed) + __details_remove(this); + return this; + }); + + + _api_register(_child_obj + '.isShown()', function () { + var ctx = this.context; + + if (ctx.length && this.length) { + // _detailsShown as false or undefined will fall through to return false + return ctx[0].aoData[this[0]]._detailsShow || false; + } + return false; + }); + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Columns + * + * {integer} - column index (>=0 count from left, <0 count from right) + * "{integer}:visIdx" - visible column index (i.e. translate to column index) (>=0 count from left, <0 count from right) + * "{integer}:visible" - alias for {integer}:visIdx (>=0 count from left, <0 count from right) + * "{string}:name" - column name + * "{string}" - jQuery selector on column header nodes + * + */ + + // can be an array of these items, comma separated list, or an array of comma + // separated lists + + var __re_column_selector = /^(.+):(name|visIdx|visible)$/; + + + // r1 and r2 are redundant - but it means that the parameters match for the + // iterator callback in columns().data() + var __columnData = function (settings, column, r1, r2, rows) { + var a = []; + for (var row = 0, ien = rows.length; row < ien; row++) { + a.push(_fnGetCellData(settings, rows[row], column)); + } + return a; + }; + + + var __column_selector = function (settings, selector, opts) { + var + columns = settings.aoColumns, + names = _pluck(columns, 'sName'), + nodes = _pluck(columns, 'nTh'); + + var run = function (s) { + var selInt = _intVal(s); + + // Selector - all + if (s === '') { + return _range(columns.length); + } + + // Selector - index + if (selInt !== null) { + return [selInt >= 0 ? + selInt : // Count from left + columns.length + selInt // Count from right (+ because its a negative value) + ]; + } + + // Selector = function + if (typeof s === 'function') { + var rows = _selector_row_indexes(settings, opts); + + return $.map(columns, function (col, idx) { + return s( + idx, + __columnData(settings, idx, 0, 0, rows), + nodes[idx] + ) ? idx : null; + }); + } + + // jQuery or string selector + var match = typeof s === 'string' ? + s.match(__re_column_selector) : + ''; + + if (match) { + switch (match[2]) { + case 'visIdx': + case 'visible': + var idx = parseInt(match[1], 10); + // Visible index given, convert to column index + if (idx < 0) { + // Counting from the right + var visColumns = $.map(columns, function (col, i) { + return col.bVisible ? i : null; + }); + return [visColumns[visColumns.length + idx]]; + } + // Counting from the left + return [_fnVisibleToColumnIndex(settings, idx)]; + + case 'name': + // match by name. `names` is column index complete and in order + return $.map(names, function (name, i) { + return name === match[1] ? i : null; + }); + + default: + return []; + } + } + + // Cell in the table body + if (s.nodeName && s._DT_CellIndex) { + return [s._DT_CellIndex.column]; + } + + // jQuery selector on the TH elements for the columns + var jqResult = $(nodes) + .filter(s) + .map(function () { + return $.inArray(this, nodes); // `nodes` is column index complete and in order + }) + .toArray(); + + if (jqResult.length || !s.nodeName) { + return jqResult; + } + + // Otherwise a node which might have a `dt-column` data attribute, or be + // a child or such an element + var host = $(s).closest('*[data-dt-column]'); + return host.length ? [host.data('dt-column')] : []; + }; + + return _selector_run('column', selector, run, settings, opts); + }; + + + var __setColumnVis = function (settings, column, vis) { + var + cols = settings.aoColumns, + col = cols[column], + data = settings.aoData, + row, cells, i, ien, tr; + + // Get + if (vis === undefined) { + return col.bVisible; + } + + // Set + // No change + if (col.bVisible === vis) { + return; + } + + if (vis) { + // Insert column + // Need to decide if we should use appendChild or insertBefore + var insertBefore = $.inArray(true, _pluck(cols, 'bVisible'), column + 1); + + for (i = 0, ien = data.length; i < ien; i++) { + tr = data[i].nTr; + cells = data[i].anCells; + + if (tr) { + // insertBefore can act like appendChild if 2nd arg is null + tr.insertBefore(cells[column], cells[insertBefore] || null); + } + } + } else { + // Remove column + $(_pluck(settings.aoData, 'anCells', column)).detach(); + } + + // Common actions + col.bVisible = vis; + _fnDrawHead(settings, settings.aoHeader); + _fnDrawHead(settings, settings.aoFooter); + + _fnSaveState(settings); + }; + + + _api_register('columns()', function (selector, opts) { + // argument shifting + if (selector === undefined) { + selector = ''; + } else if ($.isPlainObject(selector)) { + opts = selector; + selector = ''; + } + + opts = _selector_opts(opts); + + var inst = this.iterator('table', function (settings) { + return __column_selector(settings, selector, opts); + }, 1); + + // Want argument shifting here and in _row_selector? + inst.selector.cols = selector; + inst.selector.opts = opts; + + return inst; + }); + + _api_registerPlural('columns().header()', 'column().header()', function (selector, opts) { + return this.iterator('column', function (settings, column) { + return settings.aoColumns[column].nTh; + }, 1); + }); + + _api_registerPlural('columns().footer()', 'column().footer()', function (selector, opts) { + return this.iterator('column', function (settings, column) { + return settings.aoColumns[column].nTf; + }, 1); + }); + + _api_registerPlural('columns().data()', 'column().data()', function () { + return this.iterator('column-rows', __columnData, 1); + }); + + _api_registerPlural('columns().dataSrc()', 'column().dataSrc()', function () { + return this.iterator('column', function (settings, column) { + return settings.aoColumns[column].mData; + }, 1); + }); + + _api_registerPlural('columns().cache()', 'column().cache()', function (type) { + return this.iterator('column-rows', function (settings, column, i, j, rows) { + return _pluck_order(settings.aoData, rows, + type === 'search' ? '_aFilterData' : '_aSortData', column + ); + }, 1); + }); + + _api_registerPlural('columns().nodes()', 'column().nodes()', function () { + return this.iterator('column-rows', function (settings, column, i, j, rows) { + return _pluck_order(settings.aoData, rows, 'anCells', column); + }, 1); + }); + + _api_registerPlural('columns().visible()', 'column().visible()', function (vis, calc) { + var ret = this.iterator('column', function (settings, column) { + if (vis === undefined) { + return settings.aoColumns[column].bVisible; + } // else + __setColumnVis(settings, column, vis); + }); + + // Group the column visibility changes + if (vis !== undefined) { + // Second loop once the first is done for events + this.iterator('column', function (settings, column) { + _fnCallbackFire(settings, null, 'column-visibility', [settings, column, vis, calc]); + }); + + if (calc === undefined || calc) { + this.columns.adjust(); + } + } + + return ret; + }); + + _api_registerPlural('columns().indexes()', 'column().index()', function (type) { + return this.iterator('column', function (settings, column) { + return type === 'visible' ? + _fnColumnIndexToVisible(settings, column) : + column; + }, 1); + }); + + _api_register('columns.adjust()', function () { + return this.iterator('table', function (settings) { + _fnAdjustColumnSizing(settings); + }, 1); + }); + + _api_register('column.index()', function (type, idx) { + if (this.context.length !== 0) { + var ctx = this.context[0]; + + if (type === 'fromVisible' || type === 'toData') { + return _fnVisibleToColumnIndex(ctx, idx); + } else if (type === 'fromData' || type === 'toVisible') { + return _fnColumnIndexToVisible(ctx, idx); + } + } + }); + + _api_register('column()', function (selector, opts) { + return _selector_first(this.columns(selector, opts)); + }); + + + var __cell_selector = function (settings, selector, opts) { + var data = settings.aoData; + var rows = _selector_row_indexes(settings, opts); + var cells = _removeEmpty(_pluck_order(data, rows, 'anCells')); + var allCells = $([].concat.apply([], cells)); + var row; + var columns = settings.aoColumns.length; + var a, i, ien, j, o, host; + + var run = function (s) { + var fnSelector = typeof s === 'function'; + + if (s === null || s === undefined || fnSelector) { + // All cells and function selectors + a = []; + + for (i = 0, ien = rows.length; i < ien; i++) { + row = rows[i]; + + for (j = 0; j < columns; j++) { + o = { + row: row, + column: j + }; + + if (fnSelector) { + // Selector - function + host = data[row]; + + if (s(o, _fnGetCellData(settings, row, j), host.anCells ? host.anCells[j] : null)) { + a.push(o); + } + } else { + // Selector - all + a.push(o); + } + } + } + + return a; + } + + // Selector - index + if ($.isPlainObject(s)) { + return [s]; + } + + // Selector - jQuery filtered cells + var jqResult = allCells + .filter(s) + .map(function (i, el) { + return { // use a new object, in case someone changes the values + row: el._DT_CellIndex.row, + column: el._DT_CellIndex.column + }; + }) + .toArray(); + + if (jqResult.length || !s.nodeName) { + return jqResult; + } + + // Otherwise the selector is a node, and there is one last option - the + // element might be a child of an element which has dt-row and dt-column + // data attributes + host = $(s).closest('*[data-dt-row]'); + return host.length ? [{ + row: host.data('dt-row'), + column: host.data('dt-column') + }] : []; + }; + + return _selector_run('cell', selector, run, settings, opts); + }; + + + _api_register('cells()', function (rowSelector, columnSelector, opts) { + // Argument shifting + if ($.isPlainObject(rowSelector)) { + // Indexes + if (rowSelector.row === undefined) { + // Selector options in first parameter + opts = rowSelector; + rowSelector = null; + } else { + // Cell index objects in first parameter + opts = columnSelector; + columnSelector = null; + } + } + if ($.isPlainObject(columnSelector)) { + opts = columnSelector; + columnSelector = null; + } + + // Cell selector + if (columnSelector === null || columnSelector === undefined) { + return this.iterator('table', function (settings) { + return __cell_selector(settings, rowSelector, _selector_opts(opts)); + }); + } + + // Row + column selector + var columns = this.columns(columnSelector, opts); + var rows = this.rows(rowSelector, opts); + var a, i, ien, j, jen; + + var cells = this.iterator('table', function (settings, idx) { + a = []; + + for (i = 0, ien = rows[idx].length; i < ien; i++) { + for (j = 0, jen = columns[idx].length; j < jen; j++) { + a.push({ + row: rows[idx][i], + column: columns[idx][j] + }); + } + } + + return a; + }, 1); + + $.extend(cells.selector, { + cols: columnSelector, + rows: rowSelector, + opts: opts + }); + + return cells; + }); + + + _api_registerPlural('cells().nodes()', 'cell().node()', function () { + return this.iterator('cell', function (settings, row, column) { + var data = settings.aoData[row]; + + return data && data.anCells ? + data.anCells[column] : + undefined; + }, 1); + }); + + + _api_register('cells().data()', function () { + return this.iterator('cell', function (settings, row, column) { + return _fnGetCellData(settings, row, column); + }, 1); + }); + + + _api_registerPlural('cells().cache()', 'cell().cache()', function (type) { + type = type === 'search' ? '_aFilterData' : '_aSortData'; + + return this.iterator('cell', function (settings, row, column) { + return settings.aoData[row][type][column]; + }, 1); + }); + + + _api_registerPlural('cells().render()', 'cell().render()', function (type) { + return this.iterator('cell', function (settings, row, column) { + return _fnGetCellData(settings, row, column, type); + }, 1); + }); + + + _api_registerPlural('cells().indexes()', 'cell().index()', function () { + return this.iterator('cell', function (settings, row, column) { + return { + row: row, + column: column, + columnVisible: _fnColumnIndexToVisible(settings, column) + }; + }, 1); + }); + + + _api_registerPlural('cells().invalidate()', 'cell().invalidate()', function (src) { + return this.iterator('cell', function (settings, row, column) { + _fnInvalidate(settings, row, src, column); + }); + }); + + + _api_register('cell()', function (rowSelector, columnSelector, opts) { + return _selector_first(this.cells(rowSelector, columnSelector, opts)); + }); + + + _api_register('cell().data()', function (data) { + var ctx = this.context; + var cell = this[0]; + + if (data === undefined) { + // Get + return ctx.length && cell.length ? + _fnGetCellData(ctx[0], cell[0].row, cell[0].column) : + undefined; + } + + // Set + _fnSetCellData(ctx[0], cell[0].row, cell[0].column, data); + _fnInvalidate(ctx[0], cell[0].row, 'data', cell[0].column); + + return this; + }); + + + /** + * Get current ordering (sorting) that has been applied to the table. + * + * @returns {array} 2D array containing the sorting information for the first + * table in the current context. Each element in the parent array represents + * a column being sorted upon (i.e. multi-sorting with two columns would have + * 2 inner arrays). The inner arrays may have 2 or 3 elements. The first is + * the column index that the sorting condition applies to, the second is the + * direction of the sort (`desc` or `asc`) and, optionally, the third is the + * index of the sorting order from the `column.sorting` initialisation array. + */ + /** + * Set the ordering for the table. + * + * @param {integer} order Column index to sort upon. + * @param {string} direction Direction of the sort to be applied (`asc` or `desc`) + * @returns {DataTables.Api} this + */ + /** + * Set the ordering for the table. + * + * @param {array} order 1D array of sorting information to be applied. + * @param {array} [...] Optional additional sorting conditions + * @returns {DataTables.Api} this + */ + /** + * Set the ordering for the table. + * + * @param {array} order 2D array of sorting information to be applied. + * @returns {DataTables.Api} this + */ + _api_register('order()', function (order, dir) { + var ctx = this.context; + + if (order === undefined) { + // get + return ctx.length !== 0 ? + ctx[0].aaSorting : + undefined; + } + + // set + if (typeof order === 'number') { + // Simple column / direction passed in + order = [ + [order, dir] + ]; + } else if (order.length && !$.isArray(order[0])) { + // Arguments passed in (list of 1D arrays) + order = Array.prototype.slice.call(arguments); + } + // otherwise a 2D array was passed in + + return this.iterator('table', function (settings) { + settings.aaSorting = order.slice(); + }); + }); + + + /** + * Attach a sort listener to an element for a given column + * + * @param {node|jQuery|string} node Identifier for the element(s) to attach the + * listener to. This can take the form of a single DOM node, a jQuery + * collection of nodes or a jQuery selector which will identify the node(s). + * @param {integer} column the column that a click on this node will sort on + * @param {function} [callback] callback function when sort is run + * @returns {DataTables.Api} this + */ + _api_register('order.listener()', function (node, column, callback) { + return this.iterator('table', function (settings) { + _fnSortAttachListener(settings, node, column, callback); + }); + }); + + + _api_register('order.fixed()', function (set) { + if (!set) { + var ctx = this.context; + var fixed = ctx.length ? + ctx[0].aaSortingFixed : + undefined; + + return $.isArray(fixed) ? { + pre: fixed + } : + fixed; + } + + return this.iterator('table', function (settings) { + settings.aaSortingFixed = $.extend(true, {}, set); + }); + }); + + + // Order by the selected column(s) + _api_register([ + 'columns().order()', + 'column().order()' + ], function (dir) { + var that = this; + + return this.iterator('table', function (settings, i) { + var sort = []; + + $.each(that[i], function (j, col) { + sort.push([col, dir]); + }); + + settings.aaSorting = sort; + }); + }); + + + _api_register('search()', function (input, regex, smart, caseInsen) { + var ctx = this.context; + + if (input === undefined) { + // get + return ctx.length !== 0 ? + ctx[0].oPreviousSearch.sSearch : + undefined; + } + + // set + return this.iterator('table', function (settings) { + if (!settings.oFeatures.bFilter) { + return; + } + + _fnFilterComplete(settings, $.extend({}, settings.oPreviousSearch, { + "sSearch": input + "", + "bRegex": regex === null ? false : regex, + "bSmart": smart === null ? true : smart, + "bCaseInsensitive": caseInsen === null ? true : caseInsen + }), 1); + }); + }); + + + _api_registerPlural( + 'columns().search()', + 'column().search()', + function (input, regex, smart, caseInsen) { + return this.iterator('column', function (settings, column) { + var preSearch = settings.aoPreSearchCols; + + if (input === undefined) { + // get + return preSearch[column].sSearch; + } + + // set + if (!settings.oFeatures.bFilter) { + return; + } + + $.extend(preSearch[column], { + "sSearch": input + "", + "bRegex": regex === null ? false : regex, + "bSmart": smart === null ? true : smart, + "bCaseInsensitive": caseInsen === null ? true : caseInsen + }); + + _fnFilterComplete(settings, settings.oPreviousSearch, 1); + }); + } + ); + + /* + * State API methods + */ + + _api_register('state()', function () { + return this.context.length ? + this.context[0].oSavedState : + null; + }); + + + _api_register('state.clear()', function () { + return this.iterator('table', function (settings) { + // Save an empty object + settings.fnStateSaveCallback.call(settings.oInstance, settings, {}); + }); + }); + + + _api_register('state.loaded()', function () { + return this.context.length ? + this.context[0].oLoadedState : + null; + }); + + + _api_register('state.save()', function () { + return this.iterator('table', function (settings) { + _fnSaveState(settings); + }); + }); + + + /** + * Provide a common method for plug-ins to check the version of DataTables being + * used, in order to ensure compatibility. + * + * @param {string} version Version string to check for, in the format "X.Y.Z". + * Note that the formats "X" and "X.Y" are also acceptable. + * @returns {boolean} true if this version of DataTables is greater or equal to + * the required version, or false if this version of DataTales is not + * suitable + * @static + * @dtopt API-Static + * + * @example + * alert( $.fn.dataTable.versionCheck( '1.9.0' ) ); + */ + DataTable.versionCheck = DataTable.fnVersionCheck = function (version) { + var aThis = DataTable.version.split('.'); + var aThat = version.split('.'); + var iThis, iThat; + + for (var i = 0, iLen = aThat.length; i < iLen; i++) { + iThis = parseInt(aThis[i], 10) || 0; + iThat = parseInt(aThat[i], 10) || 0; + + // Parts are the same, keep comparing + if (iThis === iThat) { + continue; + } + + // Parts are different, return immediately + return iThis > iThat; + } + + return true; + }; + + + /** + * Check if a `<table>` node is a DataTable table already or not. + * + * @param {node|jquery|string} table Table node, jQuery object or jQuery + * selector for the table to test. Note that if more than more than one + * table is passed on, only the first will be checked + * @returns {boolean} true the table given is a DataTable, or false otherwise + * @static + * @dtopt API-Static + * + * @example + * if ( ! $.fn.DataTable.isDataTable( '#example' ) ) { + * $('#example').dataTable(); + * } + */ + DataTable.isDataTable = DataTable.fnIsDataTable = function (table) { + var t = $(table).get(0); + var is = false; + + $.each(DataTable.settings, function (i, o) { + var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null; + var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null; + + if (o.nTable === t || head === t || foot === t) { + is = true; + } + }); + + return is; + }; + + + /** + * Get all DataTable tables that have been initialised - optionally you can + * select to get only currently visible tables. + * + * @param {boolean} [visible=false] Flag to indicate if you want all (default) + * or visible tables only. + * @returns {array} Array of `table` nodes (not DataTable instances) which are + * DataTables + * @static + * @dtopt API-Static + * + * @example + * $.each( $.fn.dataTable.tables(true), function () { + * $(table).DataTable().columns.adjust(); + * } ); + */ + DataTable.tables = DataTable.fnTables = function (visible) { + var api = false; + + if ($.isPlainObject(visible)) { + api = visible.api; + visible = visible.visible; + } + + var a = $.map(DataTable.settings, function (o) { + if (!visible || (visible && $(o.nTable).is(':visible'))) { + return o.nTable; + } + }); + + return api ? + new _Api(a) : + a; + }; + + + /** + * Convert from camel case parameters to Hungarian notation. This is made public + * for the extensions to provide the same ability as DataTables core to accept + * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase + * parameters. + * + * @param {object} src The model object which holds all parameters that can be + * mapped. + * @param {object} user The object to convert from camel case to Hungarian. + * @param {boolean} force When set to `true`, properties which already have a + * Hungarian value in the `user` object will be overwritten. Otherwise they + * won't be. + */ + DataTable.camelToHungarian = _fnCamelToHungarian; + + + /** + * + */ + _api_register('$()', function (selector, opts) { + var + rows = this.rows(opts).nodes(), // Get all rows + jqRows = $(rows); + + return $([].concat( + jqRows.filter(selector).toArray(), + jqRows.find(selector).toArray() + )); + }); + + + // jQuery functions to operate on the tables + $.each(['on', 'one', 'off'], function (i, key) { + _api_register(key + '()', function ( /* event, handler */ ) { + var args = Array.prototype.slice.call(arguments); + + // Add the `dt` namespace automatically if it isn't already present + if (!args[0].match(/\.dt\b/)) { + args[0] += '.dt'; + } + + var inst = $(this.tables().nodes()); + inst[key].apply(inst, args); + return this; + }); + }); + + + _api_register('clear()', function () { + return this.iterator('table', function (settings) { + _fnClearTable(settings); + }); + }); + + + _api_register('settings()', function () { + return new _Api(this.context, this.context); + }); + + + _api_register('init()', function () { + var ctx = this.context; + return ctx.length ? ctx[0].oInit : null; + }); + + + _api_register('data()', function () { + return this.iterator('table', function (settings) { + return _pluck(settings.aoData, '_aData'); + }).flatten(); + }); + + + _api_register('destroy()', function (remove) { + remove = remove || false; + + return this.iterator('table', function (settings) { + var orig = settings.nTableWrapper.parentNode; + var classes = settings.oClasses; + var table = settings.nTable; + var tbody = settings.nTBody; + var thead = settings.nTHead; + var tfoot = settings.nTFoot; + var jqTable = $(table); + var jqTbody = $(tbody); + var jqWrapper = $(settings.nTableWrapper); + var rows = $.map(settings.aoData, function (r) { + return r.nTr; + }); + var i, ien; + + // Flag to note that the table is currently being destroyed - no action + // should be taken + settings.bDestroying = true; + + // Fire off the destroy callbacks for plug-ins etc + _fnCallbackFire(settings, "aoDestroyCallback", "destroy", [settings]); + + // If not being removed from the document, make all columns visible + if (!remove) { + new _Api(settings).columns().visible(true); + } + + // Blitz all `DT` namespaced events (these are internal events, the + // lowercase, `dt` events are user subscribed and they are responsible + // for removing them + jqWrapper.unbind('.DT').find(':not(tbody *)').unbind('.DT'); + $(window).unbind('.DT-' + settings.sInstance); + + // When scrolling we had to break the table up - restore it + if (table != thead.parentNode) { + jqTable.children('thead').detach(); + jqTable.append(thead); + } + + if (tfoot && table != tfoot.parentNode) { + jqTable.children('tfoot').detach(); + jqTable.append(tfoot); + } + + settings.aaSorting = []; + settings.aaSortingFixed = []; + _fnSortingClasses(settings); + + $(rows).removeClass(settings.asStripeClasses.join(' ')); + + $('th, td', thead).removeClass(classes.sSortable + ' ' + + classes.sSortableAsc + ' ' + classes.sSortableDesc + ' ' + classes.sSortableNone + ); + + if (settings.bJUI) { + $('th span.' + classes.sSortIcon + ', td span.' + classes.sSortIcon, thead).detach(); + $('th, td', thead).each(function () { + var wrapper = $('div.' + classes.sSortJUIWrapper, this); + $(this).append(wrapper.contents()); + wrapper.detach(); + }); + } + + // Add the TR elements back into the table in their original order + jqTbody.children().detach(); + jqTbody.append(rows); + + // Remove the DataTables generated nodes, events and classes + var removedMethod = remove ? 'remove' : 'detach'; + jqTable[removedMethod](); + jqWrapper[removedMethod](); + + // If we need to reattach the table to the document + if (!remove && orig) { + // insertBefore acts like appendChild if !arg[1] + orig.insertBefore(table, settings.nTableReinsertBefore); + + // Restore the width of the original table - was read from the style property, + // so we can restore directly to that + jqTable + .css('width', settings.sDestroyWidth) + .removeClass(classes.sTable); + + // If the were originally stripe classes - then we add them back here. + // Note this is not fool proof (for example if not all rows had stripe + // classes - but it's a good effort without getting carried away + ien = settings.asDestroyStripes.length; + + if (ien) { + jqTbody.children().each(function (i) { + $(this).addClass(settings.asDestroyStripes[i % ien]); + }); + } + } + + /* Remove the settings object from the settings array */ + var idx = $.inArray(settings, DataTable.settings); + if (idx !== -1) { + DataTable.settings.splice(idx, 1); + } + }); + }); + + + // Add the `every()` method for rows, columns and cells in a compact form + $.each(['column', 'row', 'cell'], function (i, type) { + _api_register(type + 's().every()', function (fn) { + var opts = this.selector.opts; + var api = this; + + return this.iterator(type, function (settings, arg1, arg2, arg3, arg4) { + // Rows and columns: + // arg1 - index + // arg2 - table counter + // arg3 - loop counter + // arg4 - undefined + // Cells: + // arg1 - row index + // arg2 - column index + // arg3 - table counter + // arg4 - loop counter + fn.call( + api[type]( + arg1, + type === 'cell' ? arg2 : opts, + type === 'cell' ? opts : undefined + ), + arg1, arg2, arg3, arg4 + ); + }); + }); + }); + + + // i18n method for extensions to be able to use the language object from the + // DataTable + _api_register('i18n()', function (token, def, plural) { + var ctx = this.context[0]; + var resolved = _fnGetObjectDataFn(token)(ctx.oLanguage); + + if (resolved === undefined) { + resolved = def; + } + + if (plural !== undefined && $.isPlainObject(resolved)) { + resolved = resolved[plural] !== undefined ? + resolved[plural] : + resolved._; + } + + return resolved.replace('%d', plural); // nb: plural might be undefined, + }); + + /** + * Version string for plug-ins to check compatibility. Allowed format is + * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used + * only for non-release builds. See http://semver.org/ for more information. + * @member + * @type string + * @default Version number + */ + DataTable.version = "1.10.12"; + + /** + * Private data store, containing all of the settings objects that are + * created for the tables on a given page. + * + * Note that the `DataTable.settings` object is aliased to + * `jQuery.fn.dataTableExt` through which it may be accessed and + * manipulated, or `jQuery.fn.dataTable.settings`. + * @member + * @type array + * @default [] + * @private + */ + DataTable.settings = []; + + /** + * Object models container, for the various models that DataTables has + * available to it. These models define the objects that are used to hold + * the active state and configuration of the table. + * @namespace + */ + DataTable.models = {}; + + + /** + * Template object for the way in which DataTables holds information about + * search information for the global filter and individual column filters. + * @namespace + */ + DataTable.models.oSearch = { + /** + * Flag to indicate if the filtering should be case insensitive or not + * @type boolean + * @default true + */ + "bCaseInsensitive": true, + + /** + * Applied search term + * @type string + * @default <i>Empty string</i> + */ + "sSearch": "", + + /** + * Flag to indicate if the search term should be interpreted as a + * regular expression (true) or not (false) and therefore and special + * regex characters escaped. + * @type boolean + * @default false + */ + "bRegex": false, + + /** + * Flag to indicate if DataTables is to use its smart filtering or not. + * @type boolean + * @default true + */ + "bSmart": true + }; + + + /** + * Template object for the way in which DataTables holds information about + * each individual row. This is the object format used for the settings + * aoData array. + * @namespace + */ + DataTable.models.oRow = { + /** + * TR element for the row + * @type node + * @default null + */ + "nTr": null, + + /** + * Array of TD elements for each row. This is null until the row has been + * created. + * @type array nodes + * @default [] + */ + "anCells": null, + + /** + * Data object from the original data source for the row. This is either + * an array if using the traditional form of DataTables, or an object if + * using mData options. The exact type will depend on the passed in + * data from the data source, or will be an array if using DOM a data + * source. + * @type array|object + * @default [] + */ + "_aData": [], + + /** + * Sorting data cache - this array is ostensibly the same length as the + * number of columns (although each index is generated only as it is + * needed), and holds the data that is used for sorting each column in the + * row. We do this cache generation at the start of the sort in order that + * the formatting of the sort data need be done only once for each cell + * per sort. This array should not be read from or written to by anything + * other than the master sorting methods. + * @type array + * @default null + * @private + */ + "_aSortData": null, + + /** + * Per cell filtering data cache. As per the sort data cache, used to + * increase the performance of the filtering in DataTables + * @type array + * @default null + * @private + */ + "_aFilterData": null, + + /** + * Filtering data cache. This is the same as the cell filtering cache, but + * in this case a string rather than an array. This is easily computed with + * a join on `_aFilterData`, but is provided as a cache so the join isn't + * needed on every search (memory traded for performance) + * @type array + * @default null + * @private + */ + "_sFilterRow": null, + + /** + * Cache of the class name that DataTables has applied to the row, so we + * can quickly look at this variable rather than needing to do a DOM check + * on className for the nTr property. + * @type string + * @default <i>Empty string</i> + * @private + */ + "_sRowStripe": "", + + /** + * Denote if the original data source was from the DOM, or the data source + * object. This is used for invalidating data, so DataTables can + * automatically read data from the original source, unless uninstructed + * otherwise. + * @type string + * @default null + * @private + */ + "src": null, + + /** + * Index in the aoData array. This saves an indexOf lookup when we have the + * object, but want to know the index + * @type integer + * @default -1 + * @private + */ + "idx": -1 + }; + + + /** + * Template object for the column information object in DataTables. This object + * is held in the settings aoColumns array and contains all the information that + * DataTables needs about each individual column. + * + * Note that this object is related to {@link DataTable.defaults.column} + * but this one is the internal data store for DataTables's cache of columns. + * It should NOT be manipulated outside of DataTables. Any configuration should + * be done through the initialisation options. + * @namespace + */ + DataTable.models.oColumn = { + /** + * Column index. This could be worked out on-the-fly with $.inArray, but it + * is faster to just hold it as a variable + * @type integer + * @default null + */ + "idx": null, + + /** + * A list of the columns that sorting should occur on when this column + * is sorted. That this property is an array allows multi-column sorting + * to be defined for a column (for example first name / last name columns + * would benefit from this). The values are integers pointing to the + * columns to be sorted on (typically it will be a single integer pointing + * at itself, but that doesn't need to be the case). + * @type array + */ + "aDataSort": null, + + /** + * Define the sorting directions that are applied to the column, in sequence + * as the column is repeatedly sorted upon - i.e. the first value is used + * as the sorting direction when the column if first sorted (clicked on). + * Sort it again (click again) and it will move on to the next index. + * Repeat until loop. + * @type array + */ + "asSorting": null, + + /** + * Flag to indicate if the column is searchable, and thus should be included + * in the filtering or not. + * @type boolean + */ + "bSearchable": null, + + /** + * Flag to indicate if the column is sortable or not. + * @type boolean + */ + "bSortable": null, + + /** + * Flag to indicate if the column is currently visible in the table or not + * @type boolean + */ + "bVisible": null, + + /** + * Store for manual type assignment using the `column.type` option. This + * is held in store so we can manipulate the column's `sType` property. + * @type string + * @default null + * @private + */ + "_sManualType": null, + + /** + * Flag to indicate if HTML5 data attributes should be used as the data + * source for filtering or sorting. True is either are. + * @type boolean + * @default false + * @private + */ + "_bAttrSrc": false, + + /** + * Developer definable function that is called whenever a cell is created (Ajax source, + * etc) or processed for input (DOM source). This can be used as a compliment to mRender + * allowing you to modify the DOM element (add background colour for example) when the + * element is available. + * @type function + * @param {element} nTd The TD node that has been created + * @param {*} sData The Data for the cell + * @param {array|object} oData The data for the whole row + * @param {int} iRow The row index for the aoData data store + * @default null + */ + "fnCreatedCell": null, + + /** + * Function to get data from a cell in a column. You should <b>never</b> + * access data directly through _aData internally in DataTables - always use + * the method attached to this property. It allows mData to function as + * required. This function is automatically assigned by the column + * initialisation method + * @type function + * @param {array|object} oData The data array/object for the array + * (i.e. aoData[]._aData) + * @param {string} sSpecific The specific data type you want to get - + * 'display', 'type' 'filter' 'sort' + * @returns {*} The data for the cell from the given row's data + * @default null + */ + "fnGetData": null, + + /** + * Function to set data for a cell in the column. You should <b>never</b> + * set the data directly to _aData internally in DataTables - always use + * this method. It allows mData to function as required. This function + * is automatically assigned by the column initialisation method + * @type function + * @param {array|object} oData The data array/object for the array + * (i.e. aoData[]._aData) + * @param {*} sValue Value to set + * @default null + */ + "fnSetData": null, + + /** + * Property to read the value for the cells in the column from the data + * source array / object. If null, then the default content is used, if a + * function is given then the return from the function is used. + * @type function|int|string|null + * @default null + */ + "mData": null, + + /** + * Partner property to mData which is used (only when defined) to get + * the data - i.e. it is basically the same as mData, but without the + * 'set' option, and also the data fed to it is the result from mData. + * This is the rendering method to match the data method of mData. + * @type function|int|string|null + * @default null + */ + "mRender": null, + + /** + * Unique header TH/TD element for this column - this is what the sorting + * listener is attached to (if sorting is enabled.) + * @type node + * @default null + */ + "nTh": null, + + /** + * Unique footer TH/TD element for this column (if there is one). Not used + * in DataTables as such, but can be used for plug-ins to reference the + * footer for each column. + * @type node + * @default null + */ + "nTf": null, + + /** + * The class to apply to all TD elements in the table's TBODY for the column + * @type string + * @default null + */ + "sClass": null, + + /** + * When DataTables calculates the column widths to assign to each column, + * it finds the longest string in each column and then constructs a + * temporary table and reads the widths from that. The problem with this + * is that "mmm" is much wider then "iiii", but the latter is a longer + * string - thus the calculation can go wrong (doing it properly and putting + * it into an DOM object and measuring that is horribly(!) slow). Thus as + * a "work around" we provide this option. It will append its value to the + * text that is found to be the longest string for the column - i.e. padding. + * @type string + */ + "sContentPadding": null, + + /** + * Allows a default value to be given for a column's data, and will be used + * whenever a null data source is encountered (this can be because mData + * is set to null, or because the data source itself is null). + * @type string + * @default null + */ + "sDefaultContent": null, + + /** + * Name for the column, allowing reference to the column by name as well as + * by index (needs a lookup to work by name). + * @type string + */ + "sName": null, + + /** + * Custom sorting data type - defines which of the available plug-ins in + * afnSortData the custom sorting will use - if any is defined. + * @type string + * @default std + */ + "sSortDataType": 'std', + + /** + * Class to be applied to the header element when sorting on this column + * @type string + * @default null + */ + "sSortingClass": null, + + /** + * Class to be applied to the header element when sorting on this column - + * when jQuery UI theming is used. + * @type string + * @default null + */ + "sSortingClassJUI": null, + + /** + * Title of the column - what is seen in the TH element (nTh). + * @type string + */ + "sTitle": null, + + /** + * Column sorting and filtering type + * @type string + * @default null + */ + "sType": null, + + /** + * Width of the column + * @type string + * @default null + */ + "sWidth": null, + + /** + * Width of the column when it was first "encountered" + * @type string + * @default null + */ + "sWidthOrig": null + }; + + + /* + * Developer note: The properties of the object below are given in Hungarian + * notation, that was used as the interface for DataTables prior to v1.10, however + * from v1.10 onwards the primary interface is camel case. In order to avoid + * breaking backwards compatibility utterly with this change, the Hungarian + * version is still, internally the primary interface, but is is not documented + * - hence the @name tags in each doc comment. This allows a Javascript function + * to create a map from Hungarian notation to camel case (going the other direction + * would require each property to be listed, which would at around 3K to the size + * of DataTables, while this method is about a 0.5K hit. + * + * Ultimately this does pave the way for Hungarian notation to be dropped + * completely, but that is a massive amount of work and will break current + * installs (therefore is on-hold until v2). + */ + + /** + * Initialisation options that can be given to DataTables at initialisation + * time. + * @namespace + */ + DataTable.defaults = { + /** + * An array of data to use for the table, passed in at initialisation which + * will be used in preference to any data which is already in the DOM. This is + * particularly useful for constructing tables purely in Javascript, for + * example with a custom Ajax call. + * @type array + * @default null + * + * @dtopt Option + * @name DataTable.defaults.data + * + * @example + * // Using a 2D array data source + * $(document).ready( function () { + * $('#example').dataTable( { + * "data": [ + * ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'], + * ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'], + * ], + * "columns": [ + * { "title": "Engine" }, + * { "title": "Browser" }, + * { "title": "Platform" }, + * { "title": "Version" }, + * { "title": "Grade" } + * ] + * } ); + * } ); + * + * @example + * // Using an array of objects as a data source (`data`) + * $(document).ready( function () { + * $('#example').dataTable( { + * "data": [ + * { + * "engine": "Trident", + * "browser": "Internet Explorer 4.0", + * "platform": "Win 95+", + * "version": 4, + * "grade": "X" + * }, + * { + * "engine": "Trident", + * "browser": "Internet Explorer 5.0", + * "platform": "Win 95+", + * "version": 5, + * "grade": "C" + * } + * ], + * "columns": [ + * { "title": "Engine", "data": "engine" }, + * { "title": "Browser", "data": "browser" }, + * { "title": "Platform", "data": "platform" }, + * { "title": "Version", "data": "version" }, + * { "title": "Grade", "data": "grade" } + * ] + * } ); + * } ); + */ + "aaData": null, + + + /** + * If ordering is enabled, then DataTables will perform a first pass sort on + * initialisation. You can define which column(s) the sort is performed + * upon, and the sorting direction, with this variable. The `sorting` array + * should contain an array for each column to be sorted initially containing + * the column's index and a direction string ('asc' or 'desc'). + * @type array + * @default [[0,'asc']] + * + * @dtopt Option + * @name DataTable.defaults.order + * + * @example + * // Sort by 3rd column first, and then 4th column + * $(document).ready( function() { + * $('#example').dataTable( { + * "order": [[2,'asc'], [3,'desc']] + * } ); + * } ); + * + * // No initial sorting + * $(document).ready( function() { + * $('#example').dataTable( { + * "order": [] + * } ); + * } ); + */ + "aaSorting": [ + [0, 'asc'] + ], + + + /** + * This parameter is basically identical to the `sorting` parameter, but + * cannot be overridden by user interaction with the table. What this means + * is that you could have a column (visible or hidden) which the sorting + * will always be forced on first - any sorting after that (from the user) + * will then be performed as required. This can be useful for grouping rows + * together. + * @type array + * @default null + * + * @dtopt Option + * @name DataTable.defaults.orderFixed + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "orderFixed": [[0,'asc']] + * } ); + * } ) + */ + "aaSortingFixed": [], + + + /** + * DataTables can be instructed to load data to display in the table from a + * Ajax source. This option defines how that Ajax call is made and where to. + * + * The `ajax` property has three different modes of operation, depending on + * how it is defined. These are: + * + * * `string` - Set the URL from where the data should be loaded from. + * * `object` - Define properties for `jQuery.ajax`. + * * `function` - Custom data get function + * + * `string` + * -------- + * + * As a string, the `ajax` property simply defines the URL from which + * DataTables will load data. + * + * `object` + * -------- + * + * As an object, the parameters in the object are passed to + * [jQuery.ajax](http://api.jquery.com/jQuery.ajax/) allowing fine control + * of the Ajax request. DataTables has a number of default parameters which + * you can override using this option. Please refer to the jQuery + * documentation for a full description of the options available, although + * the following parameters provide additional options in DataTables or + * require special consideration: + * + * * `data` - As with jQuery, `data` can be provided as an object, but it + * can also be used as a function to manipulate the data DataTables sends + * to the server. The function takes a single parameter, an object of + * parameters with the values that DataTables has readied for sending. An + * object may be returned which will be merged into the DataTables + * defaults, or you can add the items to the object that was passed in and + * not return anything from the function. This supersedes `fnServerParams` + * from DataTables 1.9-. + * + * * `dataSrc` - By default DataTables will look for the property `data` (or + * `aaData` for compatibility with DataTables 1.9-) when obtaining data + * from an Ajax source or for server-side processing - this parameter + * allows that property to be changed. You can use Javascript dotted + * object notation to get a data source for multiple levels of nesting, or + * it my be used as a function. As a function it takes a single parameter, + * the JSON returned from the server, which can be manipulated as + * required, with the returned value being that used by DataTables as the + * data source for the table. This supersedes `sAjaxDataProp` from + * DataTables 1.9-. + * + * * `success` - Should not be overridden it is used internally in + * DataTables. To manipulate / transform the data returned by the server + * use `ajax.dataSrc`, or use `ajax` as a function (see below). + * + * `function` + * ---------- + * + * As a function, making the Ajax call is left up to yourself allowing + * complete control of the Ajax request. Indeed, if desired, a method other + * than Ajax could be used to obtain the required data, such as Web storage + * or an AIR database. + * + * The function is given four parameters and no return is required. The + * parameters are: + * + * 1. _object_ - Data to send to the server + * 2. _function_ - Callback function that must be executed when the required + * data has been obtained. That data should be passed into the callback + * as the only parameter + * 3. _object_ - DataTables settings object for the table + * + * Note that this supersedes `fnServerData` from DataTables 1.9-. + * + * @type string|object|function + * @default null + * + * @dtopt Option + * @name DataTable.defaults.ajax + * @since 1.10.0 + * + * @example + * // Get JSON data from a file via Ajax. + * // Note DataTables expects data in the form `{ data: [ ...data... ] }` by default). + * $('#example').dataTable( { + * "ajax": "data.json" + * } ); + * + * @example + * // Get JSON data from a file via Ajax, using `dataSrc` to change + * // `data` to `tableData` (i.e. `{ tableData: [ ...data... ] }`) + * $('#example').dataTable( { + * "ajax": { + * "url": "data.json", + * "dataSrc": "tableData" + * } + * } ); + * + * @example + * // Get JSON data from a file via Ajax, using `dataSrc` to read data + * // from a plain array rather than an array in an object + * $('#example').dataTable( { + * "ajax": { + * "url": "data.json", + * "dataSrc": "" + * } + * } ); + * + * @example + * // Manipulate the data returned from the server - add a link to data + * // (note this can, should, be done using `render` for the column - this + * // is just a simple example of how the data can be manipulated). + * $('#example').dataTable( { + * "ajax": { + * "url": "data.json", + * "dataSrc": function ( json ) { + * for ( var i=0, ien=json.length ; i<ien ; i++ ) { + * json[i][0] = '<a href="/message/'+json[i][0]+'>View message</a>'; + * } + * return json; + * } + * } + * } ); + * + * @example + * // Add data to the request + * $('#example').dataTable( { + * "ajax": { + * "url": "data.json", + * "data": function ( d ) { + * return { + * "extra_search": $('#extra').val() + * }; + * } + * } + * } ); + * + * @example + * // Send request as POST + * $('#example').dataTable( { + * "ajax": { + * "url": "data.json", + * "type": "POST" + * } + * } ); + * + * @example + * // Get the data from localStorage (could interface with a form for + * // adding, editing and removing rows). + * $('#example').dataTable( { + * "ajax": function (data, callback, settings) { + * callback( + * JSON.parse( localStorage.getItem('dataTablesData') ) + * ); + * } + * } ); + */ + "ajax": null, + + + /** + * This parameter allows you to readily specify the entries in the length drop + * down menu that DataTables shows when pagination is enabled. It can be + * either a 1D array of options which will be used for both the displayed + * option and the value, or a 2D array which will use the array in the first + * position as the value, and the array in the second position as the + * displayed options (useful for language strings such as 'All'). + * + * Note that the `pageLength` property will be automatically set to the + * first value given in this array, unless `pageLength` is also provided. + * @type array + * @default [ 10, 25, 50, 100 ] + * + * @dtopt Option + * @name DataTable.defaults.lengthMenu + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]] + * } ); + * } ); + */ + "aLengthMenu": [10, 25, 50, 100], + + + /** + * The `columns` option in the initialisation parameter allows you to define + * details about the way individual columns behave. For a full list of + * column options that can be set, please see + * {@link DataTable.defaults.column}. Note that if you use `columns` to + * define your columns, you must have an entry in the array for every single + * column that you have in your table (these can be null if you don't which + * to specify any options). + * @member + * + * @name DataTable.defaults.column + */ + "aoColumns": null, + + /** + * Very similar to `columns`, `columnDefs` allows you to target a specific + * column, multiple columns, or all columns, using the `targets` property of + * each object in the array. This allows great flexibility when creating + * tables, as the `columnDefs` arrays can be of any length, targeting the + * columns you specifically want. `columnDefs` may use any of the column + * options available: {@link DataTable.defaults.column}, but it _must_ + * have `targets` defined in each object in the array. Values in the `targets` + * array may be: + * <ul> + * <li>a string - class name will be matched on the TH for the column</li> + * <li>0 or a positive integer - column index counting from the left</li> + * <li>a negative integer - column index counting from the right</li> + * <li>the string "_all" - all columns (i.e. assign a default)</li> + * </ul> + * @member + * + * @name DataTable.defaults.columnDefs + */ + "aoColumnDefs": null, + + + /** + * Basically the same as `search`, this parameter defines the individual column + * filtering state at initialisation time. The array must be of the same size + * as the number of columns, and each element be an object with the parameters + * `search` and `escapeRegex` (the latter is optional). 'null' is also + * accepted and the default will be used. + * @type array + * @default [] + * + * @dtopt Option + * @name DataTable.defaults.searchCols + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "searchCols": [ + * null, + * { "search": "My filter" }, + * null, + * { "search": "^[0-9]", "escapeRegex": false } + * ] + * } ); + * } ) + */ + "aoSearchCols": [], + + + /** + * An array of CSS classes that should be applied to displayed rows. This + * array may be of any length, and DataTables will apply each class + * sequentially, looping when required. + * @type array + * @default null <i>Will take the values determined by the `oClasses.stripe*` + * options</i> + * + * @dtopt Option + * @name DataTable.defaults.stripeClasses + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "stripeClasses": [ 'strip1', 'strip2', 'strip3' ] + * } ); + * } ) + */ + "asStripeClasses": null, + + + /** + * Enable or disable automatic column width calculation. This can be disabled + * as an optimisation (it takes some time to calculate the widths) if the + * tables widths are passed in using `columns`. + * @type boolean + * @default true + * + * @dtopt Features + * @name DataTable.defaults.autoWidth + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "autoWidth": false + * } ); + * } ); + */ + "bAutoWidth": true, + + + /** + * Deferred rendering can provide DataTables with a huge speed boost when you + * are using an Ajax or JS data source for the table. This option, when set to + * true, will cause DataTables to defer the creation of the table elements for + * each row until they are needed for a draw - saving a significant amount of + * time. + * @type boolean + * @default false + * + * @dtopt Features + * @name DataTable.defaults.deferRender + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "ajax": "sources/arrays.txt", + * "deferRender": true + * } ); + * } ); + */ + "bDeferRender": false, + + + /** + * Replace a DataTable which matches the given selector and replace it with + * one which has the properties of the new initialisation object passed. If no + * table matches the selector, then the new DataTable will be constructed as + * per normal. + * @type boolean + * @default false + * + * @dtopt Options + * @name DataTable.defaults.destroy + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "srollY": "200px", + * "paginate": false + * } ); + * + * // Some time later.... + * $('#example').dataTable( { + * "filter": false, + * "destroy": true + * } ); + * } ); + */ + "bDestroy": false, + + + /** + * Enable or disable filtering of data. Filtering in DataTables is "smart" in + * that it allows the end user to input multiple words (space separated) and + * will match a row containing those words, even if not in the order that was + * specified (this allow matching across multiple columns). Note that if you + * wish to use filtering in DataTables this must remain 'true' - to remove the + * default filtering input box and retain filtering abilities, please use + * {@link DataTable.defaults.dom}. + * @type boolean + * @default true + * + * @dtopt Features + * @name DataTable.defaults.searching + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "searching": false + * } ); + * } ); + */ + "bFilter": true, + + + /** + * Enable or disable the table information display. This shows information + * about the data that is currently visible on the page, including information + * about filtered data if that action is being performed. + * @type boolean + * @default true + * + * @dtopt Features + * @name DataTable.defaults.info + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "info": false + * } ); + * } ); + */ + "bInfo": true, + + + /** + * Enable jQuery UI ThemeRoller support (required as ThemeRoller requires some + * slightly different and additional mark-up from what DataTables has + * traditionally used). + * @type boolean + * @default false + * + * @dtopt Features + * @name DataTable.defaults.jQueryUI + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "jQueryUI": true + * } ); + * } ); + */ + "bJQueryUI": false, + + + /** + * Allows the end user to select the size of a formatted page from a select + * menu (sizes are 10, 25, 50 and 100). Requires pagination (`paginate`). + * @type boolean + * @default true + * + * @dtopt Features + * @name DataTable.defaults.lengthChange + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "lengthChange": false + * } ); + * } ); + */ + "bLengthChange": true, + + + /** + * Enable or disable pagination. + * @type boolean + * @default true + * + * @dtopt Features + * @name DataTable.defaults.paging + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "paging": false + * } ); + * } ); + */ + "bPaginate": true, + + + /** + * Enable or disable the display of a 'processing' indicator when the table is + * being processed (e.g. a sort). This is particularly useful for tables with + * large amounts of data where it can take a noticeable amount of time to sort + * the entries. + * @type boolean + * @default false + * + * @dtopt Features + * @name DataTable.defaults.processing + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "processing": true + * } ); + * } ); + */ + "bProcessing": false, + + + /** + * Retrieve the DataTables object for the given selector. Note that if the + * table has already been initialised, this parameter will cause DataTables + * to simply return the object that has already been set up - it will not take + * account of any changes you might have made to the initialisation object + * passed to DataTables (setting this parameter to true is an acknowledgement + * that you understand this). `destroy` can be used to reinitialise a table if + * you need. + * @type boolean + * @default false + * + * @dtopt Options + * @name DataTable.defaults.retrieve + * + * @example + * $(document).ready( function() { + * initTable(); + * tableActions(); + * } ); + * + * function initTable () + * { + * return $('#example').dataTable( { + * "scrollY": "200px", + * "paginate": false, + * "retrieve": true + * } ); + * } + * + * function tableActions () + * { + * var table = initTable(); + * // perform API operations with oTable + * } + */ + "bRetrieve": false, + + + /** + * When vertical (y) scrolling is enabled, DataTables will force the height of + * the table's viewport to the given height at all times (useful for layout). + * However, this can look odd when filtering data down to a small data set, + * and the footer is left "floating" further down. This parameter (when + * enabled) will cause DataTables to collapse the table's viewport down when + * the result set will fit within the given Y height. + * @type boolean + * @default false + * + * @dtopt Options + * @name DataTable.defaults.scrollCollapse + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "scrollY": "200", + * "scrollCollapse": true + * } ); + * } ); + */ + "bScrollCollapse": false, + + + /** + * Configure DataTables to use server-side processing. Note that the + * `ajax` parameter must also be given in order to give DataTables a + * source to obtain the required data for each draw. + * @type boolean + * @default false + * + * @dtopt Features + * @dtopt Server-side + * @name DataTable.defaults.serverSide + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "serverSide": true, + * "ajax": "xhr.php" + * } ); + * } ); + */ + "bServerSide": false, + + + /** + * Enable or disable sorting of columns. Sorting of individual columns can be + * disabled by the `sortable` option for each column. + * @type boolean + * @default true + * + * @dtopt Features + * @name DataTable.defaults.ordering + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "ordering": false + * } ); + * } ); + */ + "bSort": true, + + + /** + * Enable or display DataTables' ability to sort multiple columns at the + * same time (activated by shift-click by the user). + * @type boolean + * @default true + * + * @dtopt Options + * @name DataTable.defaults.orderMulti + * + * @example + * // Disable multiple column sorting ability + * $(document).ready( function () { + * $('#example').dataTable( { + * "orderMulti": false + * } ); + * } ); + */ + "bSortMulti": true, + + + /** + * Allows control over whether DataTables should use the top (true) unique + * cell that is found for a single column, or the bottom (false - default). + * This is useful when using complex headers. + * @type boolean + * @default false + * + * @dtopt Options + * @name DataTable.defaults.orderCellsTop + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "orderCellsTop": true + * } ); + * } ); + */ + "bSortCellsTop": false, + + + /** + * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and + * `sorting\_3` to the columns which are currently being sorted on. This is + * presented as a feature switch as it can increase processing time (while + * classes are removed and added) so for large data sets you might want to + * turn this off. + * @type boolean + * @default true + * + * @dtopt Features + * @name DataTable.defaults.orderClasses + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "orderClasses": false + * } ); + * } ); + */ + "bSortClasses": true, + + + /** + * Enable or disable state saving. When enabled HTML5 `localStorage` will be + * used to save table display information such as pagination information, + * display length, filtering and sorting. As such when the end user reloads + * the page the display display will match what thy had previously set up. + * + * Due to the use of `localStorage` the default state saving is not supported + * in IE6 or 7. If state saving is required in those browsers, use + * `stateSaveCallback` to provide a storage solution such as cookies. + * @type boolean + * @default false + * + * @dtopt Features + * @name DataTable.defaults.stateSave + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "stateSave": true + * } ); + * } ); + */ + "bStateSave": false, + + + /** + * This function is called when a TR element is created (and all TD child + * elements have been inserted), or registered if using a DOM source, allowing + * manipulation of the TR element (adding classes etc). + * @type function + * @param {node} row "TR" element for the current row + * @param {array} data Raw data array for this row + * @param {int} dataIndex The index of this row in the internal aoData array + * + * @dtopt Callbacks + * @name DataTable.defaults.createdRow + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "createdRow": function( row, data, dataIndex ) { + * // Bold the grade for all 'A' grade browsers + * if ( data[4] == "A" ) + * { + * $('td:eq(4)', row).html( '<b>A</b>' ); + * } + * } + * } ); + * } ); + */ + "fnCreatedRow": null, + + + /** + * This function is called on every 'draw' event, and allows you to + * dynamically modify any aspect you want about the created DOM. + * @type function + * @param {object} settings DataTables settings object + * + * @dtopt Callbacks + * @name DataTable.defaults.drawCallback + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "drawCallback": function( settings ) { + * alert( 'DataTables has redrawn the table' ); + * } + * } ); + * } ); + */ + "fnDrawCallback": null, + + + /** + * Identical to fnHeaderCallback() but for the table footer this function + * allows you to modify the table footer on every 'draw' event. + * @type function + * @param {node} foot "TR" element for the footer + * @param {array} data Full table data (as derived from the original HTML) + * @param {int} start Index for the current display starting point in the + * display array + * @param {int} end Index for the current display ending point in the + * display array + * @param {array int} display Index array to translate the visual position + * to the full data array + * + * @dtopt Callbacks + * @name DataTable.defaults.footerCallback + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "footerCallback": function( tfoot, data, start, end, display ) { + * tfoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+start; + * } + * } ); + * } ) + */ + "fnFooterCallback": null, + + + /** + * When rendering large numbers in the information element for the table + * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers + * to have a comma separator for the 'thousands' units (e.g. 1 million is + * rendered as "1,000,000") to help readability for the end user. This + * function will override the default method DataTables uses. + * @type function + * @member + * @param {int} toFormat number to be formatted + * @returns {string} formatted string for DataTables to show the number + * + * @dtopt Callbacks + * @name DataTable.defaults.formatNumber + * + * @example + * // Format a number using a single quote for the separator (note that + * // this can also be done with the language.thousands option) + * $(document).ready( function() { + * $('#example').dataTable( { + * "formatNumber": function ( toFormat ) { + * return toFormat.toString().replace( + * /\B(?=(\d{3})+(?!\d))/g, "'" + * ); + * }; + * } ); + * } ); + */ + "fnFormatNumber": function (toFormat) { + return toFormat.toString().replace( + /\B(?=(\d{3})+(?!\d))/g, + this.oLanguage.sThousands + ); + }, + + + /** + * This function is called on every 'draw' event, and allows you to + * dynamically modify the header row. This can be used to calculate and + * display useful information about the table. + * @type function + * @param {node} head "TR" element for the header + * @param {array} data Full table data (as derived from the original HTML) + * @param {int} start Index for the current display starting point in the + * display array + * @param {int} end Index for the current display ending point in the + * display array + * @param {array int} display Index array to translate the visual position + * to the full data array + * + * @dtopt Callbacks + * @name DataTable.defaults.headerCallback + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "fheaderCallback": function( head, data, start, end, display ) { + * head.getElementsByTagName('th')[0].innerHTML = "Displaying "+(end-start)+" records"; + * } + * } ); + * } ) + */ + "fnHeaderCallback": null, + + + /** + * The information element can be used to convey information about the current + * state of the table. Although the internationalisation options presented by + * DataTables are quite capable of dealing with most customisations, there may + * be times where you wish to customise the string further. This callback + * allows you to do exactly that. + * @type function + * @param {object} oSettings DataTables settings object + * @param {int} start Starting position in data for the draw + * @param {int} end End position in data for the draw + * @param {int} max Total number of rows in the table (regardless of + * filtering) + * @param {int} total Total number of rows in the data set, after filtering + * @param {string} pre The string that DataTables has formatted using it's + * own rules + * @returns {string} The string to be displayed in the information element. + * + * @dtopt Callbacks + * @name DataTable.defaults.infoCallback + * + * @example + * $('#example').dataTable( { + * "infoCallback": function( settings, start, end, max, total, pre ) { + * return start +" to "+ end; + * } + * } ); + */ + "fnInfoCallback": null, + + + /** + * Called when the table has been initialised. Normally DataTables will + * initialise sequentially and there will be no need for this function, + * however, this does not hold true when using external language information + * since that is obtained using an async XHR call. + * @type function + * @param {object} settings DataTables settings object + * @param {object} json The JSON object request from the server - only + * present if client-side Ajax sourced data is used + * + * @dtopt Callbacks + * @name DataTable.defaults.initComplete + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "initComplete": function(settings, json) { + * alert( 'DataTables has finished its initialisation.' ); + * } + * } ); + * } ) + */ + "fnInitComplete": null, + + + /** + * Called at the very start of each table draw and can be used to cancel the + * draw by returning false, any other return (including undefined) results in + * the full draw occurring). + * @type function + * @param {object} settings DataTables settings object + * @returns {boolean} False will cancel the draw, anything else (including no + * return) will allow it to complete. + * + * @dtopt Callbacks + * @name DataTable.defaults.preDrawCallback + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "preDrawCallback": function( settings ) { + * if ( $('#test').val() == 1 ) { + * return false; + * } + * } + * } ); + * } ); + */ + "fnPreDrawCallback": null, + + + /** + * This function allows you to 'post process' each row after it have been + * generated for each table draw, but before it is rendered on screen. This + * function might be used for setting the row class name etc. + * @type function + * @param {node} row "TR" element for the current row + * @param {array} data Raw data array for this row + * @param {int} displayIndex The display index for the current table draw + * @param {int} displayIndexFull The index of the data in the full list of + * rows (after filtering) + * + * @dtopt Callbacks + * @name DataTable.defaults.rowCallback + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "rowCallback": function( row, data, displayIndex, displayIndexFull ) { + * // Bold the grade for all 'A' grade browsers + * if ( data[4] == "A" ) { + * $('td:eq(4)', row).html( '<b>A</b>' ); + * } + * } + * } ); + * } ); + */ + "fnRowCallback": null, + + + /** + * __Deprecated__ The functionality provided by this parameter has now been + * superseded by that provided through `ajax`, which should be used instead. + * + * This parameter allows you to override the default function which obtains + * the data from the server so something more suitable for your application. + * For example you could use POST data, or pull information from a Gears or + * AIR database. + * @type function + * @member + * @param {string} source HTTP source to obtain the data from (`ajax`) + * @param {array} data A key/value pair object containing the data to send + * to the server + * @param {function} callback to be called on completion of the data get + * process that will draw the data on the page. + * @param {object} settings DataTables settings object + * + * @dtopt Callbacks + * @dtopt Server-side + * @name DataTable.defaults.serverData + * + * @deprecated 1.10. Please use `ajax` for this functionality now. + */ + "fnServerData": null, + + + /** + * __Deprecated__ The functionality provided by this parameter has now been + * superseded by that provided through `ajax`, which should be used instead. + * + * It is often useful to send extra data to the server when making an Ajax + * request - for example custom filtering information, and this callback + * function makes it trivial to send extra information to the server. The + * passed in parameter is the data set that has been constructed by + * DataTables, and you can add to this or modify it as you require. + * @type function + * @param {array} data Data array (array of objects which are name/value + * pairs) that has been constructed by DataTables and will be sent to the + * server. In the case of Ajax sourced data with server-side processing + * this will be an empty array, for server-side processing there will be a + * significant number of parameters! + * @returns {undefined} Ensure that you modify the data array passed in, + * as this is passed by reference. + * + * @dtopt Callbacks + * @dtopt Server-side + * @name DataTable.defaults.serverParams + * + * @deprecated 1.10. Please use `ajax` for this functionality now. + */ + "fnServerParams": null, + + + /** + * Load the table state. With this function you can define from where, and how, the + * state of a table is loaded. By default DataTables will load from `localStorage` + * but you might wish to use a server-side database or cookies. + * @type function + * @member + * @param {object} settings DataTables settings object + * @return {object} The DataTables state object to be loaded + * + * @dtopt Callbacks + * @name DataTable.defaults.stateLoadCallback + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "stateSave": true, + * "stateLoadCallback": function (settings) { + * var o; + * + * // Send an Ajax request to the server to get the data. Note that + * // this is a synchronous request. + * $.ajax( { + * "url": "/state_load", + * "async": false, + * "dataType": "json", + * "success": function (json) { + * o = json; + * } + * } ); + * + * return o; + * } + * } ); + * } ); + */ + "fnStateLoadCallback": function (settings) { + try { + return JSON.parse( + (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem( + 'DataTables_' + settings.sInstance + '_' + location.pathname + ) + ); + } catch (e) {} + }, + + + /** + * Callback which allows modification of the saved state prior to loading that state. + * This callback is called when the table is loading state from the stored data, but + * prior to the settings object being modified by the saved state. Note that for + * plug-in authors, you should use the `stateLoadParams` event to load parameters for + * a plug-in. + * @type function + * @param {object} settings DataTables settings object + * @param {object} data The state object that is to be loaded + * + * @dtopt Callbacks + * @name DataTable.defaults.stateLoadParams + * + * @example + * // Remove a saved filter, so filtering is never loaded + * $(document).ready( function() { + * $('#example').dataTable( { + * "stateSave": true, + * "stateLoadParams": function (settings, data) { + * data.oSearch.sSearch = ""; + * } + * } ); + * } ); + * + * @example + * // Disallow state loading by returning false + * $(document).ready( function() { + * $('#example').dataTable( { + * "stateSave": true, + * "stateLoadParams": function (settings, data) { + * return false; + * } + * } ); + * } ); + */ + "fnStateLoadParams": null, + + + /** + * Callback that is called when the state has been loaded from the state saving method + * and the DataTables settings object has been modified as a result of the loaded state. + * @type function + * @param {object} settings DataTables settings object + * @param {object} data The state object that was loaded + * + * @dtopt Callbacks + * @name DataTable.defaults.stateLoaded + * + * @example + * // Show an alert with the filtering value that was saved + * $(document).ready( function() { + * $('#example').dataTable( { + * "stateSave": true, + * "stateLoaded": function (settings, data) { + * alert( 'Saved filter was: '+data.oSearch.sSearch ); + * } + * } ); + * } ); + */ + "fnStateLoaded": null, + + + /** + * Save the table state. This function allows you to define where and how the state + * information for the table is stored By default DataTables will use `localStorage` + * but you might wish to use a server-side database or cookies. + * @type function + * @member + * @param {object} settings DataTables settings object + * @param {object} data The state object to be saved + * + * @dtopt Callbacks + * @name DataTable.defaults.stateSaveCallback + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "stateSave": true, + * "stateSaveCallback": function (settings, data) { + * // Send an Ajax request to the server with the state object + * $.ajax( { + * "url": "/state_save", + * "data": data, + * "dataType": "json", + * "method": "POST" + * "success": function () {} + * } ); + * } + * } ); + * } ); + */ + "fnStateSaveCallback": function (settings, data) { + try { + (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem( + 'DataTables_' + settings.sInstance + '_' + location.pathname, + JSON.stringify(data) + ); + } catch (e) {} + }, + + + /** + * Callback which allows modification of the state to be saved. Called when the table + * has changed state a new state save is required. This method allows modification of + * the state saving object prior to actually doing the save, including addition or + * other state properties or modification. Note that for plug-in authors, you should + * use the `stateSaveParams` event to save parameters for a plug-in. + * @type function + * @param {object} settings DataTables settings object + * @param {object} data The state object to be saved + * + * @dtopt Callbacks + * @name DataTable.defaults.stateSaveParams + * + * @example + * // Remove a saved filter, so filtering is never saved + * $(document).ready( function() { + * $('#example').dataTable( { + * "stateSave": true, + * "stateSaveParams": function (settings, data) { + * data.oSearch.sSearch = ""; + * } + * } ); + * } ); + */ + "fnStateSaveParams": null, + + + /** + * Duration for which the saved state information is considered valid. After this period + * has elapsed the state will be returned to the default. + * Value is given in seconds. + * @type int + * @default 7200 <i>(2 hours)</i> + * + * @dtopt Options + * @name DataTable.defaults.stateDuration + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "stateDuration": 60*60*24; // 1 day + * } ); + * } ) + */ + "iStateDuration": 7200, + + + /** + * When enabled DataTables will not make a request to the server for the first + * page draw - rather it will use the data already on the page (no sorting etc + * will be applied to it), thus saving on an XHR at load time. `deferLoading` + * is used to indicate that deferred loading is required, but it is also used + * to tell DataTables how many records there are in the full table (allowing + * the information element and pagination to be displayed correctly). In the case + * where a filtering is applied to the table on initial load, this can be + * indicated by giving the parameter as an array, where the first element is + * the number of records available after filtering and the second element is the + * number of records without filtering (allowing the table information element + * to be shown correctly). + * @type int | array + * @default null + * + * @dtopt Options + * @name DataTable.defaults.deferLoading + * + * @example + * // 57 records available in the table, no filtering applied + * $(document).ready( function() { + * $('#example').dataTable( { + * "serverSide": true, + * "ajax": "scripts/server_processing.php", + * "deferLoading": 57 + * } ); + * } ); + * + * @example + * // 57 records after filtering, 100 without filtering (an initial filter applied) + * $(document).ready( function() { + * $('#example').dataTable( { + * "serverSide": true, + * "ajax": "scripts/server_processing.php", + * "deferLoading": [ 57, 100 ], + * "search": { + * "search": "my_filter" + * } + * } ); + * } ); + */ + "iDeferLoading": null, + + + /** + * Number of rows to display on a single page when using pagination. If + * feature enabled (`lengthChange`) then the end user will be able to override + * this to a custom setting using a pop-up menu. + * @type int + * @default 10 + * + * @dtopt Options + * @name DataTable.defaults.pageLength + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "pageLength": 50 + * } ); + * } ) + */ + "iDisplayLength": 10, + + + /** + * Define the starting point for data display when using DataTables with + * pagination. Note that this parameter is the number of records, rather than + * the page number, so if you have 10 records per page and want to start on + * the third page, it should be "20". + * @type int + * @default 0 + * + * @dtopt Options + * @name DataTable.defaults.displayStart + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "displayStart": 20 + * } ); + * } ) + */ + "iDisplayStart": 0, + + + /** + * By default DataTables allows keyboard navigation of the table (sorting, paging, + * and filtering) by adding a `tabindex` attribute to the required elements. This + * allows you to tab through the controls and press the enter key to activate them. + * The tabindex is default 0, meaning that the tab follows the flow of the document. + * You can overrule this using this parameter if you wish. Use a value of -1 to + * disable built-in keyboard navigation. + * @type int + * @default 0 + * + * @dtopt Options + * @name DataTable.defaults.tabIndex + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "tabIndex": 1 + * } ); + * } ); + */ + "iTabIndex": 0, + + + /** + * Classes that DataTables assigns to the various components and features + * that it adds to the HTML table. This allows classes to be configured + * during initialisation in addition to through the static + * {@link DataTable.ext.oStdClasses} object). + * @namespace + * @name DataTable.defaults.classes + */ + "oClasses": {}, + + + /** + * All strings that DataTables uses in the user interface that it creates + * are defined in this object, allowing you to modified them individually or + * completely replace them all as required. + * @namespace + * @name DataTable.defaults.language + */ + "oLanguage": { + /** + * Strings that are used for WAI-ARIA labels and controls only (these are not + * actually visible on the page, but will be read by screenreaders, and thus + * must be internationalised as well). + * @namespace + * @name DataTable.defaults.language.aria + */ + "oAria": { + /** + * ARIA label that is added to the table headers when the column may be + * sorted ascending by activing the column (click or return when focused). + * Note that the column header is prefixed to this string. + * @type string + * @default : activate to sort column ascending + * + * @dtopt Language + * @name DataTable.defaults.language.aria.sortAscending + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "aria": { + * "sortAscending": " - click/return to sort ascending" + * } + * } + * } ); + * } ); + */ + "sSortAscending": ": activate to sort column ascending", + + /** + * ARIA label that is added to the table headers when the column may be + * sorted descending by activing the column (click or return when focused). + * Note that the column header is prefixed to this string. + * @type string + * @default : activate to sort column ascending + * + * @dtopt Language + * @name DataTable.defaults.language.aria.sortDescending + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "aria": { + * "sortDescending": " - click/return to sort descending" + * } + * } + * } ); + * } ); + */ + "sSortDescending": ": activate to sort column descending" + }, + + /** + * Pagination string used by DataTables for the built-in pagination + * control types. + * @namespace + * @name DataTable.defaults.language.paginate + */ + "oPaginate": { + /** + * Text to use when using the 'full_numbers' type of pagination for the + * button to take the user to the first page. + * @type string + * @default First + * + * @dtopt Language + * @name DataTable.defaults.language.paginate.first + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "paginate": { + * "first": "First page" + * } + * } + * } ); + * } ); + */ + "sFirst": "First", + + + /** + * Text to use when using the 'full_numbers' type of pagination for the + * button to take the user to the last page. + * @type string + * @default Last + * + * @dtopt Language + * @name DataTable.defaults.language.paginate.last + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "paginate": { + * "last": "Last page" + * } + * } + * } ); + * } ); + */ + "sLast": "Last", + + + /** + * Text to use for the 'next' pagination button (to take the user to the + * next page). + * @type string + * @default Next + * + * @dtopt Language + * @name DataTable.defaults.language.paginate.next + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "paginate": { + * "next": "Next page" + * } + * } + * } ); + * } ); + */ + "sNext": "Next", + + + /** + * Text to use for the 'previous' pagination button (to take the user to + * the previous page). + * @type string + * @default Previous + * + * @dtopt Language + * @name DataTable.defaults.language.paginate.previous + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "paginate": { + * "previous": "Previous page" + * } + * } + * } ); + * } ); + */ + "sPrevious": "Previous" + }, + + /** + * This string is shown in preference to `zeroRecords` when the table is + * empty of data (regardless of filtering). Note that this is an optional + * parameter - if it is not given, the value of `zeroRecords` will be used + * instead (either the default or given value). + * @type string + * @default No data available in table + * + * @dtopt Language + * @name DataTable.defaults.language.emptyTable + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "emptyTable": "No data available in table" + * } + * } ); + * } ); + */ + "sEmptyTable": "No data available in table", + + + /** + * This string gives information to the end user about the information + * that is current on display on the page. The following tokens can be + * used in the string and will be dynamically replaced as the table + * display updates. This tokens can be placed anywhere in the string, or + * removed as needed by the language requires: + * + * * `\_START\_` - Display index of the first record on the current page + * * `\_END\_` - Display index of the last record on the current page + * * `\_TOTAL\_` - Number of records in the table after filtering + * * `\_MAX\_` - Number of records in the table without filtering + * * `\_PAGE\_` - Current page number + * * `\_PAGES\_` - Total number of pages of data in the table + * + * @type string + * @default Showing _START_ to _END_ of _TOTAL_ entries + * + * @dtopt Language + * @name DataTable.defaults.language.info + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "info": "Showing page _PAGE_ of _PAGES_" + * } + * } ); + * } ); + */ + "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries", + + + /** + * Display information string for when the table is empty. Typically the + * format of this string should match `info`. + * @type string + * @default Showing 0 to 0 of 0 entries + * + * @dtopt Language + * @name DataTable.defaults.language.infoEmpty + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "infoEmpty": "No entries to show" + * } + * } ); + * } ); + */ + "sInfoEmpty": "Showing 0 to 0 of 0 entries", + + + /** + * When a user filters the information in a table, this string is appended + * to the information (`info`) to give an idea of how strong the filtering + * is. The variable _MAX_ is dynamically updated. + * @type string + * @default (filtered from _MAX_ total entries) + * + * @dtopt Language + * @name DataTable.defaults.language.infoFiltered + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "infoFiltered": " - filtering from _MAX_ records" + * } + * } ); + * } ); + */ + "sInfoFiltered": "(filtered from _MAX_ total entries)", + + + /** + * If can be useful to append extra information to the info string at times, + * and this variable does exactly that. This information will be appended to + * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are + * being used) at all times. + * @type string + * @default <i>Empty string</i> + * + * @dtopt Language + * @name DataTable.defaults.language.infoPostFix + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "infoPostFix": "All records shown are derived from real information." + * } + * } ); + * } ); + */ + "sInfoPostFix": "", + + + /** + * This decimal place operator is a little different from the other + * language options since DataTables doesn't output floating point + * numbers, so it won't ever use this for display of a number. Rather, + * what this parameter does is modify the sort methods of the table so + * that numbers which are in a format which has a character other than + * a period (`.`) as a decimal place will be sorted numerically. + * + * Note that numbers with different decimal places cannot be shown in + * the same table and still be sortable, the table must be consistent. + * However, multiple different tables on the page can use different + * decimal place characters. + * @type string + * @default + * + * @dtopt Language + * @name DataTable.defaults.language.decimal + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "decimal": "," + * "thousands": "." + * } + * } ); + * } ); + */ + "sDecimal": "", + + + /** + * DataTables has a build in number formatter (`formatNumber`) which is + * used to format large numbers that are used in the table information. + * By default a comma is used, but this can be trivially changed to any + * character you wish with this parameter. + * @type string + * @default , + * + * @dtopt Language + * @name DataTable.defaults.language.thousands + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "thousands": "'" + * } + * } ); + * } ); + */ + "sThousands": ",", + + + /** + * Detail the action that will be taken when the drop down menu for the + * pagination length option is changed. The '_MENU_' variable is replaced + * with a default select list of 10, 25, 50 and 100, and can be replaced + * with a custom select box if required. + * @type string + * @default Show _MENU_ entries + * + * @dtopt Language + * @name DataTable.defaults.language.lengthMenu + * + * @example + * // Language change only + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "lengthMenu": "Display _MENU_ records" + * } + * } ); + * } ); + * + * @example + * // Language and options change + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "lengthMenu": 'Display <select>'+ + * '<option value="10">10</option>'+ + * '<option value="20">20</option>'+ + * '<option value="30">30</option>'+ + * '<option value="40">40</option>'+ + * '<option value="50">50</option>'+ + * '<option value="-1">All</option>'+ + * '</select> records' + * } + * } ); + * } ); + */ + "sLengthMenu": "Show _MENU_ entries", + + + /** + * When using Ajax sourced data and during the first draw when DataTables is + * gathering the data, this message is shown in an empty row in the table to + * indicate to the end user the the data is being loaded. Note that this + * parameter is not used when loading data by server-side processing, just + * Ajax sourced data with client-side processing. + * @type string + * @default Loading... + * + * @dtopt Language + * @name DataTable.defaults.language.loadingRecords + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "loadingRecords": "Please wait - loading..." + * } + * } ); + * } ); + */ + "sLoadingRecords": "Loading...", + + + /** + * Text which is displayed when the table is processing a user action + * (usually a sort command or similar). + * @type string + * @default Processing... + * + * @dtopt Language + * @name DataTable.defaults.language.processing + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "processing": "DataTables is currently busy" + * } + * } ); + * } ); + */ + "sProcessing": "Processing...", + + + /** + * Details the actions that will be taken when the user types into the + * filtering input text box. The variable "_INPUT_", if used in the string, + * is replaced with the HTML text box for the filtering input allowing + * control over where it appears in the string. If "_INPUT_" is not given + * then the input box is appended to the string automatically. + * @type string + * @default Search: + * + * @dtopt Language + * @name DataTable.defaults.language.search + * + * @example + * // Input text box will be appended at the end automatically + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "search": "Filter records:" + * } + * } ); + * } ); + * + * @example + * // Specify where the filter should appear + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "search": "Apply filter _INPUT_ to table" + * } + * } ); + * } ); + */ + "sSearch": "Search:", + + + /** + * Assign a `placeholder` attribute to the search `input` element + * @type string + * @default + * + * @dtopt Language + * @name DataTable.defaults.language.searchPlaceholder + */ + "sSearchPlaceholder": "", + + + /** + * All of the language information can be stored in a file on the + * server-side, which DataTables will look up if this parameter is passed. + * It must store the URL of the language file, which is in a JSON format, + * and the object has the same properties as the oLanguage object in the + * initialiser object (i.e. the above parameters). Please refer to one of + * the example language files to see how this works in action. + * @type string + * @default <i>Empty string - i.e. disabled</i> + * + * @dtopt Language + * @name DataTable.defaults.language.url + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "url": "http://www.sprymedia.co.uk/dataTables/lang.txt" + * } + * } ); + * } ); + */ + "sUrl": "", + + + /** + * Text shown inside the table records when the is no information to be + * displayed after filtering. `emptyTable` is shown when there is simply no + * information in the table at all (regardless of filtering). + * @type string + * @default No matching records found + * + * @dtopt Language + * @name DataTable.defaults.language.zeroRecords + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "zeroRecords": "No records to display" + * } + * } ); + * } ); + */ + "sZeroRecords": "No matching records found" + }, + + + /** + * This parameter allows you to have define the global filtering state at + * initialisation time. As an object the `search` parameter must be + * defined, but all other parameters are optional. When `regex` is true, + * the search string will be treated as a regular expression, when false + * (default) it will be treated as a straight string. When `smart` + * DataTables will use it's smart filtering methods (to word match at + * any point in the data), when false this will not be done. + * @namespace + * @extends DataTable.models.oSearch + * + * @dtopt Options + * @name DataTable.defaults.search + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "search": {"search": "Initial search"} + * } ); + * } ) + */ + "oSearch": $.extend({}, DataTable.models.oSearch), + + + /** + * __Deprecated__ The functionality provided by this parameter has now been + * superseded by that provided through `ajax`, which should be used instead. + * + * By default DataTables will look for the property `data` (or `aaData` for + * compatibility with DataTables 1.9-) when obtaining data from an Ajax + * source or for server-side processing - this parameter allows that + * property to be changed. You can use Javascript dotted object notation to + * get a data source for multiple levels of nesting. + * @type string + * @default data + * + * @dtopt Options + * @dtopt Server-side + * @name DataTable.defaults.ajaxDataProp + * + * @deprecated 1.10. Please use `ajax` for this functionality now. + */ + "sAjaxDataProp": "data", + + + /** + * __Deprecated__ The functionality provided by this parameter has now been + * superseded by that provided through `ajax`, which should be used instead. + * + * You can instruct DataTables to load data from an external + * source using this parameter (use aData if you want to pass data in you + * already have). Simply provide a url a JSON object can be obtained from. + * @type string + * @default null + * + * @dtopt Options + * @dtopt Server-side + * @name DataTable.defaults.ajaxSource + * + * @deprecated 1.10. Please use `ajax` for this functionality now. + */ + "sAjaxSource": null, + + + /** + * This initialisation variable allows you to specify exactly where in the + * DOM you want DataTables to inject the various controls it adds to the page + * (for example you might want the pagination controls at the top of the + * table). DIV elements (with or without a custom class) can also be added to + * aid styling. The follow syntax is used: + * <ul> + * <li>The following options are allowed: + * <ul> + * <li>'l' - Length changing</li> + * <li>'f' - Filtering input</li> + * <li>'t' - The table!</li> + * <li>'i' - Information</li> + * <li>'p' - Pagination</li> + * <li>'r' - pRocessing</li> + * </ul> + * </li> + * <li>The following constants are allowed: + * <ul> + * <li>'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')</li> + * <li>'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')</li> + * </ul> + * </li> + * <li>The following syntax is expected: + * <ul> + * <li>'<' and '>' - div elements</li> + * <li>'<"class" and '>' - div with a class</li> + * <li>'<"#id" and '>' - div with an ID</li> + * </ul> + * </li> + * <li>Examples: + * <ul> + * <li>'<"wrapper"flipt>'</li> + * <li>'<lf<t>ip>'</li> + * </ul> + * </li> + * </ul> + * @type string + * @default lfrtip <i>(when `jQueryUI` is false)</i> <b>or</b> + * <"H"lfr>t<"F"ip> <i>(when `jQueryUI` is true)</i> + * + * @dtopt Options + * @name DataTable.defaults.dom + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "dom": '<"top"i>rt<"bottom"flp><"clear">' + * } ); + * } ); + */ + "sDom": "lfrtip", + + + /** + * Search delay option. This will throttle full table searches that use the + * DataTables provided search input element (it does not effect calls to + * `dt-api search()`, providing a delay before the search is made. + * @type integer + * @default 0 + * + * @dtopt Options + * @name DataTable.defaults.searchDelay + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "searchDelay": 200 + * } ); + * } ) + */ + "searchDelay": null, + + + /** + * DataTables features four different built-in options for the buttons to + * display for pagination control: + * + * * `simple` - 'Previous' and 'Next' buttons only + * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers + * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons + * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus + * page numbers + * + * Further methods can be added using {@link DataTable.ext.oPagination}. + * @type string + * @default simple_numbers + * + * @dtopt Options + * @name DataTable.defaults.pagingType + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "pagingType": "full_numbers" + * } ); + * } ) + */ + "sPaginationType": "simple_numbers", + + + /** + * Enable horizontal scrolling. When a table is too wide to fit into a + * certain layout, or you have a large number of columns in the table, you + * can enable x-scrolling to show the table in a viewport, which can be + * scrolled. This property can be `true` which will allow the table to + * scroll horizontally when needed, or any CSS unit, or a number (in which + * case it will be treated as a pixel measurement). Setting as simply `true` + * is recommended. + * @type boolean|string + * @default <i>blank string - i.e. disabled</i> + * + * @dtopt Features + * @name DataTable.defaults.scrollX + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "scrollX": true, + * "scrollCollapse": true + * } ); + * } ); + */ + "sScrollX": "", + + + /** + * This property can be used to force a DataTable to use more width than it + * might otherwise do when x-scrolling is enabled. For example if you have a + * table which requires to be well spaced, this parameter is useful for + * "over-sizing" the table, and thus forcing scrolling. This property can by + * any CSS unit, or a number (in which case it will be treated as a pixel + * measurement). + * @type string + * @default <i>blank string - i.e. disabled</i> + * + * @dtopt Options + * @name DataTable.defaults.scrollXInner + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "scrollX": "100%", + * "scrollXInner": "110%" + * } ); + * } ); + */ + "sScrollXInner": "", + + + /** + * Enable vertical scrolling. Vertical scrolling will constrain the DataTable + * to the given height, and enable scrolling for any data which overflows the + * current viewport. This can be used as an alternative to paging to display + * a lot of data in a small area (although paging and scrolling can both be + * enabled at the same time). This property can be any CSS unit, or a number + * (in which case it will be treated as a pixel measurement). + * @type string + * @default <i>blank string - i.e. disabled</i> + * + * @dtopt Features + * @name DataTable.defaults.scrollY + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "scrollY": "200px", + * "paginate": false + * } ); + * } ); + */ + "sScrollY": "", + + + /** + * __Deprecated__ The functionality provided by this parameter has now been + * superseded by that provided through `ajax`, which should be used instead. + * + * Set the HTTP method that is used to make the Ajax call for server-side + * processing or Ajax sourced data. + * @type string + * @default GET + * + * @dtopt Options + * @dtopt Server-side + * @name DataTable.defaults.serverMethod + * + * @deprecated 1.10. Please use `ajax` for this functionality now. + */ + "sServerMethod": "GET", + + + /** + * DataTables makes use of renderers when displaying HTML elements for + * a table. These renderers can be added or modified by plug-ins to + * generate suitable mark-up for a site. For example the Bootstrap + * integration plug-in for DataTables uses a paging button renderer to + * display pagination buttons in the mark-up required by Bootstrap. + * + * For further information about the renderers available see + * DataTable.ext.renderer + * @type string|object + * @default null + * + * @name DataTable.defaults.renderer + * + */ + "renderer": null, + + + /** + * Set the data property name that DataTables should use to get a row's id + * to set as the `id` property in the node. + * @type string + * @default DT_RowId + * + * @name DataTable.defaults.rowId + */ + "rowId": "DT_RowId" + }; + + _fnHungarianMap(DataTable.defaults); + + + /* + * Developer note - See note in model.defaults.js about the use of Hungarian + * notation and camel case. + */ + + /** + * Column options that can be given to DataTables at initialisation time. + * @namespace + */ + DataTable.defaults.column = { + /** + * Define which column(s) an order will occur on for this column. This + * allows a column's ordering to take multiple columns into account when + * doing a sort or use the data from a different column. For example first + * name / last name columns make sense to do a multi-column sort over the + * two columns. + * @type array|int + * @default null <i>Takes the value of the column index automatically</i> + * + * @name DataTable.defaults.column.orderData + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "orderData": [ 0, 1 ], "targets": [ 0 ] }, + * { "orderData": [ 1, 0 ], "targets": [ 1 ] }, + * { "orderData": 2, "targets": [ 2 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "orderData": [ 0, 1 ] }, + * { "orderData": [ 1, 0 ] }, + * { "orderData": 2 }, + * null, + * null + * ] + * } ); + * } ); + */ + "aDataSort": null, + "iDataSort": -1, + + + /** + * You can control the default ordering direction, and even alter the + * behaviour of the sort handler (i.e. only allow ascending ordering etc) + * using this parameter. + * @type array + * @default [ 'asc', 'desc' ] + * + * @name DataTable.defaults.column.orderSequence + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "orderSequence": [ "asc" ], "targets": [ 1 ] }, + * { "orderSequence": [ "desc", "asc", "asc" ], "targets": [ 2 ] }, + * { "orderSequence": [ "desc" ], "targets": [ 3 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * null, + * { "orderSequence": [ "asc" ] }, + * { "orderSequence": [ "desc", "asc", "asc" ] }, + * { "orderSequence": [ "desc" ] }, + * null + * ] + * } ); + * } ); + */ + "asSorting": ['asc', 'desc'], + + + /** + * Enable or disable filtering on the data in this column. + * @type boolean + * @default true + * + * @name DataTable.defaults.column.searchable + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "searchable": false, "targets": [ 0 ] } + * ] } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "searchable": false }, + * null, + * null, + * null, + * null + * ] } ); + * } ); + */ + "bSearchable": true, + + + /** + * Enable or disable ordering on this column. + * @type boolean + * @default true + * + * @name DataTable.defaults.column.orderable + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "orderable": false, "targets": [ 0 ] } + * ] } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "orderable": false }, + * null, + * null, + * null, + * null + * ] } ); + * } ); + */ + "bSortable": true, + + + /** + * Enable or disable the display of this column. + * @type boolean + * @default true + * + * @name DataTable.defaults.column.visible + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "visible": false, "targets": [ 0 ] } + * ] } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "visible": false }, + * null, + * null, + * null, + * null + * ] } ); + * } ); + */ + "bVisible": true, + + + /** + * Developer definable function that is called whenever a cell is created (Ajax source, + * etc) or processed for input (DOM source). This can be used as a compliment to mRender + * allowing you to modify the DOM element (add background colour for example) when the + * element is available. + * @type function + * @param {element} td The TD node that has been created + * @param {*} cellData The Data for the cell + * @param {array|object} rowData The data for the whole row + * @param {int} row The row index for the aoData data store + * @param {int} col The column index for aoColumns + * + * @name DataTable.defaults.column.createdCell + * @dtopt Columns + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [3], + * "createdCell": function (td, cellData, rowData, row, col) { + * if ( cellData == "1.7" ) { + * $(td).css('color', 'blue') + * } + * } + * } ] + * }); + * } ); + */ + "fnCreatedCell": null, + + + /** + * This parameter has been replaced by `data` in DataTables to ensure naming + * consistency. `dataProp` can still be used, as there is backwards + * compatibility in DataTables for this option, but it is strongly + * recommended that you use `data` in preference to `dataProp`. + * @name DataTable.defaults.column.dataProp + */ + + + /** + * This property can be used to read data from any data source property, + * including deeply nested objects / properties. `data` can be given in a + * number of different ways which effect its behaviour: + * + * * `integer` - treated as an array index for the data source. This is the + * default that DataTables uses (incrementally increased for each column). + * * `string` - read an object property from the data source. There are + * three 'special' options that can be used in the string to alter how + * DataTables reads the data from the source object: + * * `.` - Dotted Javascript notation. Just as you use a `.` in + * Javascript to read from nested objects, so to can the options + * specified in `data`. For example: `browser.version` or + * `browser.name`. If your object parameter name contains a period, use + * `\\` to escape it - i.e. `first\\.name`. + * * `[]` - Array notation. DataTables can automatically combine data + * from and array source, joining the data with the characters provided + * between the two brackets. For example: `name[, ]` would provide a + * comma-space separated list from the source array. If no characters + * are provided between the brackets, the original array source is + * returned. + * * `()` - Function notation. Adding `()` to the end of a parameter will + * execute a function of the name given. For example: `browser()` for a + * simple function on the data source, `browser.version()` for a + * function in a nested property or even `browser().version` to get an + * object property if the function called returns an object. Note that + * function notation is recommended for use in `render` rather than + * `data` as it is much simpler to use as a renderer. + * * `null` - use the original data source for the row rather than plucking + * data directly from it. This action has effects on two other + * initialisation options: + * * `defaultContent` - When null is given as the `data` option and + * `defaultContent` is specified for the column, the value defined by + * `defaultContent` will be used for the cell. + * * `render` - When null is used for the `data` option and the `render` + * option is specified for the column, the whole data source for the + * row is used for the renderer. + * * `function` - the function given will be executed whenever DataTables + * needs to set or get the data for a cell in the column. The function + * takes three parameters: + * * Parameters: + * * `{array|object}` The data source for the row + * * `{string}` The type call data requested - this will be 'set' when + * setting data or 'filter', 'display', 'type', 'sort' or undefined + * when gathering data. Note that when `undefined` is given for the + * type DataTables expects to get the raw data for the object back< + * * `{*}` Data to set when the second parameter is 'set'. + * * Return: + * * The return value from the function is not required when 'set' is + * the type of call, but otherwise the return is what will be used + * for the data requested. + * + * Note that `data` is a getter and setter option. If you just require + * formatting of data for output, you will likely want to use `render` which + * is simply a getter and thus simpler to use. + * + * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The + * name change reflects the flexibility of this property and is consistent + * with the naming of mRender. If 'mDataProp' is given, then it will still + * be used by DataTables, as it automatically maps the old name to the new + * if required. + * + * @type string|int|function|null + * @default null <i>Use automatically calculated column index</i> + * + * @name DataTable.defaults.column.data + * @dtopt Columns + * + * @example + * // Read table data from objects + * // JSON structure for each row: + * // { + * // "engine": {value}, + * // "browser": {value}, + * // "platform": {value}, + * // "version": {value}, + * // "grade": {value} + * // } + * $(document).ready( function() { + * $('#example').dataTable( { + * "ajaxSource": "sources/objects.txt", + * "columns": [ + * { "data": "engine" }, + * { "data": "browser" }, + * { "data": "platform" }, + * { "data": "version" }, + * { "data": "grade" } + * ] + * } ); + * } ); + * + * @example + * // Read information from deeply nested objects + * // JSON structure for each row: + * // { + * // "engine": {value}, + * // "browser": {value}, + * // "platform": { + * // "inner": {value} + * // }, + * // "details": [ + * // {value}, {value} + * // ] + * // } + * $(document).ready( function() { + * $('#example').dataTable( { + * "ajaxSource": "sources/deep.txt", + * "columns": [ + * { "data": "engine" }, + * { "data": "browser" }, + * { "data": "platform.inner" }, + * { "data": "platform.details.0" }, + * { "data": "platform.details.1" } + * ] + * } ); + * } ); + * + * @example + * // Using `data` as a function to provide different information for + * // sorting, filtering and display. In this case, currency (price) + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [ 0 ], + * "data": function ( source, type, val ) { + * if (type === 'set') { + * source.price = val; + * // Store the computed dislay and filter values for efficiency + * source.price_display = val=="" ? "" : "$"+numberFormat(val); + * source.price_filter = val=="" ? "" : "$"+numberFormat(val)+" "+val; + * return; + * } + * else if (type === 'display') { + * return source.price_display; + * } + * else if (type === 'filter') { + * return source.price_filter; + * } + * // 'sort', 'type' and undefined all just use the integer + * return source.price; + * } + * } ] + * } ); + * } ); + * + * @example + * // Using default content + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [ 0 ], + * "data": null, + * "defaultContent": "Click to edit" + * } ] + * } ); + * } ); + * + * @example + * // Using array notation - outputting a list from an array + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [ 0 ], + * "data": "name[, ]" + * } ] + * } ); + * } ); + * + */ + "mData": null, + + + /** + * This property is the rendering partner to `data` and it is suggested that + * when you want to manipulate data for display (including filtering, + * sorting etc) without altering the underlying data for the table, use this + * property. `render` can be considered to be the the read only companion to + * `data` which is read / write (then as such more complex). Like `data` + * this option can be given in a number of different ways to effect its + * behaviour: + * + * * `integer` - treated as an array index for the data source. This is the + * default that DataTables uses (incrementally increased for each column). + * * `string` - read an object property from the data source. There are + * three 'special' options that can be used in the string to alter how + * DataTables reads the data from the source object: + * * `.` - Dotted Javascript notation. Just as you use a `.` in + * Javascript to read from nested objects, so to can the options + * specified in `data`. For example: `browser.version` or + * `browser.name`. If your object parameter name contains a period, use + * `\\` to escape it - i.e. `first\\.name`. + * * `[]` - Array notation. DataTables can automatically combine data + * from and array source, joining the data with the characters provided + * between the two brackets. For example: `name[, ]` would provide a + * comma-space separated list from the source array. If no characters + * are provided between the brackets, the original array source is + * returned. + * * `()` - Function notation. Adding `()` to the end of a parameter will + * execute a function of the name given. For example: `browser()` for a + * simple function on the data source, `browser.version()` for a + * function in a nested property or even `browser().version` to get an + * object property if the function called returns an object. + * * `object` - use different data for the different data types requested by + * DataTables ('filter', 'display', 'type' or 'sort'). The property names + * of the object is the data type the property refers to and the value can + * defined using an integer, string or function using the same rules as + * `render` normally does. Note that an `_` option _must_ be specified. + * This is the default value to use if you haven't specified a value for + * the data type requested by DataTables. + * * `function` - the function given will be executed whenever DataTables + * needs to set or get the data for a cell in the column. The function + * takes three parameters: + * * Parameters: + * * {array|object} The data source for the row (based on `data`) + * * {string} The type call data requested - this will be 'filter', + * 'display', 'type' or 'sort'. + * * {array|object} The full data source for the row (not based on + * `data`) + * * Return: + * * The return value from the function is what will be used for the + * data requested. + * + * @type string|int|function|object|null + * @default null Use the data source value. + * + * @name DataTable.defaults.column.render + * @dtopt Columns + * + * @example + * // Create a comma separated list from an array of objects + * $(document).ready( function() { + * $('#example').dataTable( { + * "ajaxSource": "sources/deep.txt", + * "columns": [ + * { "data": "engine" }, + * { "data": "browser" }, + * { + * "data": "platform", + * "render": "[, ].name" + * } + * ] + * } ); + * } ); + * + * @example + * // Execute a function to obtain data + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [ 0 ], + * "data": null, // Use the full data source object for the renderer's source + * "render": "browserName()" + * } ] + * } ); + * } ); + * + * @example + * // As an object, extracting different data for the different types + * // This would be used with a data source such as: + * // { "phone": 5552368, "phone_filter": "5552368 555-2368", "phone_display": "555-2368" } + * // Here the `phone` integer is used for sorting and type detection, while `phone_filter` + * // (which has both forms) is used for filtering for if a user inputs either format, while + * // the formatted phone number is the one that is shown in the table. + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [ 0 ], + * "data": null, // Use the full data source object for the renderer's source + * "render": { + * "_": "phone", + * "filter": "phone_filter", + * "display": "phone_display" + * } + * } ] + * } ); + * } ); + * + * @example + * // Use as a function to create a link from the data source + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [ 0 ], + * "data": "download_link", + * "render": function ( data, type, full ) { + * return '<a href="'+data+'">Download</a>'; + * } + * } ] + * } ); + * } ); + */ + "mRender": null, + + + /** + * Change the cell type created for the column - either TD cells or TH cells. This + * can be useful as TH cells have semantic meaning in the table body, allowing them + * to act as a header for a row (you may wish to add scope='row' to the TH elements). + * @type string + * @default td + * + * @name DataTable.defaults.column.cellType + * @dtopt Columns + * + * @example + * // Make the first column use TH cells + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [ 0 ], + * "cellType": "th" + * } ] + * } ); + * } ); + */ + "sCellType": "td", + + + /** + * Class to give to each cell in this column. + * @type string + * @default <i>Empty string</i> + * + * @name DataTable.defaults.column.class + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "class": "my_class", "targets": [ 0 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "class": "my_class" }, + * null, + * null, + * null, + * null + * ] + * } ); + * } ); + */ + "sClass": "", + + /** + * When DataTables calculates the column widths to assign to each column, + * it finds the longest string in each column and then constructs a + * temporary table and reads the widths from that. The problem with this + * is that "mmm" is much wider then "iiii", but the latter is a longer + * string - thus the calculation can go wrong (doing it properly and putting + * it into an DOM object and measuring that is horribly(!) slow). Thus as + * a "work around" we provide this option. It will append its value to the + * text that is found to be the longest string for the column - i.e. padding. + * Generally you shouldn't need this! + * @type string + * @default <i>Empty string<i> + * + * @name DataTable.defaults.column.contentPadding + * @dtopt Columns + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * null, + * null, + * null, + * { + * "contentPadding": "mmm" + * } + * ] + * } ); + * } ); + */ + "sContentPadding": "", + + + /** + * Allows a default value to be given for a column's data, and will be used + * whenever a null data source is encountered (this can be because `data` + * is set to null, or because the data source itself is null). + * @type string + * @default null + * + * @name DataTable.defaults.column.defaultContent + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { + * "data": null, + * "defaultContent": "Edit", + * "targets": [ -1 ] + * } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * null, + * null, + * null, + * { + * "data": null, + * "defaultContent": "Edit" + * } + * ] + * } ); + * } ); + */ + "sDefaultContent": null, + + + /** + * This parameter is only used in DataTables' server-side processing. It can + * be exceptionally useful to know what columns are being displayed on the + * client side, and to map these to database fields. When defined, the names + * also allow DataTables to reorder information from the server if it comes + * back in an unexpected order (i.e. if you switch your columns around on the + * client-side, your server-side code does not also need updating). + * @type string + * @default <i>Empty string</i> + * + * @name DataTable.defaults.column.name + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "name": "engine", "targets": [ 0 ] }, + * { "name": "browser", "targets": [ 1 ] }, + * { "name": "platform", "targets": [ 2 ] }, + * { "name": "version", "targets": [ 3 ] }, + * { "name": "grade", "targets": [ 4 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "name": "engine" }, + * { "name": "browser" }, + * { "name": "platform" }, + * { "name": "version" }, + * { "name": "grade" } + * ] + * } ); + * } ); + */ + "sName": "", + + + /** + * Defines a data source type for the ordering which can be used to read + * real-time information from the table (updating the internally cached + * version) prior to ordering. This allows ordering to occur on user + * editable elements such as form inputs. + * @type string + * @default std + * + * @name DataTable.defaults.column.orderDataType + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "orderDataType": "dom-text", "targets": [ 2, 3 ] }, + * { "type": "numeric", "targets": [ 3 ] }, + * { "orderDataType": "dom-select", "targets": [ 4 ] }, + * { "orderDataType": "dom-checkbox", "targets": [ 5 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * null, + * null, + * { "orderDataType": "dom-text" }, + * { "orderDataType": "dom-text", "type": "numeric" }, + * { "orderDataType": "dom-select" }, + * { "orderDataType": "dom-checkbox" } + * ] + * } ); + * } ); + */ + "sSortDataType": "std", + + + /** + * The title of this column. + * @type string + * @default null <i>Derived from the 'TH' value for this column in the + * original HTML table.</i> + * + * @name DataTable.defaults.column.title + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "title": "My column title", "targets": [ 0 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "title": "My column title" }, + * null, + * null, + * null, + * null + * ] + * } ); + * } ); + */ + "sTitle": null, + + + /** + * The type allows you to specify how the data for this column will be + * ordered. Four types (string, numeric, date and html (which will strip + * HTML tags before ordering)) are currently available. Note that only date + * formats understood by Javascript's Date() object will be accepted as type + * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string', + * 'numeric', 'date' or 'html' (by default). Further types can be adding + * through plug-ins. + * @type string + * @default null <i>Auto-detected from raw data</i> + * + * @name DataTable.defaults.column.type + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "type": "html", "targets": [ 0 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "type": "html" }, + * null, + * null, + * null, + * null + * ] + * } ); + * } ); + */ + "sType": null, + + + /** + * Defining the width of the column, this parameter may take any CSS value + * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not + * been given a specific width through this interface ensuring that the table + * remains readable. + * @type string + * @default null <i>Automatic</i> + * + * @name DataTable.defaults.column.width + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "width": "20%", "targets": [ 0 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "width": "20%" }, + * null, + * null, + * null, + * null + * ] + * } ); + * } ); + */ + "sWidth": null + }; + + _fnHungarianMap(DataTable.defaults.column); + + + /** + * DataTables settings object - this holds all the information needed for a + * given table, including configuration, data and current application of the + * table options. DataTables does not have a single instance for each DataTable + * with the settings attached to that instance, but rather instances of the + * DataTable "class" are created on-the-fly as needed (typically by a + * $().dataTable() call) and the settings object is then applied to that + * instance. + * + * Note that this object is related to {@link DataTable.defaults} but this + * one is the internal data store for DataTables's cache of columns. It should + * NOT be manipulated outside of DataTables. Any configuration should be done + * through the initialisation options. + * @namespace + * @todo Really should attach the settings object to individual instances so we + * don't need to create new instances on each $().dataTable() call (if the + * table already exists). It would also save passing oSettings around and + * into every single function. However, this is a very significant + * architecture change for DataTables and will almost certainly break + * backwards compatibility with older installations. This is something that + * will be done in 2.0. + */ + DataTable.models.oSettings = { + /** + * Primary features of DataTables and their enablement state. + * @namespace + */ + "oFeatures": { + + /** + * Flag to say if DataTables should automatically try to calculate the + * optimum table and columns widths (true) or not (false). + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bAutoWidth": null, + + /** + * Delay the creation of TR and TD elements until they are actually + * needed by a driven page draw. This can give a significant speed + * increase for Ajax source and Javascript source data, but makes no + * difference at all fro DOM and server-side processing tables. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bDeferRender": null, + + /** + * Enable filtering on the table or not. Note that if this is disabled + * then there is no filtering at all on the table, including fnFilter. + * To just remove the filtering input use sDom and remove the 'f' option. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bFilter": null, + + /** + * Table information element (the 'Showing x of y records' div) enable + * flag. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bInfo": null, + + /** + * Present a user control allowing the end user to change the page size + * when pagination is enabled. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bLengthChange": null, + + /** + * Pagination enabled or not. Note that if this is disabled then length + * changing must also be disabled. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bPaginate": null, + + /** + * Processing indicator enable flag whenever DataTables is enacting a + * user request - typically an Ajax request for server-side processing. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bProcessing": null, + + /** + * Server-side processing enabled flag - when enabled DataTables will + * get all data from the server for every draw - there is no filtering, + * sorting or paging done on the client-side. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bServerSide": null, + + /** + * Sorting enablement flag. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bSort": null, + + /** + * Multi-column sorting + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bSortMulti": null, + + /** + * Apply a class to the columns which are being sorted to provide a + * visual highlight or not. This can slow things down when enabled since + * there is a lot of DOM interaction. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bSortClasses": null, + + /** + * State saving enablement flag. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bStateSave": null + }, + + + /** + * Scrolling settings for a table. + * @namespace + */ + "oScroll": { + /** + * When the table is shorter in height than sScrollY, collapse the + * table container down to the height of the table (when true). + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bCollapse": null, + + /** + * Width of the scrollbar for the web-browser's platform. Calculated + * during table initialisation. + * @type int + * @default 0 + */ + "iBarWidth": 0, + + /** + * Viewport width for horizontal scrolling. Horizontal scrolling is + * disabled if an empty string. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string + */ + "sX": null, + + /** + * Width to expand the table to when using x-scrolling. Typically you + * should not need to use this. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string + * @deprecated + */ + "sXInner": null, + + /** + * Viewport height for vertical scrolling. Vertical scrolling is disabled + * if an empty string. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string + */ + "sY": null + }, + + /** + * Language information for the table. + * @namespace + * @extends DataTable.defaults.oLanguage + */ + "oLanguage": { + /** + * Information callback function. See + * {@link DataTable.defaults.fnInfoCallback} + * @type function + * @default null + */ + "fnInfoCallback": null + }, + + /** + * Browser support parameters + * @namespace + */ + "oBrowser": { + /** + * Indicate if the browser incorrectly calculates width:100% inside a + * scrolling element (IE6/7) + * @type boolean + * @default false + */ + "bScrollOversize": false, + + /** + * Determine if the vertical scrollbar is on the right or left of the + * scrolling container - needed for rtl language layout, although not + * all browsers move the scrollbar (Safari). + * @type boolean + * @default false + */ + "bScrollbarLeft": false, + + /** + * Flag for if `getBoundingClientRect` is fully supported or not + * @type boolean + * @default false + */ + "bBounding": false, + + /** + * Browser scrollbar width + * @type integer + * @default 0 + */ + "barWidth": 0 + }, + + + "ajax": null, + + + /** + * Array referencing the nodes which are used for the features. The + * parameters of this object match what is allowed by sDom - i.e. + * <ul> + * <li>'l' - Length changing</li> + * <li>'f' - Filtering input</li> + * <li>'t' - The table!</li> + * <li>'i' - Information</li> + * <li>'p' - Pagination</li> + * <li>'r' - pRocessing</li> + * </ul> + * @type array + * @default [] + */ + "aanFeatures": [], + + /** + * Store data information - see {@link DataTable.models.oRow} for detailed + * information. + * @type array + * @default [] + */ + "aoData": [], + + /** + * Array of indexes which are in the current display (after filtering etc) + * @type array + * @default [] + */ + "aiDisplay": [], + + /** + * Array of indexes for display - no filtering + * @type array + * @default [] + */ + "aiDisplayMaster": [], + + /** + * Map of row ids to data indexes + * @type object + * @default {} + */ + "aIds": {}, + + /** + * Store information about each column that is in use + * @type array + * @default [] + */ + "aoColumns": [], + + /** + * Store information about the table's header + * @type array + * @default [] + */ + "aoHeader": [], + + /** + * Store information about the table's footer + * @type array + * @default [] + */ + "aoFooter": [], + + /** + * Store the applied global search information in case we want to force a + * research or compare the old search to a new one. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @namespace + * @extends DataTable.models.oSearch + */ + "oPreviousSearch": {}, + + /** + * Store the applied search for each column - see + * {@link DataTable.models.oSearch} for the format that is used for the + * filtering information for each column. + * @type array + * @default [] + */ + "aoPreSearchCols": [], + + /** + * Sorting that is applied to the table. Note that the inner arrays are + * used in the following manner: + * <ul> + * <li>Index 0 - column number</li> + * <li>Index 1 - current sorting direction</li> + * </ul> + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type array + * @todo These inner arrays should really be objects + */ + "aaSorting": null, + + /** + * Sorting that is always applied to the table (i.e. prefixed in front of + * aaSorting). + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type array + * @default [] + */ + "aaSortingFixed": [], + + /** + * Classes to use for the striping of a table. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type array + * @default [] + */ + "asStripeClasses": null, + + /** + * If restoring a table - we should restore its striping classes as well + * @type array + * @default [] + */ + "asDestroyStripes": [], + + /** + * If restoring a table - we should restore its width + * @type int + * @default 0 + */ + "sDestroyWidth": 0, + + /** + * Callback functions array for every time a row is inserted (i.e. on a draw). + * @type array + * @default [] + */ + "aoRowCallback": [], + + /** + * Callback functions for the header on each draw. + * @type array + * @default [] + */ + "aoHeaderCallback": [], + + /** + * Callback function for the footer on each draw. + * @type array + * @default [] + */ + "aoFooterCallback": [], + + /** + * Array of callback functions for draw callback functions + * @type array + * @default [] + */ + "aoDrawCallback": [], + + /** + * Array of callback functions for row created function + * @type array + * @default [] + */ + "aoRowCreatedCallback": [], + + /** + * Callback functions for just before the table is redrawn. A return of + * false will be used to cancel the draw. + * @type array + * @default [] + */ + "aoPreDrawCallback": [], + + /** + * Callback functions for when the table has been initialised. + * @type array + * @default [] + */ + "aoInitComplete": [], + + + /** + * Callbacks for modifying the settings to be stored for state saving, prior to + * saving state. + * @type array + * @default [] + */ + "aoStateSaveParams": [], + + /** + * Callbacks for modifying the settings that have been stored for state saving + * prior to using the stored values to restore the state. + * @type array + * @default [] + */ + "aoStateLoadParams": [], + + /** + * Callbacks for operating on the settings object once the saved state has been + * loaded + * @type array + * @default [] + */ + "aoStateLoaded": [], + + /** + * Cache the table ID for quick access + * @type string + * @default <i>Empty string</i> + */ + "sTableId": "", + + /** + * The TABLE node for the main table + * @type node + * @default null + */ + "nTable": null, + + /** + * Permanent ref to the thead element + * @type node + * @default null + */ + "nTHead": null, + + /** + * Permanent ref to the tfoot element - if it exists + * @type node + * @default null + */ + "nTFoot": null, + + /** + * Permanent ref to the tbody element + * @type node + * @default null + */ + "nTBody": null, + + /** + * Cache the wrapper node (contains all DataTables controlled elements) + * @type node + * @default null + */ + "nTableWrapper": null, + + /** + * Indicate if when using server-side processing the loading of data + * should be deferred until the second draw. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + * @default false + */ + "bDeferLoading": false, + + /** + * Indicate if all required information has been read in + * @type boolean + * @default false + */ + "bInitialised": false, + + /** + * Information about open rows. Each object in the array has the parameters + * 'nTr' and 'nParent' + * @type array + * @default [] + */ + "aoOpenRows": [], + + /** + * Dictate the positioning of DataTables' control elements - see + * {@link DataTable.model.oInit.sDom}. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string + * @default null + */ + "sDom": null, + + /** + * Search delay (in mS) + * @type integer + * @default null + */ + "searchDelay": null, + + /** + * Which type of pagination should be used. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string + * @default two_button + */ + "sPaginationType": "two_button", + + /** + * The state duration (for `stateSave`) in seconds. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type int + * @default 0 + */ + "iStateDuration": 0, + + /** + * Array of callback functions for state saving. Each array element is an + * object with the following parameters: + * <ul> + * <li>function:fn - function to call. Takes two parameters, oSettings + * and the JSON string to save that has been thus far created. Returns + * a JSON string to be inserted into a json object + * (i.e. '"param": [ 0, 1, 2]')</li> + * <li>string:sName - name of callback</li> + * </ul> + * @type array + * @default [] + */ + "aoStateSave": [], + + /** + * Array of callback functions for state loading. Each array element is an + * object with the following parameters: + * <ul> + * <li>function:fn - function to call. Takes two parameters, oSettings + * and the object stored. May return false to cancel state loading</li> + * <li>string:sName - name of callback</li> + * </ul> + * @type array + * @default [] + */ + "aoStateLoad": [], + + /** + * State that was saved. Useful for back reference + * @type object + * @default null + */ + "oSavedState": null, + + /** + * State that was loaded. Useful for back reference + * @type object + * @default null + */ + "oLoadedState": null, + + /** + * Source url for AJAX data for the table. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string + * @default null + */ + "sAjaxSource": null, + + /** + * Property from a given object from which to read the table data from. This + * can be an empty string (when not server-side processing), in which case + * it is assumed an an array is given directly. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string + */ + "sAjaxDataProp": null, + + /** + * Note if draw should be blocked while getting data + * @type boolean + * @default true + */ + "bAjaxDataGet": true, + + /** + * The last jQuery XHR object that was used for server-side data gathering. + * This can be used for working with the XHR information in one of the + * callbacks + * @type object + * @default null + */ + "jqXHR": null, + + /** + * JSON returned from the server in the last Ajax request + * @type object + * @default undefined + */ + "json": undefined, + + /** + * Data submitted as part of the last Ajax request + * @type object + * @default undefined + */ + "oAjaxData": undefined, + + /** + * Function to get the server-side data. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type function + */ + "fnServerData": null, + + /** + * Functions which are called prior to sending an Ajax request so extra + * parameters can easily be sent to the server + * @type array + * @default [] + */ + "aoServerParams": [], + + /** + * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if + * required). + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string + */ + "sServerMethod": null, + + /** + * Format numbers for display. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type function + */ + "fnFormatNumber": null, + + /** + * List of options that can be used for the user selectable length menu. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type array + * @default [] + */ + "aLengthMenu": null, + + /** + * Counter for the draws that the table does. Also used as a tracker for + * server-side processing + * @type int + * @default 0 + */ + "iDraw": 0, + + /** + * Indicate if a redraw is being done - useful for Ajax + * @type boolean + * @default false + */ + "bDrawing": false, + + /** + * Draw index (iDraw) of the last error when parsing the returned data + * @type int + * @default -1 + */ + "iDrawError": -1, + + /** + * Paging display length + * @type int + * @default 10 + */ + "_iDisplayLength": 10, + + /** + * Paging start point - aiDisplay index + * @type int + * @default 0 + */ + "_iDisplayStart": 0, + + /** + * Server-side processing - number of records in the result set + * (i.e. before filtering), Use fnRecordsTotal rather than + * this property to get the value of the number of records, regardless of + * the server-side processing setting. + * @type int + * @default 0 + * @private + */ + "_iRecordsTotal": 0, + + /** + * Server-side processing - number of records in the current display set + * (i.e. after filtering). Use fnRecordsDisplay rather than + * this property to get the value of the number of records, regardless of + * the server-side processing setting. + * @type boolean + * @default 0 + * @private + */ + "_iRecordsDisplay": 0, + + /** + * Flag to indicate if jQuery UI marking and classes should be used. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bJUI": null, + + /** + * The classes to use for the table + * @type object + * @default {} + */ + "oClasses": {}, + + /** + * Flag attached to the settings object so you can check in the draw + * callback if filtering has been done in the draw. Deprecated in favour of + * events. + * @type boolean + * @default false + * @deprecated + */ + "bFiltered": false, + + /** + * Flag attached to the settings object so you can check in the draw + * callback if sorting has been done in the draw. Deprecated in favour of + * events. + * @type boolean + * @default false + * @deprecated + */ + "bSorted": false, + + /** + * Indicate that if multiple rows are in the header and there is more than + * one unique cell per column, if the top one (true) or bottom one (false) + * should be used for sorting / title by DataTables. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bSortCellsTop": null, + + /** + * Initialisation object that is used for the table + * @type object + * @default null + */ + "oInit": null, + + /** + * Destroy callback functions - for plug-ins to attach themselves to the + * destroy so they can clean up markup and events. + * @type array + * @default [] + */ + "aoDestroyCallback": [], + + + /** + * Get the number of records in the current record set, before filtering + * @type function + */ + "fnRecordsTotal": function () { + return _fnDataSource(this) == 'ssp' ? + this._iRecordsTotal * 1 : + this.aiDisplayMaster.length; + }, + + /** + * Get the number of records in the current record set, after filtering + * @type function + */ + "fnRecordsDisplay": function () { + return _fnDataSource(this) == 'ssp' ? + this._iRecordsDisplay * 1 : + this.aiDisplay.length; + }, + + /** + * Get the display end point - aiDisplay index + * @type function + */ + "fnDisplayEnd": function () { + var + len = this._iDisplayLength, + start = this._iDisplayStart, + calc = start + len, + records = this.aiDisplay.length, + features = this.oFeatures, + paginate = features.bPaginate; + + if (features.bServerSide) { + return paginate === false || len === -1 ? + start + records : + Math.min(start + len, this._iRecordsDisplay); + } else { + return !paginate || calc > records || len === -1 ? + records : + calc; + } + }, + + /** + * The DataTables object for this table + * @type object + * @default null + */ + "oInstance": null, + + /** + * Unique identifier for each instance of the DataTables object. If there + * is an ID on the table node, then it takes that value, otherwise an + * incrementing internal counter is used. + * @type string + * @default null + */ + "sInstance": null, + + /** + * tabindex attribute value that is added to DataTables control elements, allowing + * keyboard navigation of the table and its controls. + */ + "iTabIndex": 0, + + /** + * DIV container for the footer scrolling table if scrolling + */ + "nScrollHead": null, + + /** + * DIV container for the footer scrolling table if scrolling + */ + "nScrollFoot": null, + + /** + * Last applied sort + * @type array + * @default [] + */ + "aLastSort": [], + + /** + * Stored plug-in instances + * @type object + * @default {} + */ + "oPlugins": {}, + + /** + * Function used to get a row's id from the row's data + * @type function + * @default null + */ + "rowIdFn": null, + + /** + * Data location where to store a row's id + * @type string + * @default null + */ + "rowId": null + }; + + /** + * Extension object for DataTables that is used to provide all extension + * options. + * + * Note that the `DataTable.ext` object is available through + * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is + * also aliased to `jQuery.fn.dataTableExt` for historic reasons. + * @namespace + * @extends DataTable.models.ext + */ + + + /** + * DataTables extensions + * + * This namespace acts as a collection area for plug-ins that can be used to + * extend DataTables capabilities. Indeed many of the build in methods + * use this method to provide their own capabilities (sorting methods for + * example). + * + * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy + * reasons + * + * @namespace + */ + DataTable.ext = _ext = { + /** + * Buttons. For use with the Buttons extension for DataTables. This is + * defined here so other extensions can define buttons regardless of load + * order. It is _not_ used by DataTables core. + * + * @type object + * @default {} + */ + buttons: {}, + + + /** + * Element class names + * + * @type object + * @default {} + */ + classes: {}, + + + /** + * DataTables build type (expanded by the download builder) + * + * @type string + */ + builder: "-source-", + + + /** + * Error reporting. + * + * How should DataTables report an error. Can take the value 'alert', + * 'throw', 'none' or a function. + * + * @type string|function + * @default alert + */ + errMode: "alert", + + + /** + * Feature plug-ins. + * + * This is an array of objects which describe the feature plug-ins that are + * available to DataTables. These feature plug-ins are then available for + * use through the `dom` initialisation option. + * + * Each feature plug-in is described by an object which must have the + * following properties: + * + * * `fnInit` - function that is used to initialise the plug-in, + * * `cFeature` - a character so the feature can be enabled by the `dom` + * instillation option. This is case sensitive. + * + * The `fnInit` function has the following input parameters: + * + * 1. `{object}` DataTables settings object: see + * {@link DataTable.models.oSettings} + * + * And the following return is expected: + * + * * {node|null} The element which contains your feature. Note that the + * return may also be void if your plug-in does not require to inject any + * DOM elements into DataTables control (`dom`) - for example this might + * be useful when developing a plug-in which allows table control via + * keyboard entry + * + * @type array + * + * @example + * $.fn.dataTable.ext.features.push( { + * "fnInit": function( oSettings ) { + * return new TableTools( { "oDTSettings": oSettings } ); + * }, + * "cFeature": "T" + * } ); + */ + feature: [], + + + /** + * Row searching. + * + * This method of searching is complimentary to the default type based + * searching, and a lot more comprehensive as it allows you complete control + * over the searching logic. Each element in this array is a function + * (parameters described below) that is called for every row in the table, + * and your logic decides if it should be included in the searching data set + * or not. + * + * Searching functions have the following input parameters: + * + * 1. `{object}` DataTables settings object: see + * {@link DataTable.models.oSettings} + * 2. `{array|object}` Data for the row to be processed (same as the + * original format that was passed in as the data source, or an array + * from a DOM data source + * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which + * can be useful to retrieve the `TR` element if you need DOM interaction. + * + * And the following return is expected: + * + * * {boolean} Include the row in the searched result set (true) or not + * (false) + * + * Note that as with the main search ability in DataTables, technically this + * is "filtering", since it is subtractive. However, for consistency in + * naming we call it searching here. + * + * @type array + * @default [] + * + * @example + * // The following example shows custom search being applied to the + * // fourth column (i.e. the data[3] index) based on two input values + * // from the end-user, matching the data in a certain range. + * $.fn.dataTable.ext.search.push( + * function( settings, data, dataIndex ) { + * var min = document.getElementById('min').value * 1; + * var max = document.getElementById('max').value * 1; + * var version = data[3] == "-" ? 0 : data[3]*1; + * + * if ( min == "" && max == "" ) { + * return true; + * } + * else if ( min == "" && version < max ) { + * return true; + * } + * else if ( min < version && "" == max ) { + * return true; + * } + * else if ( min < version && version < max ) { + * return true; + * } + * return false; + * } + * ); + */ + search: [], + + + /** + * Selector extensions + * + * The `selector` option can be used to extend the options available for the + * selector modifier options (`selector-modifier` object data type) that + * each of the three built in selector types offer (row, column and cell + + * their plural counterparts). For example the Select extension uses this + * mechanism to provide an option to select only rows, columns and cells + * that have been marked as selected by the end user (`{selected: true}`), + * which can be used in conjunction with the existing built in selector + * options. + * + * Each property is an array to which functions can be pushed. The functions + * take three attributes: + * + * * Settings object for the host table + * * Options object (`selector-modifier` object type) + * * Array of selected item indexes + * + * The return is an array of the resulting item indexes after the custom + * selector has been applied. + * + * @type object + */ + selector: { + cell: [], + column: [], + row: [] + }, + + + /** + * Internal functions, exposed for used in plug-ins. + * + * Please note that you should not need to use the internal methods for + * anything other than a plug-in (and even then, try to avoid if possible). + * The internal function may change between releases. + * + * @type object + * @default {} + */ + internal: {}, + + + /** + * Legacy configuration options. Enable and disable legacy options that + * are available in DataTables. + * + * @type object + */ + legacy: { + /** + * Enable / disable DataTables 1.9 compatible server-side processing + * requests + * + * @type boolean + * @default null + */ + ajax: null + }, + + + /** + * Pagination plug-in methods. + * + * Each entry in this object is a function and defines which buttons should + * be shown by the pagination rendering method that is used for the table: + * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the + * buttons are displayed in the document, while the functions here tell it + * what buttons to display. This is done by returning an array of button + * descriptions (what each button will do). + * + * Pagination types (the four built in options and any additional plug-in + * options defined here) can be used through the `paginationType` + * initialisation parameter. + * + * The functions defined take two parameters: + * + * 1. `{int} page` The current page index + * 2. `{int} pages` The number of pages in the table + * + * Each function is expected to return an array where each element of the + * array can be one of: + * + * * `first` - Jump to first page when activated + * * `last` - Jump to last page when activated + * * `previous` - Show previous page when activated + * * `next` - Show next page when activated + * * `{int}` - Show page of the index given + * * `{array}` - A nested array containing the above elements to add a + * containing 'DIV' element (might be useful for styling). + * + * Note that DataTables v1.9- used this object slightly differently whereby + * an object with two functions would be defined for each plug-in. That + * ability is still supported by DataTables 1.10+ to provide backwards + * compatibility, but this option of use is now decremented and no longer + * documented in DataTables 1.10+. + * + * @type object + * @default {} + * + * @example + * // Show previous, next and current page buttons only + * $.fn.dataTableExt.oPagination.current = function ( page, pages ) { + * return [ 'previous', page, 'next' ]; + * }; + */ + pager: {}, + + + renderer: { + pageButton: {}, + header: {} + }, + + + /** + * Ordering plug-ins - custom data source + * + * The extension options for ordering of data available here is complimentary + * to the default type based ordering that DataTables typically uses. It + * allows much greater control over the the data that is being used to + * order a column, but is necessarily therefore more complex. + * + * This type of ordering is useful if you want to do ordering based on data + * live from the DOM (for example the contents of an 'input' element) rather + * than just the static string that DataTables knows of. + * + * The way these plug-ins work is that you create an array of the values you + * wish to be ordering for the column in question and then return that + * array. The data in the array much be in the index order of the rows in + * the table (not the currently ordering order!). Which order data gathering + * function is run here depends on the `dt-init columns.orderDataType` + * parameter that is used for the column (if any). + * + * The functions defined take two parameters: + * + * 1. `{object}` DataTables settings object: see + * {@link DataTable.models.oSettings} + * 2. `{int}` Target column index + * + * Each function is expected to return an array: + * + * * `{array}` Data for the column to be ordering upon + * + * @type array + * + * @example + * // Ordering using `input` node values + * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col ) + * { + * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { + * return $('input', td).val(); + * } ); + * } + */ + order: {}, + + + /** + * Type based plug-ins. + * + * Each column in DataTables has a type assigned to it, either by automatic + * detection or by direct assignment using the `type` option for the column. + * The type of a column will effect how it is ordering and search (plug-ins + * can also make use of the column type if required). + * + * @namespace + */ + type: { + /** + * Type detection functions. + * + * The functions defined in this object are used to automatically detect + * a column's type, making initialisation of DataTables super easy, even + * when complex data is in the table. + * + * The functions defined take two parameters: + * + * 1. `{*}` Data from the column cell to be analysed + * 2. `{settings}` DataTables settings object. This can be used to + * perform context specific type detection - for example detection + * based on language settings such as using a comma for a decimal + * place. Generally speaking the options from the settings will not + * be required + * + * Each function is expected to return: + * + * * `{string|null}` Data type detected, or null if unknown (and thus + * pass it on to the other type detection functions. + * + * @type array + * + * @example + * // Currency type detection plug-in: + * $.fn.dataTable.ext.type.detect.push( + * function ( data, settings ) { + * // Check the numeric part + * if ( ! $.isNumeric( data.substring(1) ) ) { + * return null; + * } + * + * // Check prefixed by currency + * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) { + * return 'currency'; + * } + * return null; + * } + * ); + */ + detect: [], + + + /** + * Type based search formatting. + * + * The type based searching functions can be used to pre-format the + * data to be search on. For example, it can be used to strip HTML + * tags or to de-format telephone numbers for numeric only searching. + * + * Note that is a search is not defined for a column of a given type, + * no search formatting will be performed. + * + * Pre-processing of searching data plug-ins - When you assign the sType + * for a column (or have it automatically detected for you by DataTables + * or a type detection plug-in), you will typically be using this for + * custom sorting, but it can also be used to provide custom searching + * by allowing you to pre-processing the data and returning the data in + * the format that should be searched upon. This is done by adding + * functions this object with a parameter name which matches the sType + * for that target column. This is the corollary of <i>afnSortData</i> + * for searching data. + * + * The functions defined take a single parameter: + * + * 1. `{*}` Data from the column cell to be prepared for searching + * + * Each function is expected to return: + * + * * `{string|null}` Formatted string that will be used for the searching. + * + * @type object + * @default {} + * + * @example + * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { + * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); + * } + */ + search: {}, + + + /** + * Type based ordering. + * + * The column type tells DataTables what ordering to apply to the table + * when a column is sorted upon. The order for each type that is defined, + * is defined by the functions available in this object. + * + * Each ordering option can be described by three properties added to + * this object: + * + * * `{type}-pre` - Pre-formatting function + * * `{type}-asc` - Ascending order function + * * `{type}-desc` - Descending order function + * + * All three can be used together, only `{type}-pre` or only + * `{type}-asc` and `{type}-desc` together. It is generally recommended + * that only `{type}-pre` is used, as this provides the optimal + * implementation in terms of speed, although the others are provided + * for compatibility with existing Javascript sort functions. + * + * `{type}-pre`: Functions defined take a single parameter: + * + * 1. `{*}` Data from the column cell to be prepared for ordering + * + * And return: + * + * * `{*}` Data to be sorted upon + * + * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort + * functions, taking two parameters: + * + * 1. `{*}` Data to compare to the second parameter + * 2. `{*}` Data to compare to the first parameter + * + * And returning: + * + * * `{*}` Ordering match: <0 if first parameter should be sorted lower + * than the second parameter, ===0 if the two parameters are equal and + * >0 if the first parameter should be sorted height than the second + * parameter. + * + * @type object + * @default {} + * + * @example + * // Numeric ordering of formatted numbers with a pre-formatter + * $.extend( $.fn.dataTable.ext.type.order, { + * "string-pre": function(x) { + * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); + * return parseFloat( a ); + * } + * } ); + * + * @example + * // Case-sensitive string ordering, with no pre-formatting method + * $.extend( $.fn.dataTable.ext.order, { + * "string-case-asc": function(x,y) { + * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + * }, + * "string-case-desc": function(x,y) { + * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + * } + * } ); + */ + order: {} + }, + + /** + * Unique DataTables instance counter + * + * @type int + * @private + */ + _unique: 0, + + + // + // Depreciated + // The following properties are retained for backwards compatiblity only. + // The should not be used in new projects and will be removed in a future + // version + // + + /** + * Version check function. + * @type function + * @depreciated Since 1.10 + */ + fnVersionCheck: DataTable.fnVersionCheck, + + + /** + * Index for what 'this' index API functions should use + * @type int + * @deprecated Since v1.10 + */ + iApiIndex: 0, + + + /** + * jQuery UI class container + * @type object + * @deprecated Since v1.10 + */ + oJUIClasses: {}, + + + /** + * Software version + * @type string + * @deprecated Since v1.10 + */ + sVersion: DataTable.version + }; + + + // + // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts + // + $.extend(_ext, { + afnFiltering: _ext.search, + aTypes: _ext.type.detect, + ofnSearch: _ext.type.search, + oSort: _ext.type.order, + afnSortData: _ext.order, + aoFeatures: _ext.feature, + oApi: _ext.internal, + oStdClasses: _ext.classes, + oPagination: _ext.pager + }); + + + $.extend(DataTable.ext.classes, { + "sTable": "dataTable", + "sNoFooter": "no-footer", + + /* Paging buttons */ + "sPageButton": "paginate_button", + "sPageButtonActive": "current", + "sPageButtonDisabled": "disabled", + + /* Striping classes */ + "sStripeOdd": "odd", + "sStripeEven": "even", + + /* Empty row */ + "sRowEmpty": "dataTables_empty", + + /* Features */ + "sWrapper": "dataTables_wrapper", + "sFilter": "dataTables_filter", + "sInfo": "dataTables_info", + "sPaging": "dataTables_paginate paging_", + /* Note that the type is postfixed */ + "sLength": "dataTables_length", + "sProcessing": "dataTables_processing", + + /* Sorting */ + "sSortAsc": "sorting_asc", + "sSortDesc": "sorting_desc", + "sSortable": "sorting", + /* Sortable in both directions */ + "sSortableAsc": "sorting_asc_disabled", + "sSortableDesc": "sorting_desc_disabled", + "sSortableNone": "sorting_disabled", + "sSortColumn": "sorting_", + /* Note that an int is postfixed for the sorting order */ + + /* Filtering */ + "sFilterInput": "", + + /* Page length */ + "sLengthSelect": "", + + /* Scrolling */ + "sScrollWrapper": "dataTables_scroll", + "sScrollHead": "dataTables_scrollHead", + "sScrollHeadInner": "dataTables_scrollHeadInner", + "sScrollBody": "dataTables_scrollBody", + "sScrollFoot": "dataTables_scrollFoot", + "sScrollFootInner": "dataTables_scrollFootInner", + + /* Misc */ + "sHeaderTH": "", + "sFooterTH": "", + + // Deprecated + "sSortJUIAsc": "", + "sSortJUIDesc": "", + "sSortJUI": "", + "sSortJUIAscAllowed": "", + "sSortJUIDescAllowed": "", + "sSortJUIWrapper": "", + "sSortIcon": "", + "sJUIHeader": "", + "sJUIFooter": "" + }); + + + (function () { + + // Reused strings for better compression. Closure compiler appears to have a + // weird edge case where it is trying to expand strings rather than use the + // variable version. This results in about 200 bytes being added, for very + // little preference benefit since it this run on script load only. + var _empty = ''; + _empty = ''; + + var _stateDefault = _empty + 'ui-state-default'; + var _sortIcon = _empty + 'css_right ui-icon ui-icon-'; + var _headerFooter = _empty + 'fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix'; + + $.extend(DataTable.ext.oJUIClasses, DataTable.ext.classes, { + /* Full numbers paging buttons */ + "sPageButton": "fg-button ui-button " + _stateDefault, + "sPageButtonActive": "ui-state-disabled", + "sPageButtonDisabled": "ui-state-disabled", + + /* Features */ + "sPaging": "dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi " + + "ui-buttonset-multi paging_", + /* Note that the type is postfixed */ + + /* Sorting */ + "sSortAsc": _stateDefault + " sorting_asc", + "sSortDesc": _stateDefault + " sorting_desc", + "sSortable": _stateDefault + " sorting", + "sSortableAsc": _stateDefault + " sorting_asc_disabled", + "sSortableDesc": _stateDefault + " sorting_desc_disabled", + "sSortableNone": _stateDefault + " sorting_disabled", + "sSortJUIAsc": _sortIcon + "triangle-1-n", + "sSortJUIDesc": _sortIcon + "triangle-1-s", + "sSortJUI": _sortIcon + "carat-2-n-s", + "sSortJUIAscAllowed": _sortIcon + "carat-1-n", + "sSortJUIDescAllowed": _sortIcon + "carat-1-s", + "sSortJUIWrapper": "DataTables_sort_wrapper", + "sSortIcon": "DataTables_sort_icon", + + /* Scrolling */ + "sScrollHead": "dataTables_scrollHead " + _stateDefault, + "sScrollFoot": "dataTables_scrollFoot " + _stateDefault, + + /* Misc */ + "sHeaderTH": _stateDefault, + "sFooterTH": _stateDefault, + "sJUIHeader": _headerFooter + " ui-corner-tl ui-corner-tr", + "sJUIFooter": _headerFooter + " ui-corner-bl ui-corner-br" + }); + + }()); + + + var extPagination = DataTable.ext.pager; + + function _numbers(page, pages) { + var + numbers = [], + buttons = extPagination.numbers_length, + half = Math.floor(buttons / 2), + i = 1; + + if (pages <= buttons) { + numbers = _range(0, pages); + } else if (page <= half) { + numbers = _range(0, buttons - 2); + numbers.push('ellipsis'); + numbers.push(pages - 1); + } else if (page >= pages - 1 - half) { + numbers = _range(pages - (buttons - 2), pages); + numbers.splice(0, 0, 'ellipsis'); // no unshift in ie6 + numbers.splice(0, 0, 0); + } else { + numbers = _range(page - half + 2, page + half - 1); + numbers.push('ellipsis'); + numbers.push(pages - 1); + numbers.splice(0, 0, 'ellipsis'); + numbers.splice(0, 0, 0); + } + + numbers.DT_el = 'span'; + return numbers; + } + + + $.extend(extPagination, { + simple: function (page, pages) { + return ['previous', 'next']; + }, + + full: function (page, pages) { + return ['first', 'previous', 'next', 'last']; + }, + + numbers: function (page, pages) { + return [_numbers(page, pages)]; + }, + + simple_numbers: function (page, pages) { + return ['previous', _numbers(page, pages), 'next']; + }, + + full_numbers: function (page, pages) { + return ['first', 'previous', _numbers(page, pages), 'next', 'last']; + }, + + // For testing and plug-ins to use + _numbers: _numbers, + + // Number of number buttons (including ellipsis) to show. _Must be odd!_ + numbers_length: 7 + }); + + + $.extend(true, DataTable.ext.renderer, { + pageButton: { + _: function (settings, host, idx, buttons, page, pages) { + var classes = settings.oClasses; + var lang = settings.oLanguage.oPaginate; + var aria = settings.oLanguage.oAria.paginate || {}; + var btnDisplay, btnClass, counter = 0; + + var attach = function (container, buttons) { + var i, ien, node, button; + var clickHandler = function (e) { + _fnPageChange(settings, e.data.action, true); + }; + + for (i = 0, ien = buttons.length; i < ien; i++) { + button = buttons[i]; + + if ($.isArray(button)) { + var inner = $('<' + (button.DT_el || 'div') + '/>') + .appendTo(container); + attach(inner, button); + } else { + btnDisplay = null; + btnClass = ''; + + switch (button) { + case 'ellipsis': + container.append('<span class="ellipsis">…</span>'); + break; + + case 'first': + btnDisplay = lang.sFirst; + btnClass = button + (page > 0 ? + '' : ' ' + classes.sPageButtonDisabled); + break; + + case 'previous': + btnDisplay = lang.sPrevious; + btnClass = button + (page > 0 ? + '' : ' ' + classes.sPageButtonDisabled); + break; + + case 'next': + btnDisplay = lang.sNext; + btnClass = button + (page < pages - 1 ? + '' : ' ' + classes.sPageButtonDisabled); + break; + + case 'last': + btnDisplay = lang.sLast; + btnClass = button + (page < pages - 1 ? + '' : ' ' + classes.sPageButtonDisabled); + break; + + default: + btnDisplay = button + 1; + btnClass = page === button ? + classes.sPageButtonActive : ''; + break; + } + + if (btnDisplay !== null) { + node = $('<a>', { + 'class': classes.sPageButton + ' ' + btnClass, + 'aria-controls': settings.sTableId, + 'aria-label': aria[button], + 'data-dt-idx': counter, + 'tabindex': settings.iTabIndex, + 'id': idx === 0 && typeof button === 'string' ? + settings.sTableId + '_' + button : null + }) + .html(btnDisplay) + .appendTo(container); + + _fnBindAction( + node, { + action: button + }, clickHandler + ); + + counter++; + } + } + } + }; + + // IE9 throws an 'unknown error' if document.activeElement is used + // inside an iframe or frame. Try / catch the error. Not good for + // accessibility, but neither are frames. + var activeEl; + + try { + // Because this approach is destroying and recreating the paging + // elements, focus is lost on the select button which is bad for + // accessibility. So we want to restore focus once the draw has + // completed + activeEl = $(host).find(document.activeElement).data('dt-idx'); + } catch (e) {} + + attach($(host).empty(), buttons); + + if (activeEl) { + $(host).find('[data-dt-idx=' + activeEl + ']').focus(); + } + } + } + }); + + + // Built in type detection. See model.ext.aTypes for information about + // what is required from this methods. + $.extend(DataTable.ext.type.detect, [ + // Plain numbers - first since V8 detects some plain numbers as dates + // e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...). + function (d, settings) { + var decimal = settings.oLanguage.sDecimal; + return _isNumber(d, decimal) ? 'num' + decimal : null; + }, + + // Dates (only those recognised by the browser's Date.parse) + function (d, settings) { + // V8 will remove any unknown characters at the start and end of the + // expression, leading to false matches such as `$245.12` or `10%` being + // a valid date. See forum thread 18941 for detail. + if (d && !(d instanceof Date) && (!_re_date_start.test(d) || !_re_date_end.test(d))) { + return null; + } + var parsed = Date.parse(d); + return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null; + }, + + // Formatted numbers + function (d, settings) { + var decimal = settings.oLanguage.sDecimal; + return _isNumber(d, decimal, true) ? 'num-fmt' + decimal : null; + }, + + // HTML numeric + function (d, settings) { + var decimal = settings.oLanguage.sDecimal; + return _htmlNumeric(d, decimal) ? 'html-num' + decimal : null; + }, + + // HTML numeric, formatted + function (d, settings) { + var decimal = settings.oLanguage.sDecimal; + return _htmlNumeric(d, decimal, true) ? 'html-num-fmt' + decimal : null; + }, + + // HTML (this is strict checking - there must be html) + function (d, settings) { + return _empty(d) || (typeof d === 'string' && d.indexOf('<') !== -1) ? + 'html' : null; + } + ]); + + + // Filter formatting functions. See model.ext.ofnSearch for information about + // what is required from these methods. + // + // Note that additional search methods are added for the html numbers and + // html formatted numbers by `_addNumericSort()` when we know what the decimal + // place is + + + $.extend(DataTable.ext.type.search, { + html: function (data) { + return _empty(data) ? + data : + typeof data === 'string' ? + data + .replace(_re_new_lines, " ") + .replace(_re_html, "") : + ''; + }, + + string: function (data) { + return _empty(data) ? + data : + typeof data === 'string' ? + data.replace(_re_new_lines, " ") : + data; + } + }); + + + var __numericReplace = function (d, decimalPlace, re1, re2) { + if (d !== 0 && (!d || d === '-')) { + return -Infinity; + } + + // If a decimal place other than `.` is used, it needs to be given to the + // function so we can detect it and replace with a `.` which is the only + // decimal place Javascript recognises - it is not locale aware. + if (decimalPlace) { + d = _numToDecimal(d, decimalPlace); + } + + if (d.replace) { + if (re1) { + d = d.replace(re1, ''); + } + + if (re2) { + d = d.replace(re2, ''); + } + } + + return d * 1; + }; + + + // Add the numeric 'deformatting' functions for sorting and search. This is done + // in a function to provide an easy ability for the language options to add + // additional methods if a non-period decimal place is used. + function _addNumericSort(decimalPlace) { + $.each({ + // Plain numbers + "num": function (d) { + return __numericReplace(d, decimalPlace); + }, + + // Formatted numbers + "num-fmt": function (d) { + return __numericReplace(d, decimalPlace, _re_formatted_numeric); + }, + + // HTML numeric + "html-num": function (d) { + return __numericReplace(d, decimalPlace, _re_html); + }, + + // HTML numeric, formatted + "html-num-fmt": function (d) { + return __numericReplace(d, decimalPlace, _re_html, _re_formatted_numeric); + } + }, + function (key, fn) { + // Add the ordering method + _ext.type.order[key + decimalPlace + '-pre'] = fn; + + // For HTML types add a search formatter that will strip the HTML + if (key.match(/^html\-/)) { + _ext.type.search[key + decimalPlace] = _ext.type.search.html; + } + } + ); + } + + + // Default sort methods + $.extend(_ext.type.order, { + // Dates + "date-pre": function (d) { + return Date.parse(d) || 0; + }, + + // html + "html-pre": function (a) { + return _empty(a) ? + '' : + a.replace ? + a.replace(/<.*?>/g, "").toLowerCase() : + a + ''; + }, + + // string + "string-pre": function (a) { + // This is a little complex, but faster than always calling toString, + // http://jsperf.com/tostring-v-check + return _empty(a) ? + '' : + typeof a === 'string' ? + a.toLowerCase() : + !a.toString ? + '' : + a.toString(); + }, + + // string-asc and -desc are retained only for compatibility with the old + // sort methods + "string-asc": function (x, y) { + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + }, + + "string-desc": function (x, y) { + return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + } + }); + + + // Numeric sorting types - order doesn't matter here + _addNumericSort(''); + + + $.extend(true, DataTable.ext.renderer, { + header: { + _: function (settings, cell, column, classes) { + // No additional mark-up required + // Attach a sort listener to update on sort - note that using the + // `DT` namespace will allow the event to be removed automatically + // on destroy, while the `dt` namespaced event is the one we are + // listening for + $(settings.nTable).on('order.dt.DT', function (e, ctx, sorting, columns) { + if (settings !== ctx) { // need to check this this is the host + return; // table, not a nested one + } + + var colIdx = column.idx; + + cell + .removeClass( + column.sSortingClass + ' ' + + classes.sSortAsc + ' ' + + classes.sSortDesc + ) + .addClass(columns[colIdx] == 'asc' ? + classes.sSortAsc : columns[colIdx] == 'desc' ? + classes.sSortDesc : + column.sSortingClass + ); + }); + }, + + jqueryui: function (settings, cell, column, classes) { + $('<div/>') + .addClass(classes.sSortJUIWrapper) + .append(cell.contents()) + .append($('<span/>') + .addClass(classes.sSortIcon + ' ' + column.sSortingClassJUI) + ) + .appendTo(cell); + + // Attach a sort listener to update on sort + $(settings.nTable).on('order.dt.DT', function (e, ctx, sorting, columns) { + if (settings !== ctx) { + return; + } + + var colIdx = column.idx; + + cell + .removeClass(classes.sSortAsc + " " + classes.sSortDesc) + .addClass(columns[colIdx] == 'asc' ? + classes.sSortAsc : columns[colIdx] == 'desc' ? + classes.sSortDesc : + column.sSortingClass + ); + + cell + .find('span.' + classes.sSortIcon) + .removeClass( + classes.sSortJUIAsc + " " + + classes.sSortJUIDesc + " " + + classes.sSortJUI + " " + + classes.sSortJUIAscAllowed + " " + + classes.sSortJUIDescAllowed + ) + .addClass(columns[colIdx] == 'asc' ? + classes.sSortJUIAsc : columns[colIdx] == 'desc' ? + classes.sSortJUIDesc : + column.sSortingClassJUI + ); + }); + } + } + }); + + /* + * Public helper functions. These aren't used internally by DataTables, or + * called by any of the options passed into DataTables, but they can be used + * externally by developers working with DataTables. They are helper functions + * to make working with DataTables a little bit easier. + */ + + var __htmlEscapeEntities = function (d) { + return typeof d === 'string' ? + d.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"') : + d; + }; + + /** + * Helpers for `columns.render`. + * + * The options defined here can be used with the `columns.render` initialisation + * option to provide a display renderer. The following functions are defined: + * + * * `number` - Will format numeric data (defined by `columns.data`) for + * display, retaining the original unformatted data for sorting and filtering. + * It takes 5 parameters: + * * `string` - Thousands grouping separator + * * `string` - Decimal point indicator + * * `integer` - Number of decimal points to show + * * `string` (optional) - Prefix. + * * `string` (optional) - Postfix (/suffix). + * * `text` - Escape HTML to help prevent XSS attacks. It has no optional + * parameters. + * + * @example + * // Column definition using the number renderer + * { + * data: "salary", + * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' ) + * } + * + * @namespace + */ + DataTable.render = { + number: function (thousands, decimal, precision, prefix, postfix) { + return { + display: function (d) { + if (typeof d !== 'number' && typeof d !== 'string') { + return d; + } + + var negative = d < 0 ? '-' : ''; + var flo = parseFloat(d); + + // If NaN then there isn't much formatting that we can do - just + // return immediately, escaping any HTML (this was supposed to + // be a number after all) + if (isNaN(flo)) { + return __htmlEscapeEntities(d); + } + + d = Math.abs(flo); + + var intPart = parseInt(d, 10); + var floatPart = precision ? + decimal + (d - intPart).toFixed(precision).substring(2) : + ''; + + return negative + (prefix || '') + + intPart.toString().replace( + /\B(?=(\d{3})+(?!\d))/g, thousands + ) + + floatPart + + (postfix || ''); + } + }; + }, + + text: function () { + return { + display: __htmlEscapeEntities + }; + } + }; + + + /* + * This is really a good bit rubbish this method of exposing the internal methods + * publicly... - To be fixed in 2.0 using methods on the prototype + */ + + + /** + * Create a wrapper function for exporting an internal functions to an external API. + * @param {string} fn API function name + * @returns {function} wrapped function + * @memberof DataTable#internal + */ + function _fnExternApiFunc(fn) { + return function () { + var args = [_fnSettingsFromNode(this[DataTable.ext.iApiIndex])].concat( + Array.prototype.slice.call(arguments) + ); + return DataTable.ext.internal[fn].apply(this, args); + }; + } + + + /** + * Reference to internal functions for use by plug-in developers. Note that + * these methods are references to internal functions and are considered to be + * private. If you use these methods, be aware that they are liable to change + * between versions. + * @namespace + */ + $.extend(DataTable.ext.internal, { + _fnExternApiFunc: _fnExternApiFunc, + _fnBuildAjax: _fnBuildAjax, + _fnAjaxUpdate: _fnAjaxUpdate, + _fnAjaxParameters: _fnAjaxParameters, + _fnAjaxUpdateDraw: _fnAjaxUpdateDraw, + _fnAjaxDataSrc: _fnAjaxDataSrc, + _fnAddColumn: _fnAddColumn, + _fnColumnOptions: _fnColumnOptions, + _fnAdjustColumnSizing: _fnAdjustColumnSizing, + _fnVisibleToColumnIndex: _fnVisibleToColumnIndex, + _fnColumnIndexToVisible: _fnColumnIndexToVisible, + _fnVisbleColumns: _fnVisbleColumns, + _fnGetColumns: _fnGetColumns, + _fnColumnTypes: _fnColumnTypes, + _fnApplyColumnDefs: _fnApplyColumnDefs, + _fnHungarianMap: _fnHungarianMap, + _fnCamelToHungarian: _fnCamelToHungarian, + _fnLanguageCompat: _fnLanguageCompat, + _fnBrowserDetect: _fnBrowserDetect, + _fnAddData: _fnAddData, + _fnAddTr: _fnAddTr, + _fnNodeToDataIndex: _fnNodeToDataIndex, + _fnNodeToColumnIndex: _fnNodeToColumnIndex, + _fnGetCellData: _fnGetCellData, + _fnSetCellData: _fnSetCellData, + _fnSplitObjNotation: _fnSplitObjNotation, + _fnGetObjectDataFn: _fnGetObjectDataFn, + _fnSetObjectDataFn: _fnSetObjectDataFn, + _fnGetDataMaster: _fnGetDataMaster, + _fnClearTable: _fnClearTable, + _fnDeleteIndex: _fnDeleteIndex, + _fnInvalidate: _fnInvalidate, + _fnGetRowElements: _fnGetRowElements, + _fnCreateTr: _fnCreateTr, + _fnBuildHead: _fnBuildHead, + _fnDrawHead: _fnDrawHead, + _fnDraw: _fnDraw, + _fnReDraw: _fnReDraw, + _fnAddOptionsHtml: _fnAddOptionsHtml, + _fnDetectHeader: _fnDetectHeader, + _fnGetUniqueThs: _fnGetUniqueThs, + _fnFeatureHtmlFilter: _fnFeatureHtmlFilter, + _fnFilterComplete: _fnFilterComplete, + _fnFilterCustom: _fnFilterCustom, + _fnFilterColumn: _fnFilterColumn, + _fnFilter: _fnFilter, + _fnFilterCreateSearch: _fnFilterCreateSearch, + _fnEscapeRegex: _fnEscapeRegex, + _fnFilterData: _fnFilterData, + _fnFeatureHtmlInfo: _fnFeatureHtmlInfo, + _fnUpdateInfo: _fnUpdateInfo, + _fnInfoMacros: _fnInfoMacros, + _fnInitialise: _fnInitialise, + _fnInitComplete: _fnInitComplete, + _fnLengthChange: _fnLengthChange, + _fnFeatureHtmlLength: _fnFeatureHtmlLength, + _fnFeatureHtmlPaginate: _fnFeatureHtmlPaginate, + _fnPageChange: _fnPageChange, + _fnFeatureHtmlProcessing: _fnFeatureHtmlProcessing, + _fnProcessingDisplay: _fnProcessingDisplay, + _fnFeatureHtmlTable: _fnFeatureHtmlTable, + _fnScrollDraw: _fnScrollDraw, + _fnApplyToChildren: _fnApplyToChildren, + _fnCalculateColumnWidths: _fnCalculateColumnWidths, + _fnThrottle: _fnThrottle, + _fnConvertToWidth: _fnConvertToWidth, + _fnGetWidestNode: _fnGetWidestNode, + _fnGetMaxLenString: _fnGetMaxLenString, + _fnStringToCss: _fnStringToCss, + _fnSortFlatten: _fnSortFlatten, + _fnSort: _fnSort, + _fnSortAria: _fnSortAria, + _fnSortListener: _fnSortListener, + _fnSortAttachListener: _fnSortAttachListener, + _fnSortingClasses: _fnSortingClasses, + _fnSortData: _fnSortData, + _fnSaveState: _fnSaveState, + _fnLoadState: _fnLoadState, + _fnSettingsFromNode: _fnSettingsFromNode, + _fnLog: _fnLog, + _fnMap: _fnMap, + _fnBindAction: _fnBindAction, + _fnCallbackReg: _fnCallbackReg, + _fnCallbackFire: _fnCallbackFire, + _fnLengthOverflow: _fnLengthOverflow, + _fnRenderer: _fnRenderer, + _fnDataSource: _fnDataSource, + _fnRowAttributes: _fnRowAttributes, + _fnCalculateEnd: function () {} // Used by a lot of plug-ins, but redundant + // in 1.10, so this dead-end function is + // added to prevent errors + }); + + + // jQuery access + $.fn.dataTable = DataTable; + + // Provide access to the host jQuery object (circular reference) + DataTable.$ = $; + + // Legacy aliases + $.fn.dataTableSettings = DataTable.settings; + $.fn.dataTableExt = DataTable.ext; + + // With a capital `D` we return a DataTables API instance rather than a + // jQuery object + $.fn.DataTable = function (opts) { + return $(this).dataTable(opts).api(); + }; + + // All properties that are available to $.fn.dataTable should also be + // available on $.fn.DataTable + $.each(DataTable, function (prop, val) { + $.fn.DataTable[prop] = val; + }); + + + // Information about events fired by DataTables - for documentation. + /** + * Draw event, fired whenever the table is redrawn on the page, at the same + * point as fnDrawCallback. This may be useful for binding events or + * performing calculations when the table is altered at all. + * @name DataTable#draw.dt + * @event + * @param {event} e jQuery event object + * @param {object} o DataTables settings object {@link DataTable.models.oSettings} + */ + + /** + * Search event, fired when the searching applied to the table (using the + * built-in global search, or column filters) is altered. + * @name DataTable#search.dt + * @event + * @param {event} e jQuery event object + * @param {object} o DataTables settings object {@link DataTable.models.oSettings} + */ + + /** + * Page change event, fired when the paging of the table is altered. + * @name DataTable#page.dt + * @event + * @param {event} e jQuery event object + * @param {object} o DataTables settings object {@link DataTable.models.oSettings} + */ + + /** + * Order event, fired when the ordering applied to the table is altered. + * @name DataTable#order.dt + * @event + * @param {event} e jQuery event object + * @param {object} o DataTables settings object {@link DataTable.models.oSettings} + */ + + /** + * DataTables initialisation complete event, fired when the table is fully + * drawn, including Ajax data loaded, if Ajax data is required. + * @name DataTable#init.dt + * @event + * @param {event} e jQuery event object + * @param {object} oSettings DataTables settings object + * @param {object} json The JSON object request from the server - only + * present if client-side Ajax sourced data is used</li></ol> + */ + + /** + * State save event, fired when the table has changed state a new state save + * is required. This event allows modification of the state saving object + * prior to actually doing the save, including addition or other state + * properties (for plug-ins) or modification of a DataTables core property. + * @name DataTable#stateSaveParams.dt + * @event + * @param {event} e jQuery event object + * @param {object} oSettings DataTables settings object + * @param {object} json The state information to be saved + */ + + /** + * State load event, fired when the table is loading state from the stored + * data, but prior to the settings object being modified by the saved state + * - allowing modification of the saved state is required or loading of + * state for a plug-in. + * @name DataTable#stateLoadParams.dt + * @event + * @param {event} e jQuery event object + * @param {object} oSettings DataTables settings object + * @param {object} json The saved state information + */ + + /** + * State loaded event, fired when state has been loaded from stored data and + * the settings object has been modified by the loaded data. + * @name DataTable#stateLoaded.dt + * @event + * @param {event} e jQuery event object + * @param {object} oSettings DataTables settings object + * @param {object} json The saved state information + */ + + /** + * Processing event, fired when DataTables is doing some kind of processing + * (be it, order, searcg or anything else). It can be used to indicate to + * the end user that there is something happening, or that something has + * finished. + * @name DataTable#processing.dt + * @event + * @param {event} e jQuery event object + * @param {object} oSettings DataTables settings object + * @param {boolean} bShow Flag for if DataTables is doing processing or not + */ + + /** + * Ajax (XHR) event, fired whenever an Ajax request is completed from a + * request to made to the server for new data. This event is called before + * DataTables processed the returned data, so it can also be used to pre- + * process the data returned from the server, if needed. + * + * Note that this trigger is called in `fnServerData`, if you override + * `fnServerData` and which to use this event, you need to trigger it in you + * success function. + * @name DataTable#xhr.dt + * @event + * @param {event} e jQuery event object + * @param {object} o DataTables settings object {@link DataTable.models.oSettings} + * @param {object} json JSON returned from the server + * + * @example + * // Use a custom property returned from the server in another DOM element + * $('#table').dataTable().on('xhr.dt', function (e, settings, json) { + * $('#status').html( json.status ); + * } ); + * + * @example + * // Pre-process the data returned from the server + * $('#table').dataTable().on('xhr.dt', function (e, settings, json) { + * for ( var i=0, ien=json.aaData.length ; i<ien ; i++ ) { + * json.aaData[i].sum = json.aaData[i].one + json.aaData[i].two; + * } + * // Note no return - manipulate the data directly in the JSON object. + * } ); + */ + + /** + * Destroy event, fired when the DataTable is destroyed by calling fnDestroy + * or passing the bDestroy:true parameter in the initialisation object. This + * can be used to remove bound events, added DOM nodes, etc. + * @name DataTable#destroy.dt + * @event + * @param {event} e jQuery event object + * @param {object} o DataTables settings object {@link DataTable.models.oSettings} + */ + + /** + * Page length change event, fired when number of records to show on each + * page (the length) is changed. + * @name DataTable#length.dt + * @event + * @param {event} e jQuery event object + * @param {object} o DataTables settings object {@link DataTable.models.oSettings} + * @param {integer} len New length + */ + + /** + * Column sizing has changed. + * @name DataTable#column-sizing.dt + * @event + * @param {event} e jQuery event object + * @param {object} o DataTables settings object {@link DataTable.models.oSettings} + */ + + /** + * Column visibility has changed. + * @name DataTable#column-visibility.dt + * @event + * @param {event} e jQuery event object + * @param {object} o DataTables settings object {@link DataTable.models.oSettings} + * @param {int} column Column index + * @param {bool} vis `false` if column now hidden, or `true` if visible + */ + + return $.fn.dataTable; + })); diff --git a/js/xloadtree/xtree2.js b/js/xloadtree/xtree2.js index c7feecbc..ebac7e14 100644 --- a/js/xloadtree/xtree2.js +++ b/js/xloadtree/xtree2.js @@ -54,11 +54,12 @@ // WebFXTreePersisitance function WebFXTreePersistence() {} var _p = WebFXTreePersistence.prototype; -_p.getExpanded = function (oNode) { return false; }; +_p.getExpanded = function (oNode) { + return false; +}; _p.setExpanded = function (oNode, bOpen) {}; - // Cookie handling function WebFXCookie() {} @@ -128,7 +129,6 @@ _p.setExpanded = function (oNode, bOpen) { }; - // this object provides a few useful methods when working with arrays var arrayHelper = { indexOf: function (a, o) { @@ -161,25 +161,25 @@ var arrayHelper = { // WebFX Tree Config object // /////////////////////////////////////////////////////////////////////////////// var webFXTreeConfig = { - rootIcon : "images/folder.png", - openRootIcon : "images/openfolder.png", - folderIcon : "images/folder.png", - openFolderIcon : "images/openfolder.png", - fileIcon : "images/file.png", - iIcon : "images/I.png", - lIcon : "images/L.png", - lMinusIcon : "images/Lminus.png", - lPlusIcon : "images/Lplus.png", - tIcon : "images/T.png", - tMinusIcon : "images/Tminus.png", - tPlusIcon : "images/Tplus.png", - plusIcon : "images/plus.png", - minusIcon : "images/minus.png", - blankIcon : "images/blank.png", - defaultText : "Tree Item", - defaultAction : null, - defaultBehavior : "classic", - usePersistence : true + rootIcon: "images/folder.png", + openRootIcon: "images/openfolder.png", + folderIcon: "images/folder.png", + openFolderIcon: "images/openfolder.png", + fileIcon: "images/file.png", + iIcon: "images/I.png", + lIcon: "images/L.png", + lMinusIcon: "images/Lminus.png", + lPlusIcon: "images/Lplus.png", + tIcon: "images/T.png", + tMinusIcon: "images/Tminus.png", + tPlusIcon: "images/Tplus.png", + plusIcon: "images/plus.png", + minusIcon: "images/minus.png", + blankIcon: "images/blank.png", + defaultText: "Tree Item", + defaultAction: null, + defaultBehavior: "classic", + usePersistence: true }; /////////////////////////////////////////////////////////////////////////////// @@ -201,7 +201,7 @@ var webFXTreeHandler = { addNode: function (oNode) { this.all[oNode.id] = oNode; }, - removeNode: function (oNode) { + removeNode: function (oNode) { delete this.all[oNode.id]; }, @@ -235,24 +235,24 @@ var webFXTreeHandler = { _htmlToText: function (s) { switch (s) { - case "&": - return "&"; - case "<": - return "<"; - case ">": - return ">"; - case """: - return "\""; - case " ": - return String.fromCharCode(160); - default: - if (/\s+/.test(s)) { - return " "; - } - if (/^<BR/gi.test(s)) { - return "\n"; - } - return ""; + case "&": + return "&"; + case "<": + return "<"; + case ">": + return ">"; + case """: + return "\""; + case " ": + return String.fromCharCode(160); + default: + if (/\s+/.test(s)) { + return " "; + } + if (/^<BR/gi.test(s)) { + return "\n"; + } + return ""; } }, @@ -262,18 +262,18 @@ var webFXTreeHandler = { _textToHtml: function (s) { switch (s) { - case "&": - return "&"; - case "<": - return "<"; - case ">": - return ">"; - case "\n": - return "<BR>"; - case "\"": - return """; // so we can use this in attributes - default: - return " "; + case "&": + return "&"; + case "<": + return "<"; + case ">": + return ">"; + case "\n": + return "<BR>"; + case "\"": + return """; // so we can use this in attributes + default: + return " "; } }, @@ -300,7 +300,7 @@ function WebFXTreeAbstractNode(sText, oAction, oIconAction) { _p = WebFXTreeAbstractNode.prototype; _p._selected = false; -_p.indentWidth = 19; +_p.indentWidth = 15; _p.open = false; _p.text = webFXTreeConfig.defaultText; _p.action = null; @@ -381,7 +381,6 @@ _p.add = function (oChild, oBefore) { }; - _p.remove = function (oChild) { // backwards compatible. If no argument remove the node if (arguments.length == 0) { @@ -586,7 +585,7 @@ _p._setSelected = function (b) { if (this._selected != b) { this._selected = b; - var wasFocused = false; // used to keep focus state + var wasFocused = false; // used to keep focus state var si = t.getSelected(); if (b && si != null && si != this) { var oldFireChange = t._fireChange; @@ -893,24 +892,24 @@ _p.getExpandIconSrc = function () { } switch (bits) { - case 1: - return webFXTreeConfig.plusIcon; - case 2: - return webFXTreeConfig.minusIcon; - case 4: - return webFXTreeConfig.lIcon; - case 5: - return webFXTreeConfig.lPlusIcon; - case 6: - return webFXTreeConfig.lMinusIcon; - case 8: - return webFXTreeConfig.tIcon; - case 9: - return webFXTreeConfig.tPlusIcon; - case 10: - return webFXTreeConfig.tMinusIcon; - default: // 0 - return webFXTreeConfig.blankIcon; + case 1: + return webFXTreeConfig.plusIcon; + case 2: + return webFXTreeConfig.minusIcon; + case 4: + return webFXTreeConfig.lIcon; + case 5: + return webFXTreeConfig.lPlusIcon; + case 6: + return webFXTreeConfig.lMinusIcon; + case 8: + return webFXTreeConfig.tIcon; + case 9: + return webFXTreeConfig.tPlusIcon; + case 10: + return webFXTreeConfig.tMinusIcon; + default: // 0 + return webFXTreeConfig.blankIcon; } } else { if (t && hideLines) { @@ -1079,8 +1078,8 @@ _p.setIconAction = function (oAction) { var el = this.getIconElement(); if (el) { el.href = this._getIconHref(); - } -} + } +} _p.getIconAction = function () { if (this.iconAction) @@ -1144,8 +1143,8 @@ _p._onmousedown = function (e) { } this.select(); - if (/*!/webfx-tree-item-label/.test(el.className) && */!webFXTreeHandler.opera) { // opera cancels the click if focus is called - + if ( /*!/webfx-tree-item-label/.test(el.className) && */ !webFXTreeHandler.opera) { // opera cancels the click if focus is called + // in case we are not clicking on the label if (webFXTreeHandler.ie) { window.setTimeout("WebFXTreeAbstractNode._onTimeoutFocus(\"" + this.id + "\")", 10); @@ -1170,7 +1169,7 @@ _p._onclick = function (e) { var doAction = null; if (/webfx-tree-icon/.test(el.className) && this.iconAction) { - doAction = this.iconAction; + doAction = this.iconAction; } else { doAction = this.action; } @@ -1207,52 +1206,52 @@ _p._onkeydown = function (e) { var n; var rv = true; switch (e.keyCode) { - case 39: // RIGHT - if (e.altKey) { - rv = true; - break; - } - if (this.hasChildren()) { - if (!this.getExpanded()) { - this.setExpanded(true); - } else { - this.getFirstChild().focus(); - } - } - rv = false; + case 39: // RIGHT + if (e.altKey) { + rv = true; break; - case 37: // LEFT - if (e.altKey) { - rv = true; - break; - } - if (this.hasChildren() && this.getExpanded()) { - this.setExpanded(false); + } + if (this.hasChildren()) { + if (!this.getExpanded()) { + this.setExpanded(true); } else { - var p = this.getParent(); - var t = this.getTree(); - // don't go to root if hidden - if (p && (t.showRootNode || p != t)) { - p.focus(); - } + this.getFirstChild().focus(); } - rv = false; - break; - - case 40: // DOWN - n = this.getNextShownNode(); - if (n) { - n.focus(); - } - rv = false; + } + rv = false; + break; + case 37: // LEFT + if (e.altKey) { + rv = true; break; - case 38: // UP - n = this.getPreviousShownNode() - if (n) { - n.focus(); + } + if (this.hasChildren() && this.getExpanded()) { + this.setExpanded(false); + } else { + var p = this.getParent(); + var t = this.getTree(); + // don't go to root if hidden + if (p && (t.showRootNode || p != t)) { + p.focus(); } - rv = false; - break; + } + rv = false; + break; + + case 40: // DOWN + n = this.getNextShownNode(); + if (n) { + n.focus(); + } + rv = false; + break; + case 38: // UP + n = this.getPreviousShownNode() + if (n) { + n.focus(); + } + rv = false; + break; } if (!rv && e.preventDefault) { @@ -1325,11 +1324,6 @@ _p.getPreviousShownNode = function () { }; - - - - - /////////////////////////////////////////////////////////////////////////////// // WebFXTree /////////////////////////////////////////////////////////////////////////////// @@ -1412,7 +1406,7 @@ _p.getRowClassName = function () { _p.getIconSrc = function () { var behavior = this.getTree() ? this.getTree().getBehavior() : webFXTreeConfig.defaultBehavior; var open = behavior == "classic" && this.getExpanded() || - behavior != "classic" && this.isSelected(); + behavior != "classic" && this.isSelected(); if (open && this.openIcon) { return this.openIcon; } @@ -1454,7 +1448,7 @@ _p.setTabIndex = function (i) { this.tabIndex = i; if (n) { n._setTabIndex(i); - } + } }; _p.getTabIndex = function () { @@ -1545,7 +1539,6 @@ _p.getSuspendRedraw = function () { }; - /////////////////////////////////////////////////////////////////////////////// // WebFXTreeItem /////////////////////////////////////////////////////////////////////////////// @@ -1593,7 +1586,7 @@ _p.getCreated = function () { _p.getIconSrc = function () { var behavior = this.getTree() ? this.getTree().getBehavior() : webFXTreeConfig.defaultBehavior; var open = behavior == "classic" && this.getExpanded() || - behavior != "classic" && this.isSelected(); + behavior != "classic" && this.isSelected(); if (open && this.openIcon) { return this.openIcon; } @@ -1611,8 +1604,6 @@ _p.getIconSrc = function () { /* end tree model */ - - if (window.attachEvent) { window.attachEvent("onunload", function () { for (var id in webFXTreeHandler.all) diff --git a/logout.php b/logout.php deleted file mode 100644 index 7e73ebda..00000000 --- a/logout.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php - -/** - * Logs a user out of the app - * - * $Id: logout.php,v 1.3 2003/09/10 01:55:52 chriskl Exp $ - */ - -if (!ini_get('session.auto_start')) { - session_name('PPA_ID'); - session_start(); -} -unset($_SESSION); -session_destroy(); - -header('Location: index.php'); diff --git a/src/classes/Highlight.php b/src/classes/Highlight.php new file mode 100644 index 00000000..1cbb0d2a --- /dev/null +++ b/src/classes/Highlight.php @@ -0,0 +1,1112 @@ +<?php +namespace PHPPgAdmin; + +/* This software is licensed through a BSD-style License. + * http://www.opensource.org/licenses/bsd-license.php + +Copyright (c) 2003, 2004, Jacob D. Cohen +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +Neither the name of Jacob D. Cohen nor the names of his contributors +may be used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + */ + +class Highlight { + + const NORMAL_TEXT = 1; + const DQ_LITERAL = 2; + const DQ_ESCAPE = 3; + const SQ_LITERAL = 4; + const SQ_ESCAPE = 5; + const SLASH_BEGIN = 6; + const STAR_COMMENT = 7; + const STAR_END = 8; + const LINE_COMMENT = 9; + const HTML_ENTITY = 10; + const LC_ESCAPE = 11; + const BLOCK_COMMENT = 12; + const PAREN_BEGIN = 13; + const DASH_BEGIN = 14; + const BT_LITERAL = 15; + const BT_ESCAPE = 16; + const XML_TAG_BEGIN = 17; + const XML_TAG = 18; + const XML_PI = 19; + const SCH_NORMAL = 20; + const SCH_STRESC = 21; + const SCH_IDEXPR = 22; + const SCH_NUMLIT = 23; + const SCH_CHRLIT = 24; + const SCH_STRLIT = 25; + public $initial_state = ["Scheme" => self::SCH_NORMAL]; + public $sch = []; + public $c89 = []; + public $c99 = []; + public $cpp = []; + public $cs = []; + public $edges = []; + public $java = []; + public $mirc = []; + public $pascal = []; + public $perl = []; + public $php = []; + public $pli = []; + public $process = []; + public $process_end = []; + public $python = []; + public $ruby = []; + public $sql = []; + public $states = []; + public $vb = []; + public $xml = []; + + /* Constructor */ + + function __construct() { + + $keyword_replace = function ($keywords, $text, $ncs = false) { + $cm = ($ncs) ? "i" : ""; + foreach ($keywords as $keyword) { + $search[] = "/(\\b$keyword\\b)/" . $cm; + $replace[] = '<span class="keyword">\\0</span>'; + } + + $search[] = "/(\\bclass\s)/"; + $replace[] = '<span class="keyword">\\0</span>'; + + return preg_replace($search, $replace, $text); + }; + + $preproc_replace = function ($preproc, $text) { + foreach ($preproc as $proc) { + $search[] = "/(\\s*#\s*$proc\\b)/"; + $replace[] = '<span class="keyword">\\0</span>'; + } + + return preg_replace($search, $replace, $text); + }; + + $sch_syntax_helper = function ($text) { + return $text; + }; + + $syntax_highlight_helper = function ($text, $language) use ($keyword_replace, $preproc_replac) { + $preproc = []; + $preproc["C++"] = [ + "if", "ifdef", "ifndef", "elif", "else", + "endif", "include", "define", "undef", "line", + "error", "pragma"]; + $preproc["C89"] = &$preproc["C++"]; + $preproc["C"] = &$preproc["C89"]; + + $keywords = [ + "C++" => [ + "asm", "auto", "bool", "break", "case", + "catch", "char", /*class*/"const", "const_cast", + "continue", "default", "delete", "do", "double", + "dynamic_cast", "else", "enum", "explicit", "export", + "extern", "false", "float", "for", "friend", + "goto", "if", "inline", "int", "long", + "mutable", "namespace", "new", "operator", "private", + "protected", "public", "register", "reinterpret_cast", "return", + "short", "signed", "sizeof", "static", "static_cast", + "struct", "switch", "template", "this", "throw", + "true", "try", "typedef", "typeid", "typename", + "union", "unsigned", "using", "virtual", "void", + "volatile", "wchar_t", "while"], + + "C89" => [ + "auto", "break", "case", "char", "const", + "continue", "default", "do", "double", "else", + "enum", "extern", "float", "for", "goto", + "if", "int", "long", "register", "return", + "short", "signed", "sizeof", "static", "struct", + "switch", "typedef", "union", "unsigned", "void", + "volatile", "while"], + + "C" => [ + "auto", "break", "case", "char", "const", + "continue", "default", "do", "double", "else", + "enum", "extern", "float", "for", "goto", + "if", "int", "long", "register", "return", + "short", "signed", "sizeof", "static", "struct", + "switch", "typedef", "union", "unsigned", "void", + "volatile", "while", "__restrict", "_Bool"], + + "PHP" => [ + "and", "or", "xor", "__FILE__", "__LINE__", + "array", "as", "break", "case", "cfunction", + /*class*/"const", "continue", "declare", "default", + "die", "do", "echo", "else", "elseif", + "empty", "enddeclare", "endfor", "endforeach", "endif", + "endswitch", "endwhile", "eval", "exit", "extends", + "for", "foreach", "function", "global", "if", + "include", "include_once", "isset", "list", "new", + "old_function", "print", "require", "require_once", "return", + "static", "switch", "unset", "use", "var", + "while", "__FUNCTION__", "__CLASS__"], + + "Perl" => [ + "-A", "-B", "-C", "-M", "-O", + "-R", "-S", "-T", "-W", "-X", + "-b", "-c", "-d", "-e", "-f", + "-g", "-k", "-l", "-o", "-p", + "-r", "-s", "-t", "-u", "-w", + "-x", "-z", "ARGV", "DATA", "ENV", + "SIG", "STDERR", "STDIN", "STDOUT", "atan2", + "bind", "binmode", "bless", "caller", "chdir", + "chmod", "chomp", "chop", "chown", "chr", + "chroot", "close", "closedir", "cmp", "connect", + "continue", "cos", "crypt", "dbmclose", "dbmopen", + "defined", "delete", "die", "do", "dump", + "each", "else", "elsif", "endgrent", "endhostent", + "endnetent", "endprotent", "endpwent", "endservent", "eof", + "eq", "eval", "exec", "exists", "exit", + "exp", "fcntl", "fileno", "flock", "for", + "foreach", "fork", "format", "formline", "ge", + "getc", "getgrent", "getgrid", "getgrnam", "gethostbyaddr", + "gethostbyname", "gethostent", "getlogin", "getnetbyaddr", "getnetbyname", + "getnetent", "getpeername", "getpgrp", "getppid", "getpriority", + "getprotobyname", "getprotobynumber", "getprotoent", "getpwent", "getpwnam", + "getpwuid", "getservbyname", "getservbyport", "getservent", "getsockname", + "getsockopt", "glob", "gmtime", "goto", "grep", + /*gt*/"hex", "if", "import", "index", + "int", "ioctl", "join", "keys", "kill", + "last", "lc", "lcfirst", "le", "length", + "link", "listen", "local", "localtime", "log", + "lstat", /*lt*/"m", "map", "mkdir", + "msgctl", "msgget", "msgrcv", "msgsnd", "my", + "ne", "next", "no", "oct", "open", + "opendir", "ord", "pack", "package", "pipe", + "pop", "pos", "print", "printf", "push", + "q", "qq", "quotemeta", "qw", "qx", + "rand", "read", "readdir", "readlink", "recv", + "redo", "ref", "refname", "require", "reset", + "return", "reverse", "rewinddir", "rindex", "rmdir", + "s", "scalar", "seek", "seekdir", "select", + "semctl", "semget", "semop", "send", "setgrent", + "sethostent", "setnetent", "setpgrp", "setpriority", "setprotoent", + "setpwent", "setservent", "setsockopt", "shift", "shmctl", + "shmget", "shmread", "shmwrite", "shutdown", "sin", + "sleep", "socket", "socketpair", "sort", "splice", + "split", "sprintf", "sqrt", "srand", "stat", + "study", "sub", "substr", "symlink", "syscall", + "sysopen", "sysread", "system", "syswrite", "tell", + "telldir", "tie", "tied", "time", "times", + "tr", "truncate", "uc", "ucfirst", "umask", + "undef", "unless", "unlink", "unpack", "unshift", + "untie", "until", "use", "utime", "values", + "vec", "wait", "waitpid", "wantarray", "warn", + "while", "write", "y", "or", "and", + "not"], + + "Java" => [ + "abstract", "boolean", "break", "byte", "case", + "catch", "char", /*class*/"const", "continue", + "default", "do", "double", "else", "extends", + "final", "finally", "float", "for", "goto", + "if", "implements", "import", "instanceof", "int", + "interface", "long", "native", "new", "package", + "private", "protected", "public", "return", "short", + "static", "strictfp", "super", "switch", "synchronized", + "this", "throw", "throws", "transient", "try", + "void", "volatile", "while"], + + "VB" => [ + "AddressOf", "Alias", "And", "Any", "As", + "Binary", "Boolean", "ByRef", "Byte", "ByVal", + "Call", "Case", "CBool", "CByte", "CCur", + "CDate", "CDbl", "CInt", "CLng", "Close", + "Const", "CSng", "CStr", "Currency", "CVar", + "CVErr", "Date", "Debug", "Declare", "DefBool", + "DefByte", "DefCur", "DefDate", "DefDbl", "DefInt", + "DefLng", "DefObj", "DefSng", "DefStr", "DefVar", + "Dim", "Do", "Double", "Each", "Else", + "End", "Enum", "Eqv", "Erase", "Error", + "Event", "Exit", "For", "Friend", "Function", + "Get", "Get", "Global", "GoSub", "GoTo", + "If", "Imp", "Implements", "In", "Input", + "Integer", "Is", "LBound", "Len", "Let", + "Lib", "Like", "Line", "Lock", "Long", + "Loop", "LSet", "Mod", "Name", "Next", + "Not", "Nothing", "Null", "Object", "On", + "Open", "Option Base 1", "Option Compare Binary", + "Option Compare Database", "Option Compare Text", "Option Explicit", + "Option Private Module", "Optional", "Or", "Output", + "ParamArray", "Preserve", "Print", "Private", "Property", + "Public", "Put", "RaiseEvent", "Random", "Read", + "ReDim", "Resume", "Return", "RSet", "Seek", + "Select", "Set", "Single", "Spc", "Static", + "Step", "Stop", "String", "Sub", "Tab", + "Then", "To", "Type", "UBound", "Unlock", + "Variant", "Wend", "While", "With", "WithEvents", + "Write", "Xor"], + + "C#" => [ + "abstract", "as", "base", "bool", "break", + "byte", "case", "catch", "char", "checked", + /*class*/"const", "continue", "decimal", "default", + "delegate", "do", "double", "else", "enum", + "event", "explicit", "extern", "false", "finally", + "fixed", "float", "for", "foreach", "goto", + "if", "implicit", "in", "int", "interface", + "internal", "is", "lock", "long", "namespace", + "new", "null", "object", "operator", "out", + "override", "params", "private", "protected", "public", + "readonly", "ref", "return", "sbyte", "sealed", + "short", "sizeof", "stackalloc", "static", "string", + "struct", "switch", "this", "throw", "true", + "try", "typeof", "uint", "ulong", "unchecked", + "unsafe", "ushort", "using", "virtual", "volatile", + "void", "while"], + + "Ruby" => [ + "alias", "and", "begin", "break", "case", + /*class*/"def", "defined", "do", "else", + "elsif", "end", "ensure", "false", "for", + "if", "in", "module", "next", "module", + "next", "nil", "not", "or", "redo", + "rescue", "retry", "return", "self", "super", + "then", "true", "undef", "unless", "until", + "when", "while", "yield"], + + "Python" => [ + "and", "assert", "break", /*"class",*/"continue", + "def", "del", "elif", "else", "except", + "exec", "finally", "for", "from", "global", + "if", "import", "in", "is", "lambda", + "not", "or", "pass", "print", "raise", + "return", "try", "while", "yield"], + + "Pascal" => [ + "Absolute", "Abstract", "All", "And", "And_then", + "Array", "Asm", "Begin", "Bindable", "Case", + /*"Class",*/"Const", "Constructor", "Destructor", "Div", + "Do", "Downto", "Else", "End", "Export", + "File", "For", "Function", "Goto", "If", + "Import", "Implementation", "Inherited", "In", "Inline", + "Interface", "Is", "Label", "Mod", "Module", + "Nil", "Not", "Object", "Of", "Only", + "Operator", "Or", "Or_else", "Otherwise", "Packed", + "Pow", "Procedure", "Program", "Property", "Protected", + "Qualified", "Record", "Repeat", "Restricted", "Set", + "Shl", "Shr", "Then", "To", "Type", + "Unit", "Until", "Uses", "Value", "Var", + "View", "Virtual", "While", "With", "Xor"], + + "mIRC" => [ + ], + + "PL/I" => [ + "A", "ABS", "ACOS", "%ACTIVATE", "ACTUALCOUNT", + "ADD", "ADDR", "ADDREL", "ALIGNED", "ALLOCATE", + "ALLOC", "ALLOCATION", "ALLOCN", "ANY", "ANYCONDITION", + "APPEND", "AREA", "ASIN", "ATAN", "ATAND", + "ATANH", "AUTOMATIC", "AUTO", "B", "B1", + "B2", "B3", "B4", "BACKUP_DATE", "BASED", + "BATCH", "BEGIN", "BINARY", "BIN", "BIT", + "BLOCK_BOUNDARY_FORMAT", "BLOCK_IO", "BLOCK_SIZE", "BOOL", + "BUCKET_SIZE", "BUILTIN", "BY", "BYTE", "BYTESIZE", + "CALL", "CANCEL_CONTROL_O", "CARRIAGE_RETURN_FORMAT", + "CEIL", "CHAR", "CHARACTER", "CLOSE", "COLLATE", "COLUMN", + "CONDITION", "CONTIGUOUS", "CONTIGUOUS_BEST_TRY", "CONTROLLED", + "CONVERSION", "COPY", "COS", "COSD", "COSH", + "CREATION_DATE", "CURRENT_POSITION", "DATE", + "DATETIME", "%DEACTIVATE", "DECIMAL", "DEC", "%DECLARE", + "%DCL", "DECLARE", "DCL", "DECODE", "DEFAULT_FILE_NAME", + "DEFERRED_WRITE", "DEFINED", "DEF", "DELETE", + "DESCRIPTOR", "%DICTIONARY", "DIMENSION", "DIM", "DIRECT", + "DISPLAY", "DIVIDE", "%DO", "DO", "E", + "EDIT", "%ELSE", "ELSE", "EMPTY", "ENCODE", + "%END", "END", "ENDFILE", "ENDPAGE", "ENTRY", + "ENVIRONMENT", "ENV", "%ERROR", "ERROR", "EVERY", + "EXP", "EXPIRATION_DATE", "EXTEND", "EXTENSION_SIZE", + "EXTERNAL", "EXT", "F", "FAST_DELETE", "%FATAL", + "FILE", "FILE_ID", "FILE_ID_TO", "FILE_SIZE", + "FINISH", "FIXED", "FIXEDOVERFLOW", "FOFL", + "FIXED_CONTROL_FROM", "FIXED_CONTROL_SIZE", "FIXED_CONTROL_SIZE_TO", + "FIXED_CONTROL_TO", "FIXED_LENGTH_RECORDS", "FLOAT", + "FLOOR", "FLUSH", "FORMAT", "FREE", "FROM", + "GET", "GLOBALDEF", "GLOBALREF", "%GOTO", + "GOTO", "GO", "TO", "GROUP_PROTETION", "HBOUND", + "HIGH", "INDENT", "%IF", "IF", "IGNORE_LINE_MARKS", + "IN", "%INCLUDE", "INDEX", "INDEXED", "INDEX_NUMBER", + "%INFORM", "INFORM", "INITIAL", "INIT", "INITIAL_FILL", + "INPUT", "INT", "INTERNAL", "INTO", "KEY", + "KEYED", "KEYFROM", "KEYTO", "LABEL", "LBOUND", + "LEAVE", "LENGTH", "LIKE", "LINE", "LINENO", + "LINESIZE", "%LIST", "LIST", "LOCK_ON_READ", "LOCK_ON_WRITE", + "LOG", "LOG10", "LOG2", "LOW", "LTRIM", + "MAIN", "MANUAL_UNLOCKING", "MATCH_GREATER", + "MATCH_GREATER_EQUAL", "MATCH_NEXT", "MATCH_NEXT_EQUAL", + "MAX", "MAXIMUM_RECORD_NUMBER", "MAXIMUM_RECORD_SIZE", + "MAXLENGTH", "MEMBER", "MIN", "MOD", "MULTIBLOCK_COUNT", + "MULTIBUFFER_COUNT", "MULTIPLY", "NEXT_VOLUME", "%NOLIST", + "NOLOCK", "NONEXISTENT_RECORD", "NONRECURSIVE", "NONVARYING", + "NONVAR", "NORESCAN", "NO_ECHO", "NO_FILTER", "NO_SHARE", + "NULL", "OFFSET", "ON", "ONARGSLIST", "ONCHAR", + "ONCODE", "ONFILE", "ONKEY", "ONSOURCE", "OPEN", + "OPTIONAL", "OPTIONS", "OTHERWISE", "OTHER", "OUTPUT", + "OVERFLOW", "OFL", "OWNER_GROUP", "OWNER_ID", + "OWNER_MEMBER", "OWNER_PROTECTION", "P", "%PAGE", + "PAGE", "PAGENO", "PAGESIZE", "PARAMETER", "PARM", + "PICTURE", "PIC", "POINTER", "PTR", "POSINT", + "POSITION", "POS", "PRECISION", "PREC", "PRESENT", + "PRINT", "PRINTER_FORMAT", "%PROCEDURE", "%PROC", + "PROCEDURE", "PROC", "PROD", "PROMPT", "PURGE_TYPE_AHEAD", + "PUT", "R", "RANK", "READ", "READONLY", + "READ_AHEAD", "READ_CHECK", "READ_REGARDLESS", "RECORD", + "RECORD_ID", "RECORD_ID_ACCESS", "RECORD_ID_TO", "RECURSIVE", + "REFER", "REFERENCE", "RELEASE", "REPEAT", "%REPLACE", + "RESCAN", "RESIGNAL", "RETRIEVAL_POINTERS", "%RETURN", + "RETURN", "RETURNS", "REVERSE", "REVERT", "REVISION_DATE", + "REWIND", "REWIND_ON_CLOSE", "REWIND_ON_OPEN", + "REWRITE", "ROUND", "RTRIM", "%SBTTL", "SCALARVARYING", + "SEARCH", "SELECT", "SEQUENTIAL", "SEQL", + "SET", "SHARED_READ", "SHARED_WRITE", "SIGN", + "SIGNAL", "SIN", "SIND", "SINH", "SIZE", + "SKIP", "SNAP", "SOME", "SPACEBLOCK", "SPOOL", + "SQRT", "STATEMENT", "STATIC", "STOP", "STORAGE", + "STREAM", "STRING", "STRINGRANGE", "STRG", + "STRUCTURE", "SUBSCRIPTRANGE", "SUBRG", "SUBSTR", + "SUBTRACT", "SUM", "SUPERCEDE", "SYSIN", "SYSPRINT", + "SYSTEM", "SYSTEM_PROTECTION", "TAB", "TAN", + "TAND", "TANH", "TEMPORARY", "%THEN", "THEN", + "TIME", "TIMEOUT_PERIOD", "%TITLE", "TITLE", + "TO", "TRANSLATE", "TRIM", "TRUNC", "TRUNCATE", + "UNALIGNED", "UNAL", "UNDEFINED", "UNDF", "UNDERFLOW", + "UFL", "UNION", "UNSPEC", "UNTIL", "UPDATE", + "USER_OPEN", "VALID", "VALUE", "VAL", "VARIABLE", + "VARIANT", "VARYING", "VAR", "VAXCONDITION", "VERIFY", + "WAIT_FOR_RECORD", "%WARN", "WARN", "WHEN", + "WHILE", "WORLD_PROTECTION", "WRITE", "WRITE_BEHIND", + "WRITE_CHECK", "X", "ZERODIVIDE"], + + "SQL" => [ + "abort", "abs", "absolute", "access", + "action", "ada", "add", "admin", + "after", "aggregate", "alias", "all", + "allocate", "alter", "analyse", "analyze", + "and", "any", "are", "array", + "as", "asc", "asensitive", "assertion", + "assignment", "asymmetric", "at", "atomic", + "authorization", "avg", "backward", "before", + "begin", "between", "bigint", "binary", + "bit", "bitvar", "bit_length", "blob", + "boolean", "both", "breadth", "by", + "c", "cache", "call", "called", + "cardinality", "cascade", "cascaded", "case", + "cast", "catalog", "catalog_name", "chain", + "char", "character", "characteristics", "character_length", + "character_set_catalog", "character_set_name", "character_set_schema", "char_length", + "check", "checked", "checkpoint", /* "class", */ + "class_origin", "clob", "close", "cluster", + "coalesce", "cobol", "collate", "collation", + "collation_catalog", "collation_name", "collation_schema", "column", + "column_name", "command_function", "command_function_code", "comment", + "commit", "committed", "completion", "condition_number", + "connect", "connection", "connection_name", "constraint", + "constraints", "constraint_catalog", "constraint_name", "constraint_schema", + "constructor", "contains", "continue", "conversion", + "convert", "copy", "corresponding", "count", + "create", "createdb", "createuser", "cross", + "cube", "current", "current_date", "current_path", + "current_role", "current_time", "current_timestamp", "current_user", + "cursor", "cursor_name", "cycle", "data", + "database", "date", "datetime_interval_code", "datetime_interval_precision", + "day", "deallocate", "dec", "decimal", + "declare", "default", "defaults", "deferrable", + "deferred", "defined", "definer", "delete", + "delimiter", "delimiters", "depth", "deref", + "desc", "describe", "descriptor", "destroy", + "destructor", "deterministic", "diagnostics", "dictionary", + "disconnect", "dispatch", "distinct", "do", + "domain", "double", "drop", "dynamic", + "dynamic_function", "dynamic_function_code", "each", "else", + "encoding", "encrypted", "end", "end-exec", + "equals", "escape", "every", "except", + "exception", "excluding", "exclusive", "exec", + "execute", "existing", "exists", "explain", + "external", "extract", "false", "fetch", + "final", "first", "float", "for", + "force", "foreign", "fortran", "forward", + "found", "free", "freeze", "from", + "full", "function", "g", "general", + "generated", "get", "global", "go", + "goto", "grant", "granted", "group", + "grouping", "handler", "having", "hierarchy", + "hold", "host", "hour", "identity", + "ignore", "ilike", "immediate", "immutable", + "implementation", "implicit", "in", "including", + "increment", "index", "indicator", "infix", + "inherits", "initialize", "initially", "inner", + "inout", "input", "insensitive", "insert", + "instance", "instantiable", "instead", "int", + "integer", "intersect", "interval", "into", + "invoker", "is", "isnull", "isolation", + "iterate", "join", "k", "key", + "key_member", "key_type", "lancompiler", "language", + "large", "last", "lateral", "leading", + "left", "length", "less", "level", + "like", "limit", "listen", "load", + "local", "localtime", "localtimestamp", "location", + "locator", "lock", "lower", "m", + "map", "match", "max", "maxvalue", + "message_length", "message_octet_length", "message_text", "method", + "min", "minute", "minvalue", "mod", + "mode", "modifies", "modify", "module", + "month", "more", "move", "mumps", + "name", "names", "national", "natural", + "nchar", "nclob", "new", "next", + "no", "nocreatedb", "nocreateuser", "none", + "not", "nothing", "notify", "notnull", + "null", "nullable", "nullif", "number", + "numeric", "object", "octet_length", "of", + "off", "offset", "oids", "old", + "on", "only", "open", "operation", + "operator", "option", "options", "or", + "order", "ordinality", "out", "outer", + "output", "overlaps", "overlay", "overriding", + "owner", "pad", "parameter", "parameters", + "parameter_mode", "parameter_name", "parameter_ordinal_position", "parameter_specific_catalog", + "parameter_specific_name", "parameter_specific_schema", "partial", "pascal", + "password", "path", "pendant", "placing", + "pli", "position", "postfix", "precision", + "prefix", "preorder", "prepare", "preserve", + "primary", "prior", "privileges", "procedural", + "procedure", "public", "read", "reads", + "real", "recheck", "recursive", "ref", + "references", "referencing", "reindex", "relative", + "rename", "repeatable", "replace", "reset", + "restart", "restrict", "result", "return", + "returned_length", "returned_octet_length", "returned_sqlstate", "returns", + "revoke", "right", "role", "rollback", + "rollup", "routine", "routine_catalog", "routine_name", + "routine_schema", "row", "rows", "row_count", + "rule", "savepoint", "scale", "schema", + "schema_name", "scope", "scroll", "search", + "second", "section", "security", "select", + "self", "sensitive", "sequence", "serializable", + "server_name", "session", "session_user", "set", + "setof", "sets", "share", "show", + "similar", "simple", "size", "smallint", + "some", "source", "space", "specific", + "specifictype", "specific_name", "sql", "sqlcode", + "sqlerror", "sqlexception", "sqlstate", "sqlwarning", + "stable", "start", "state", "statement", + "static", "statistics", "stdin", "stdout", + "storage", "strict", "structure", "style", + "subclass_origin", "sublist", "substring", "sum", + "symmetric", "sysid", "system", "system_user", + "table", "table_name", "temp", "template", + "temporary", "terminate", "text", "than", "then", + "time", "timestamp", "timezone_hour", "timezone_minute", + "to", "toast", "trailing", "transaction", + "transactions_committed", "transactions_rolled_back", "transaction_active", "transform", + "transforms", "translate", "translation", "treat", + "trigger", "trigger_catalog", "trigger_name", "trigger_schema", + "trim", "true", "truncate", "trusted", + "type", "uncommitted", "under", "unencrypted", + "union", "unique", "unknown", "unlisten", + "unnamed", "unnest", "until", "update", + "upper", "usage", "user", "user_defined_type_catalog", + "user_defined_type_name", "user_defined_type_schema", "using", "vacuum", + "valid", "validator", "value", "values", + "varchar", "variable", "varying", "verbose", + "version", "view", "volatile", "when", + "whenever", "where", "with", "without", + "work", "write", "year", "zone"], + + ]; + + $case_insensitive = [ + "VB" => true, + "Pascal" => true, + "PL/I" => true, + "SQL" => true, + ]; + $ncs = false; + if (array_key_exists($language, $case_insensitive)) { + $ncs = true; + } + + $text = (array_key_exists($language, $preproc)) ? + $preproc_replace($preproc[$language], $text) : + $text; + $text = (array_key_exists($language, $keywords)) ? + $keyword_replace($keywords[$language], $text, $ncs) : + $text; + + return $text; + }; + + $rtrim1 = function ($span, $lang, $ch) use ($syntax_highlight_helper) { + return $syntax_highlight_helper(substr($span, 0, -1), $lang); + }; + + $rtrim1_htmlesc = function ($span, $lang, $ch) { + return htmlspecialchars(substr($span, 0, -1)); + }; + + $sch_rtrim1 = function ($span, $lang, $ch) use ($sch_syntax_helper) { + return $sch_syntax_helper(substr($span, 0, -1)); + }; + + $rtrim2 = function ($span, $lang, $ch) { + return substr($span, 0, -2); + }; + + $syn_proc = function ($span, $lang, $ch) use ($syntax_highlight_helper) { + return $syntax_highlight_helper($span, $lang); + }; + + $dash_putback = function ($span, $lang, $ch) use ($syntax_highlight_helper) { + return $syntax_highlight_helper('-' . $span, $lang); + }; + + $slash_putback = function ($span, $lang, $ch) use ($syntax_highlight_helper) { + return $syntax_highlight_helper('/' . $span, $lang); + }; + + $slash_putback_rtrim1 = function ($span, $lang, $ch) use ($rtrim1) { + return $rtrim1('/' . $span, $lang, $ch); + }; + + $lparen_putback = function ($span, $lang, $ch) use ($syntax_highlight_helper) { + return $syntax_highlight_helper('(' . $span, $lang); + }; + + $lparen_putback_rtrim1 = function ($span, $lang, $ch) use ($rtrim1) { + return $rtrim1('(' . $span, $lang, $ch); + }; + + $prepend_xml_opentag = function ($span, $lang, $ch) { + return '<span class="xml_tag"><' . $span; + }; + + $proc_void = function ($span, $lang, $ch) { + return $span; + }; + + $this->sch[self::SCH_NORMAL][0] = self::SCH_NORMAL; + $this->sch[self::SCH_NORMAL]['"'] = self::SCH_STRLIT; + $this->sch[self::SCH_NORMAL]["#"] = self::SCH_CHRLIT; + $this->sch[self::SCH_NORMAL]["0"] = self::SCH_NUMLIT; + $this->sch[self::SCH_NORMAL]["1"] = self::SCH_NUMLIT; + $this->sch[self::SCH_NORMAL]["2"] = self::SCH_NUMLIT; + $this->sch[self::SCH_NORMAL]["3"] = self::SCH_NUMLIT; + $this->sch[self::SCH_NORMAL]["4"] = self::SCH_NUMLIT; + $this->sch[self::SCH_NORMAL]["5"] = self::SCH_NUMLIT; + $this->sch[self::SCH_NORMAL]["6"] = self::SCH_NUMLIT; + $this->sch[self::SCH_NORMAL]["7"] = self::SCH_NUMLIT; + $this->sch[self::SCH_NORMAL]["8"] = self::SCH_NUMLIT; + $this->sch[self::SCH_NORMAL]["9"] = self::SCH_NUMLIT; + + $this->sch[self::SCH_STRLIT]['"'] = self::SCH_NORMAL; + $this->sch[self::SCH_STRLIT]["\n"] = self::SCH_NORMAL; + $this->sch[self::SCH_STRLIT]["\\"] = self::SCH_STRESC; + $this->sch[self::SCH_STRLIT][0] = self::SCH_STRLIT; + + $this->sch[self::SCH_CHRLIT][" "] = self::SCH_NORMAL; + $this->sch[self::SCH_CHRLIT]["\t"] = self::SCH_NORMAL; + $this->sch[self::SCH_CHRLIT]["\n"] = self::SCH_NORMAL; + $this->sch[self::SCH_CHRLIT]["\r"] = self::SCH_NORMAL; + $this->sch[self::SCH_CHRLIT][0] = self::SCH_CHRLIT; + + $this->sch[self::SCH_NUMLIT][" "] = self::SCH_NORMAL; + $this->sch[self::SCH_NUMLIT]["\t"] = self::SCH_NORMAL; + $this->sch[self::SCH_NUMLIT]["\n"] = self::SCH_NORMAL; + $this->sch[self::SCH_NUMLIT]["\r"] = self::SCH_NORMAL; + $this->sch[self::SCH_NUMLIT][0] = self::SCH_NUMLIT; + + // + // State transitions for C + // + $this->c89[self::NORMAL_TEXT]["\""] = self::DQ_LITERAL; + $this->c89[self::NORMAL_TEXT]["'"] = self::SQ_LITERAL; + $this->c89[self::NORMAL_TEXT]["/"] = self::SLASH_BEGIN; + $this->c89[self::NORMAL_TEXT][0] = self::NORMAL_TEXT; + + $this->c89[self::DQ_LITERAL]["\""] = self::NORMAL_TEXT; + $this->c89[self::DQ_LITERAL]["\n"] = self::NORMAL_TEXT; + $this->c89[self::DQ_LITERAL]["\\"] = self::DQ_ESCAPE; + $this->c89[self::DQ_LITERAL][0] = self::DQ_LITERAL; + + $this->c89[self::DQ_ESCAPE][0] = self::DQ_LITERAL; + + $this->c89[self::SQ_LITERAL]["'"] = self::NORMAL_TEXT; + $this->c89[self::SQ_LITERAL]["\n"] = self::NORMAL_TEXT; + $this->c89[self::SQ_LITERAL]["\\"] = self::SQ_ESCAPE; + $this->c89[self::SQ_LITERAL][0] = self::SQ_LITERAL; + + $this->c89[self::SQ_ESCAPE][0] = self::SQ_LITERAL; + + $this->c89[self::SLASH_BEGIN]["*"] = self::STAR_COMMENT; + $this->c89[self::SLASH_BEGIN][0] = self::NORMAL_TEXT; + + $this->c89[self::STAR_COMMENT]["*"] = self::STAR_END; + $this->c89[self::STAR_COMMENT][0] = self::STAR_COMMENT; + + $this->c89[self::STAR_END]["/"] = self::NORMAL_TEXT; + $this->c89[self::STAR_END]["*"] = self::STAR_END; + $this->c89[self::STAR_END][0] = self::STAR_COMMENT; + + // + // State transitions for C++ + // Inherit transitions from C, and add line comment support + // + $this->cpp = $this->c89; + $this->cpp[self::SLASH_BEGIN]["/"] = self::LINE_COMMENT; + $this->cpp[self::LINE_COMMENT]["\n"] = self::NORMAL_TEXT; + $this->cpp[self::LINE_COMMENT]["\\"] = self::LC_ESCAPE; + $this->cpp[self::LINE_COMMENT][0] = self::LINE_COMMENT; + + $this->cpp[self::LC_ESCAPE]["\r"] = self::LC_ESCAPE; + $this->cpp[self::LC_ESCAPE][0] = self::LINE_COMMENT; + + // + // State transitions for C99. + // C99 supports line comments like C++ + // + $this->c99 = $this->cpp; + + // State transitions for PL/I + // Kinda like C + $this->pli = $this->c89; + + // + // State transitions for PHP + // Inherit transitions from C++, and add perl-style line comment support + $this->php = $this->cpp; + $this->php[self::NORMAL_TEXT]["#"] = self::LINE_COMMENT; + $this->php[self::SQ_LITERAL]["\n"] = self::SQ_LITERAL; + $this->php[self::DQ_LITERAL]["\n"] = self::DQ_LITERAL; + + // + // State transitions for Perl + $this->perl[self::NORMAL_TEXT]["#"] = self::LINE_COMMENT; + $this->perl[self::NORMAL_TEXT]["\""] = self::DQ_LITERAL; + $this->perl[self::NORMAL_TEXT]["'"] = self::SQ_LITERAL; + $this->perl[self::NORMAL_TEXT][0] = self::NORMAL_TEXT; + + $this->perl[self::DQ_LITERAL]["\""] = self::NORMAL_TEXT; + $this->perl[self::DQ_LITERAL]["\\"] = self::DQ_ESCAPE; + $this->perl[self::DQ_LITERAL][0] = self::DQ_LITERAL; + + $this->perl[self::DQ_ESCAPE][0] = self::DQ_LITERAL; + + $this->perl[self::SQ_LITERAL]["'"] = self::NORMAL_TEXT; + $this->perl[self::SQ_LITERAL]["\\"] = self::SQ_ESCAPE; + $this->perl[self::SQ_LITERAL][0] = self::SQ_LITERAL; + + $this->perl[self::SQ_ESCAPE][0] = self::SQ_LITERAL; + + $this->perl[self::LINE_COMMENT]["\n"] = self::NORMAL_TEXT; + $this->perl[self::LINE_COMMENT][0] = self::LINE_COMMENT; + + $this->mirc[self::NORMAL_TEXT]["\""] = self::DQ_LITERAL; + $this->mirc[self::NORMAL_TEXT][";"] = self::LINE_COMMENT; + $this->mirc[self::NORMAL_TEXT][0] = self::NORMAL_TEXT; + + $this->mirc[self::DQ_LITERAL]["\""] = self::NORMAL_TEXT; + $this->mirc[self::DQ_LITERAL]["\\"] = self::DQ_ESCAPE; + $this->mirc[self::DQ_LITERAL][0] = self::DQ_LITERAL; + + $this->mirc[self::DQ_ESCAPE][0] = self::DQ_LITERAL; + + $this->mirc[self::LINE_COMMENT]["\n"] = self::NORMAL_TEXT; + $this->mirc[self::LINE_COMMENT][0] = self::LINE_COMMENT; + + $this->ruby = $this->perl; + + $this->python = $this->perl; + + $this->java = $this->cpp; + + $this->vb = $this->perl; + $this->vb[self::NORMAL_TEXT]["#"] = self::NORMAL_TEXT; + $this->vb[self::NORMAL_TEXT]["'"] = self::LINE_COMMENT; + + $this->cs = $this->java; + + $this->pascal = $this->c89; + $this->pascal[self::NORMAL_TEXT]["("] = self::PAREN_BEGIN; + $this->pascal[self::NORMAL_TEXT]["/"] = self::SLASH_BEGIN; + $this->pascal[self::NORMAL_TEXT]["{"] = self::BLOCK_COMMENT; + + $this->pascal[self::PAREN_BEGIN]["*"] = self::STAR_COMMENT; + $this->pascal[self::PAREN_BEGIN]["'"] = self::SQ_LITERAL; + $this->pascal[self::PAREN_BEGIN]['"'] = self::DQ_LITERAL; + $this->pascal[self::PAREN_BEGIN][0] = self::NORMAL_TEXT; + + $this->pascal[self::SLASH_BEGIN]["'"] = self::SQ_LITERAL; + $this->pascal[self::SLASH_BEGIN]['"'] = self::DQ_LITERAL; + $this->pascal[self::SLASH_BEGIN]['/'] = self::LINE_COMMENT; + $this->pascal[self::SLASH_BEGIN][0] = self::NORMAL_TEXT; + + $this->pascal[self::STAR_COMMENT]["*"] = self::STAR_END; + $this->pascal[self::STAR_COMMENT][0] = self::STAR_COMMENT; + + $this->pascal[self::BLOCK_COMMENT]["}"] = self::NORMAL_TEXT; + $this->pascal[self::BLOCK_COMMENT][0] = self::BLOCK_COMMENT; + + $this->pascal[self::LINE_COMMENT]["\n"] = self::NORMAL_TEXT; + $this->pascal[self::LINE_COMMENT][0] = self::LINE_COMMENT; + + $this->pascal[self::STAR_END][")"] = self::NORMAL_TEXT; + $this->pascal[self::STAR_END]["*"] = self::STAR_END; + $this->pascal[self::STAR_END][0] = self::STAR_COMMENT; + + $this->sql[self::NORMAL_TEXT]['"'] = self::DQ_LITERAL; + $this->sql[self::NORMAL_TEXT]["'"] = self::SQ_LITERAL; + $this->sql[self::NORMAL_TEXT]['`'] = self::BT_LITERAL; + $this->sql[self::NORMAL_TEXT]['-'] = self::DASH_BEGIN; + $this->sql[self::NORMAL_TEXT][0] = self::NORMAL_TEXT; + + $this->sql[self::DQ_LITERAL]['"'] = self::NORMAL_TEXT; + $this->sql[self::DQ_LITERAL]['\\'] = self::DQ_ESCAPE; + $this->sql[self::DQ_LITERAL][0] = self::DQ_LITERAL; + + $this->sql[self::SQ_LITERAL]["'"] = self::NORMAL_TEXT; + $this->sql[self::SQ_LITERAL]['\\'] = self::SQ_ESCAPE; + $this->sql[self::SQ_LITERAL][0] = self::SQ_LITERAL; + + $this->sql[self::BT_LITERAL]['`'] = self::NORMAL_TEXT; + $this->sql[self::BT_LITERAL]['\\'] = self::BT_ESCAPE; + $this->sql[self::BT_LITERAL][0] = self::BT_LITERAL; + + $this->sql[self::DQ_ESCAPE][0] = self::DQ_LITERAL; + $this->sql[self::SQ_ESCAPE][0] = self::SQ_LITERAL; + $this->sql[self::BT_ESCAPE][0] = self::BT_LITERAL; + + $this->sql[self::DASH_BEGIN]["-"] = self::LINE_COMMENT; + $this->sql[self::DASH_BEGIN][0] = self::NORMAL_TEXT; + + $this->sql[self::LINE_COMMENT]["\n"] = self::NORMAL_TEXT; + $this->sql[self::LINE_COMMENT]["\\"] = self::LC_ESCAPE; + $this->sql[self::LINE_COMMENT][0] = self::LINE_COMMENT; + + $this->sql[self::LC_ESCAPE]["\r"] = self::LC_ESCAPE; + $this->sql[self::LC_ESCAPE][0] = self::LINE_COMMENT; + + $this->xml[self::NORMAL_TEXT]["<"] = self::XML_TAG_BEGIN; + $this->xml[self::NORMAL_TEXT]["&"] = self::HTML_ENTITY; + $this->xml[self::NORMAL_TEXT][0] = self::NORMAL_TEXT; + $this->xml[self::HTML_ENTITY][";"] = self::NORMAL_TEXT; + $this->xml[self::HTML_ENTITY]["<"] = self::XML_TAG_BEGIN; + $this->xml[self::HTML_ENTITY][0] = self::HTML_ENTITY; + $this->xml[self::XML_TAG_BEGIN]["?"] = self::XML_PI; + $this->xml[self::XML_TAG_BEGIN]["!"] = self::LINE_COMMENT; + $this->xml[self::XML_TAG_BEGIN][0] = self::XML_TAG; + $this->xml[self::XML_TAG][">"] = self::NORMAL_TEXT; + $this->xml[self::XML_TAG]["\""] = self::DQ_LITERAL; + $this->xml[self::XML_TAG]["'"] = self::SQ_LITERAL; + $this->xml[self::XML_TAG][0] = self::XML_TAG; + $this->xml[self::XML_PI][">"] = self::NORMAL_TEXT; + $this->xml[self::XML_PI][0] = self::XML_TAG; + $this->xml[self::LINE_COMMENT][">"] = self::NORMAL_TEXT; + $this->xml[self::LINE_COMMENT][0] = self::LINE_COMMENT; + $this->xml[self::DQ_LITERAL]["\""] = self::XML_TAG; + $this->xml[self::DQ_LITERAL]["&"] = self::DQ_ESCAPE; + $this->xml[self::DQ_LITERAL][0] = self::DQ_LITERAL; + $this->xml[self::SQ_LITERAL]["'"] = self::XML_TAG; + $this->xml[self::SQ_LITERAL]["&"] = self::SQ_ESCAPE; + $this->xml[self::SQ_LITERAL][0] = self::SQ_LITERAL; + $this->xml[self::DQ_ESCAPE][";"] = self::DQ_LITERAL; + $this->xml[self::DQ_ESCAPE][0] = self::DQ_ESCAPE; + + // + // Main state transition table + // + $this->states = [ + "C89" => $this->c89, + "C" => $this->c99, + "C++" => $this->cpp, + "PHP" => $this->php, + "Perl" => $this->perl, + "Java" => $this->java, + "VB" => $this->vb, + "C#" => $this->cs, + "Ruby" => $this->ruby, + "Python" => $this->python, + "Pascal" => $this->pascal, + "mIRC" => $this->mirc, + "PL/I" => $this->pli, + "SQL" => $this->sql, + "XML" => $this->xml, + "Scheme" => $this->sch, + ]; + + // + // Process functions + // + $this->process["C89"][self::NORMAL_TEXT][self::SQ_LITERAL] = $rtrim1; + $this->process["C89"][self::NORMAL_TEXT][self::DQ_LITERAL] = $rtrim1; + $this->process["C89"][self::NORMAL_TEXT][self::SLASH_BEGIN] = $rtrim1; + $this->process["C89"][self::NORMAL_TEXT][0] = $syn_proc; + + $this->process["C89"][self::SLASH_BEGIN][self::STAR_COMMENT] = $rtrim1; + $this->process["C89"][self::SLASH_BEGIN][0] = $slash_putback; + + $this->process["Scheme"][self::SCH_NORMAL][self::SCH_STRLIT] = $$this; + $this->process["Scheme"][self::SCH_NORMAL][self::SCH_CHRLIT] = $$this; + $this->process["Scheme"][self::SCH_NORMAL][self::SCH_NUMLIT] = $$this; + + $this->process["SQL"][self::NORMAL_TEXT][self::SQ_LITERAL] = $rtrim1; + $this->process["SQL"][self::NORMAL_TEXT][self::DQ_LITERAL] = $rtrim1; + $this->process["SQL"][self::NORMAL_TEXT][self::BT_LITERAL] = $rtrim1; + $this->process["SQL"][self::NORMAL_TEXT][self::DASH_BEGIN] = $rtrim1; + $this->process["SQL"][self::NORMAL_TEXT][0] = $syn_proc; + + $this->process["SQL"][self::DASH_BEGIN][self::LINE_COMMENT] = $rtrim1; + $this->process["SQL"][self::DASH_BEGIN][0] = $dash_putback; + + $this->process["PL/I"] = $this->process["C89"]; + + $this->process["C++"] = $this->process["C89"]; + $this->process["C++"][self::SLASH_BEGIN][self::LINE_COMMENT] = $rtrim1; + + $this->process["C"] = $this->process["C++"]; + + $this->process["PHP"] = $this->process["C++"]; + $this->process["PHP"][self::NORMAL_TEXT][self::LINE_COMMENT] = $rtrim1; + + $this->process["Perl"][self::NORMAL_TEXT][self::SQ_LITERAL] = $rtrim1; + $this->process["Perl"][self::NORMAL_TEXT][self::DQ_LITERAL] = $rtrim1; + $this->process["Perl"][self::NORMAL_TEXT][self::LINE_COMMENT] = $rtrim1; + $this->process["Perl"][self::NORMAL_TEXT][0] = $syn_proc; + + $this->process["Ruby"] = $this->process["Perl"]; + $this->process["Python"] = $this->process["Perl"]; + + $this->process["mIRC"][self::NORMAL_TEXT][self::DQ_LITERAL] = $rtrim1; + $this->process["mIRC"][self::NORMAL_TEXT][self::LINE_COMMENT] = $rtrim1; + $this->process["mIRC"][self::NORMAL_TEXT][0] = $syn_proc; + + $this->process["VB"] = $this->process["Perl"]; + + $this->process["Java"] = $this->process["C++"]; + + $this->process["C#"] = $this->process["Java"]; + + $this->process["Pascal"] = $this->process["C++"]; + $this->process["Pascal"][self::NORMAL_TEXT][self::LINE_COMMENT] = $rtrim1; + $this->process["Pascal"][self::NORMAL_TEXT][self::BLOCK_COMMENT] = $rtrim1; + $this->process["Pascal"][self::NORMAL_TEXT][self::PAREN_BEGIN] = $rtrim1; + $this->process["Pascal"][self::SLASH_BEGIN][self::SQ_LITERAL] = $slash_putback_rtrim1; + $this->process["Pascal"][self::SLASH_BEGIN][self::DQ_LITERAL] = $slash_putback_rtrim1; + $this->process["Pascal"][self::SLASH_BEGIN][0] = $slash_putback; + $this->process["Pascal"][self::PAREN_BEGIN][self::SQ_LITERAL] = $lparen_putback_rtrim1; + $this->process["Pascal"][self::PAREN_BEGIN][self::DQ_LITERAL] = $lparen_putback_rtrim1; + $this->process["Pascal"][self::PAREN_BEGIN][self::STAR_COMMENT] = $rtrim1; + $this->process["Pascal"][self::PAREN_BEGIN][0] = $lparen_putback; + + $this->process["XML"][self::NORMAL_TEXT][self::XML_TAG_BEGIN] = $rtrim1; + $this->process["XML"][self::NORMAL_TEXT][self::HTML_ENTITY] = $rtrim1; + $this->process["XML"][self::HTML_ENTITY][self::XML_TAG_BEGIN] = $rtrim1; + $this->process["XML"][self::HTML_ENTITY][0] = $proc_void; + $this->process["XML"][self::XML_TAG_BEGIN][self::XML_TAG] = $prepend_xml_opentag; + $this->process["XML"][self::XML_TAG_BEGIN][self::XML_PI] = $rtrim1; + $this->process["XML"][self::XML_TAG_BEGIN][self::LINE_COMMENT] = $rtrim1; + $this->process["XML"][self::LINE_COMMENT][self::NORMAL_TEXT] = $rtrim1_htmlesc; + $this->process["XML"][self::XML_TAG][self::NORMAL_TEXT] = $rtrim1; + $this->process["XML"][self::XML_TAG][self::DQ_LITERAL] = $rtrim1; + $this->process["XML"][self::DQ_LITERAL][self::XML_TAG] = $rtrim1; + $this->process["XML"][self::DQ_LITERAL][self::DQ_ESCAPE] = $rtrim1; + + $this->process_end["C89"] = $syntax_highlight_helper; + $this->process_end["C++"] = $this->process_end["C89"]; + $this->process_end["C"] = $this->process_end["C89"]; + $this->process_end["PHP"] = $this->process_end["C89"]; + $this->process_end["Perl"] = $this->process_end["C89"]; + $this->process_end["Java"] = $this->process_end["C89"]; + $this->process_end["VB"] = $this->process_end["C89"]; + $this->process_end["C#"] = $this->process_end["C89"]; + $this->process_end["Ruby"] = $this->process_end["C89"]; + $this->process_end["Python"] = $this->process_end["C89"]; + $this->process_end["Pascal"] = $this->process_end["C89"]; + $this->process_end["mIRC"] = $this->process_end["C89"]; + $this->process_end["PL/I"] = $this->process_end["C89"]; + $this->process_end["SQL"] = $this->process_end["C89"]; + $this->process_end["Scheme"] = $sch_syntax_helper; + + $this->edges["C89"][self::NORMAL_TEXT . "," . self::DQ_LITERAL] = '<span class="literal">"'; + $this->edges["C89"][self::NORMAL_TEXT . "," . self::SQ_LITERAL] = '<span class="literal">\''; + $this->edges["C89"][self::SLASH_BEGIN . "," . self::STAR_COMMENT] = '<span class="comment">/*'; + $this->edges["C89"][self::DQ_LITERAL . "," . self::NORMAL_TEXT] = '</span>'; + $this->edges["C89"][self::SQ_LITERAL . "," . self::NORMAL_TEXT] = '</span>'; + $this->edges["C89"][self::STAR_END . "," . self::NORMAL_TEXT] = '</span>'; + + $this->edges["Scheme"][self::SCH_NORMAL . "," . self::SCH_STRLIT] = '<span class="sch_str">"'; + $this->edges["Scheme"][self::SCH_NORMAL . "," . self::SCH_NUMLIT] = '<span class="sch_num">'; + $this->edges["Scheme"][self::SCH_NORMAL . "," . self::SCH_CHRLIT] = '<span class="sch_chr">#'; + $this->edges["Scheme"][self::SCH_STRLIT . "," . self::SCH_NORMAL] = '</span>'; + $this->edges["Scheme"][self::SCH_NUMLIT . "," . self::SCH_NORMAL] = '</span>'; + $this->edges["Scheme"][self::SCH_CHRLIT . "," . self::SCH_NORMAL] = '</span>'; + + $this->edges["SQL"][self::NORMAL_TEXT . "," . self::DQ_LITERAL] = '<span class="literal">"'; + $this->edges["SQL"][self::NORMAL_TEXT . "," . self::SQ_LITERAL] = '<span class="literal">\''; + $this->edges["SQL"][self::DASH_BEGIN . "," . self::LINE_COMMENT] = '<span class="comment">--'; + $this->edges["SQL"][self::NORMAL_TEXT . "," . self::BT_LITERAL] = '`'; + $this->edges["SQL"][self::DQ_LITERAL . "," . self::NORMAL_TEXT] = '</span>'; + $this->edges["SQL"][self::SQ_LITERAL . "," . self::NORMAL_TEXT] = '</span>'; + $this->edges["SQL"][self::LINE_COMMENT . "," . self::NORMAL_TEXT] = '</span>'; + + $this->edges["PL/I"] = $this->edges["C89"]; + + $this->edges["C++"] = $this->edges["C89"]; + $this->edges["C++"][self::SLASH_BEGIN . "," . self::LINE_COMMENT] = '<span class="comment">//'; + $this->edges["C++"][self::LINE_COMMENT . "," . self::NORMAL_TEXT] = '</span>'; + + $this->edges["C"] = $this->edges["C++"]; + + $this->edges["PHP"] = $this->edges["C++"]; + $this->edges["PHP"][self::NORMAL_TEXT . "," . self::LINE_COMMENT] = '<span class="comment">#'; + + $this->edges["Perl"][self::NORMAL_TEXT . "," . self::DQ_LITERAL] = '<span class="literal">"'; + $this->edges["Perl"][self::NORMAL_TEXT . "," . self::SQ_LITERAL] = '<span class="literal">\''; + $this->edges["Perl"][self::DQ_LITERAL . "," . self::NORMAL_TEXT] = '</span>'; + $this->edges["Perl"][self::SQ_LITERAL . "," . self::NORMAL_TEXT] = '</span>'; + $this->edges["Perl"][self::NORMAL_TEXT . "," . self::LINE_COMMENT] = '<span class="comment">#'; + $this->edges["Perl"][self::LINE_COMMENT . "," . self::NORMAL_TEXT] = '</span>'; + + $this->edges["Ruby"] = $this->edges["Perl"]; + + $this->edges["Python"] = $this->edges["Perl"]; + + $this->edges["mIRC"][self::NORMAL_TEXT . "," . self::DQ_LITERAL] = '<span class="literal">"'; + $this->edges["mIRC"][self::NORMAL_TEXT . "," . self::LINE_COMMENT] = '<span class="comment">;'; + $this->edges["mIRC"][self::DQ_LITERAL . "," . self::NORMAL_TEXT] = '</span>'; + $this->edges["mIRC"][self::LINE_COMMENT . "," . self::NORMAL_TEXT] = '</span>'; + + $this->edges["VB"] = $this->edges["Perl"]; + $this->edges["VB"][self::NORMAL_TEXT . "," . self::LINE_COMMENT] = '<span class="comment">\''; + + $this->edges["Java"] = $this->edges["C++"]; + + $this->edges["C#"] = $this->edges["Java"]; + + $this->edges["Pascal"] = $this->edges["C89"]; + $this->edges["Pascal"][self::PAREN_BEGIN . "," . self::STAR_COMMENT] = '<span class="comment">(*'; + $this->edges["Pascal"][self::PAREN_BEGIN . "," . self::DQ_LITERAL] = '<span class="literal">"'; + $this->edges["Pascal"][self::PAREN_BEGIN . "," . self::SQ_LITERAL] = '<span class="literal">\''; + $this->edges["Pascal"][self::SLASH_BEGIN . "," . self::DQ_LITERAL] = '<span class="literal">"'; + $this->edges["Pascal"][self::SLASH_BEGIN . "," . self::SQ_LITERAL] = '<span class="literal">\''; + $this->edges["Pascal"][self::SLASH_BEGIN . "," . self::LINE_COMMENT] = '<span class="comment">//'; + $this->edges["Pascal"][self::NORMAL_TEXT . "," . self::BLOCK_COMMENT] = '<span class="comment">{'; + $this->edges["Pascal"][self::LINE_COMMENT . "," . self::NORMAL_TEXT] = '</span>'; + $this->edges["Pascal"][self::BLOCK_COMMENT . "," . self::NORMAL_TEXT] = '</span>'; + + $this->edges["XML"][self::NORMAL_TEXT . "," . self::HTML_ENTITY] = '<span class="html_entity">&'; + $this->edges["XML"][self::HTML_ENTITY . "," . self::NORMAL_TEXT] = '</span>'; + $this->edges["XML"][self::HTML_ENTITY . "," . self::XML_TAG_BEGIN] = '</span>'; + $this->edges["XML"][self::XML_TAG . "," . self::NORMAL_TEXT] = '></span>'; + $this->edges["XML"][self::XML_TAG_BEGIN . "," . self::XML_PI] = '<span class="xml_pi"><?'; + $this->edges["XML"][self::XML_TAG_BEGIN . "," . self::LINE_COMMENT] = '<span class="comment"><!'; + $this->edges["XML"][self::LINE_COMMENT . "," . self::NORMAL_TEXT] = '></span>'; + $this->edges["XML"][self::XML_TAG . "," . self::DQ_LITERAL] = '<span class="literal">"'; + $this->edges["XML"][self::DQ_LITERAL . "," . self::XML_TAG] = '"</span>'; + $this->edges["XML"][self::DQ_LITERAL . "," . self::DQ_ESCAPE] = '<span class="html_entity">&'; + $this->edges["XML"][self::DQ_ESCAPE . "," . self::DQ_LITERAL] = '</span>'; + $this->edges["XML"][self::XML_TAG . "," . self::SQ_LITERAL] = '<span class="literal">\''; + $this->edges["XML"][self::SQ_LITERAL . "," . self::XML_TAG] = '\'</span>'; + $this->edges["XML"][self::SQ_LITERAL . "," . self::SQ_ESCAPE] = '<span class="html_entity">&'; + $this->edges["XML"][self::SQ_ESCAPE . "," . self::SQ_LITERAL] = '</span>'; + + } + + /** + * Syntax highlight function + * Does the bulk of the syntax highlighting by lexing the input + * string, then calling the helper function to highlight keywords. + */ + function syntax_highlight($text, $language) { + if ($language == "Plain Text") { + return $text; + } + + // + // The State Machine + // + if (array_key_exists($language, $this->initial_state)) { + $state = $this->initial_state[$language]; + } else { + $state = self::NORMAL_TEXT; + } + + $output = ""; + $span = ""; + while (strlen($text) > 0) { + $ch = substr($text, 0, 1); + $text = substr($text, 1); + + $oldstate = $state; + $state = (array_key_exists($ch, $this->states[$language][$state])) ? + $this->states[$language][$state][$ch] : + $this->states[$language][$state][0]; + + $span .= $ch; + + if ($oldstate != $state) { + if (array_key_exists($language, $this->process) && + array_key_exists($oldstate, $this->process[$language])) { + if (array_key_exists($state, $this->process[$language][$oldstate])) { + $pf = $this->process[$language][$oldstate][$state]; + $output .= $pf($span, $language, $ch); + } else { + $pf = $this->process[$language][$oldstate][0]; + $output .= $pf($span, $language, $ch); + } + } else { + $output .= $span; + } + + if (array_key_exists($language, $this->edges) && + array_key_exists("$oldstate,$state", $this->edges[$language])) { + $output .= $this->edges[$language]["$oldstate,$state"]; + } + + $span = ""; + } + } + + if (array_key_exists($language, $this->process_end) && $state == self::NORMAL_TEXT) { + $output .= $this->process_end[$language]($span, $language); + } else { + $output .= $span; + } + + if ($state != self::NORMAL_TEXT) { + if (array_key_exists($language, $this->edges) && + array_key_exists("$state," . self::NORMAL_TEXT, $this->edges[$language])) { + $output .= $this->edges[$language]["$state," . self::NORMAL_TEXT]; + } + + } + + return $output; + } +} diff --git a/src/classes/Misc.php b/src/classes/Misc.php index 50f06d26..17d1cc49 100644 --- a/src/classes/Misc.php +++ b/src/classes/Misc.php @@ -2,6 +2,7 @@ namespace PHPPgAdmin; +use \PHPPgAdmin\Controller\LoginController; use \PHPPgAdmin\Decorators\Decorator; /** @@ -16,6 +17,7 @@ class Misc { private $_no_db_connection = false; private $_reload_drop_database = false; private $_reload_browser = false; + private $_no_bottom_link = false; private $app = null; private $data = null; private $database = null; @@ -26,6 +28,7 @@ class Misc { public $form = ''; public $href = ''; public $lang = []; + private $server_info = null; private $_no_output = false; /* Constructor */ @@ -38,10 +41,18 @@ class Misc { $this->conf = $container->get('conf'); $this->view = $container->get('view'); $this->plugin_manager = $container->get('plugin_manager'); - $this->appName = $container->get('settings')['appName']; - $this->appVersion = $container->get('settings')['appVersion']; $this->appLangFiles = $container->get('appLangFiles'); + $this->appName = $container->get('settings')['appName']; + $this->appVersion = $container->get('settings')['appVersion']; + $this->postgresqlMinVer = $container->get('settings')['postgresqlMinVer']; + $this->phpMinVer = $container->get('settings')['phpMinVer']; + + // Check the version of PHP + if (version_compare(phpversion(), $this->phpMinVer, '<')) { + exit(sprintf('Version of PHP not supported. Please upgrade to version %s or later.', $this->phpMinVer)); + } + if (count($this->conf['servers']) === 1) { $info = $this->conf['servers'][0]; $this->server_id = $info['host'] . ':' . $info['port'] . ':' . $info['sslmode']; @@ -50,35 +61,53 @@ class Misc { } else if (isset($_SESSION['webdbLogin']) && count($_SESSION['webdbLogin']) > 0) { $this->server_id = array_keys($_SESSION['webdbLogin'])[0]; } + + $_server_info = $this->getServerInfo(); + /* starting with PostgreSQL 9.0, we can set the application name */ + if (isset($_server_info['pgVersion']) && $_server_info['pgVersion'] >= 9) { + putenv("PGAPPNAME=" . $this->appName . '_' . $this->appVersion); + } + //\PC::debug($this->conf, 'conf'); //\PC::debug($this->server_id, 'server_id'); } function getConnection($database = '', $server_id = null) { + $lang = $this->lang; - if ($server_id !== null) { - $this->server_id = $server_id; - } - $server_info = $this->getServerInfo($this->server_id); - - $database_to_use = $this->getDatabase($database); - // Perform extra security checks if this config option is set - if ($this->conf['extra_login_security']) { - // Disallowed logins if extra_login_security is enabled. - // These must be lowercase. - $bad_usernames = ['pgsql', 'postgres', 'root', 'administrator']; + if ($this->_connection === null) { + if ($server_id !== null) { + $this->server_id = $server_id; + } + $server_info = $this->getServerInfo($this->server_id); + + $database_to_use = $this->getDatabase($database); + // Perform extra security checks if this config option is set + if ($this->conf['extra_login_security']) { + // Disallowed logins if extra_login_security is enabled. + // These must be lowercase. + $bad_usernames = [ + 'pgsql' => 'pgsql', + 'postgres' => 'postgres', + 'root' => 'root', + 'administrator' => 'administrator', + ]; - $username = strtolower($server_info['username']); + if (isset($server_info['username']) && array_key_exists(strtolower($server_info['username']), $bad_usernames)) { + unset($_SESSION['webdbLogin'][$this->server_id]); + $msg = $lang['strlogindisallowed']; + $login_controller = new LoginController($this->app->getContainer()); + return $login_controller->render(); + } - if ($server_info['password'] == '' || in_array($username, $bad_usernames)) { - unset($_SESSION['webdbLogin'][$this->server_id]); - $msg = $lang['strlogindisallowed']; - include '../views/login.php'; - exit; + if (!isset($server_info['password']) || $server_info['password'] == '') { + unset($_SESSION['webdbLogin'][$this->server_id]); + $msg = $lang['strlogindisallowed']; + $login_controller = new LoginController($this->app->getContainer()); + return $login_controller->render(); + } } - } - if ($this->_connection === null) { // Create the connection object and make the connection $this->_connection = new \PHPPgAdmin\Database\Connection( $server_info['host'], @@ -93,6 +122,15 @@ class Misc { } /** + * sets $_no_bottom_link boolean value + * @param boolean $flag [description] + */ + function setNoBottomLink($flag) { + $this->_no_bottom_link = boolval($flag); + return $this; + } + + /** * sets $_no_db_connection boolean value, allows to render scripts that do not need an active session * @param boolean $flag [description] */ @@ -102,9 +140,7 @@ class Misc { } function setNoOutput($flag) { - global $_no_output; $this->_no_output = boolval($flag); - $_no_output = $this->_no_output; return $this; } @@ -141,18 +177,14 @@ class Misc { * @param boolean $flag sets internal $_reload_browser var which will be passed to the footer methods */ function setReloadBrowser($flag) { - global $_reload_browser; - $_reload_browser = $flag; $this->_reload_browser = boolval($flag); return $this; } /** * [setReloadBrowser description] - * @param boolean $flag sets internal $_reload_browser var which will be passed to the footer methods + * @param boolean $flag sets internal $_reload_drop_database var which will be passed to the footer methods */ function setReloadDropDatabase($flag) { - global $_reload_drop_database; - $_reload_drop_database = $flag; $this->_reload_drop_database = boolval($flag); return $this; } @@ -167,6 +199,12 @@ class Misc { $this->server_id = $server_id; } + $server_info = $this->getServerInfo($this->server_id); + + if ($this->_no_db_connection || !isset($server_info['username'])) { + return null; + } + if ($this->data === null) { $_connection = $this->getConnection($database, $this->server_id); @@ -174,7 +212,7 @@ class Misc { // The description of the server is returned in $platform. $_type = $_connection->getDriver($platform); if ($_type === null) { - printf($lang['strpostgresqlversionnotsupported'], $postgresqlMinVer); + printf($lang['strpostgresqlversionnotsupported'], $this->postgresqlMinVer); exit; } $_type = '\PHPPgAdmin\Database\\' . $_type; @@ -220,6 +258,7 @@ class Misc { */ function isDumpEnabled($all = false) { $info = $this->getServerInfo(); + return !empty($info[$all ? 'pg_dumpall_path' : 'pg_dump_path']); } @@ -257,7 +296,7 @@ class Misc { } function getSubjectParams($subject) { - global $plugin_manager; + $plugin_manager = $this->plugin_manager; $vars = []; @@ -403,7 +442,7 @@ class Misc { if (!isset($vars['url'])) { $vars['url'] = '/redirect'; } - \PC::debug($vars, 'getSubjectParams'); + //\PC::debug($vars, 'getSubjectParams'); if ($vars['url'] == '/redirect' && isset($vars['params']['subject'])) { $vars['url'] = '/redirect/' . $vars['params']['subject']; unset($vars['params']['subject']); @@ -417,12 +456,6 @@ class Misc { return "{$vars['url']}?" . http_build_query($vars['params'], '', '&'); } - function getForm() { - if (!$this->form) { - $this->form = $this->setForm(); - } - return $this->form; - } /** * Sets the form tracking variable */ @@ -715,13 +748,13 @@ class Misc { } /** - * Prints the page header. If global variable $_no_output is + * Prints the page header. If member variable $this->_no_output is * set then no header is drawn. * @param $title The title of the page * @param $script script tag * @param $do_print boolean if false, the function will return the header content */ - function printHeader($title = '', $script = null, $do_print = true) { + function printHeader($title = '', $script = null, $do_print = true, $template = 'header.twig') { if (function_exists('newrelic_disable_autorum')) { newrelic_disable_autorum(); @@ -735,7 +768,7 @@ class Misc { $viewVars['appName'] = htmlspecialchars($this->appName) . ($title != '') ? htmlspecialchars(" - {$title}") : ''; - $header_html = $this->view->fetch('header.twig', $viewVars); + $header_html = $this->view->fetch($template, $viewVars); if ($script) { $header_html .= "{$script}\n"; @@ -770,14 +803,15 @@ class Misc { $lang = $this->lang; $footer_html = ''; - \PC::debug($this->_reload_browser, '$_reload_browser'); + //\PC::debug($this->_reload_browser, '$_reload_browser'); if ($this->_reload_browser) { $footer_html .= $this->printReload(false, false); } elseif ($this->_reload_drop_database) { $footer_html .= $this->printReload(true, false); } - - $footer_html .= "<a href=\"#\" class=\"bottom_link\">" . $lang['strgotoppage'] . "</a>"; + if (!$this->_no_bottom_link) { + $footer_html .= "<a href=\"#\" class=\"bottom_link\">" . $lang['strgotoppage'] . "</a>"; + } $footer_html .= "</body>\n"; $footer_html .= "</html>\n"; @@ -798,7 +832,8 @@ class Misc { function printBody($doBody = true, $bodyClass = '') { $bodyClass = htmlspecialchars($bodyClass); - $bodyHtml = "<body " . ($bodyClass == '' ? '' : " class=\"{$bodyClass}\"") . ">\n"; + $bodyHtml = '<body class="detailbody ' . ($bodyClass == '' ? '' : $bodyClass) . '">'; + $bodyHtml .= "\n"; if (!$this->_no_output && $doBody) { echo $bodyHtml; @@ -833,128 +868,6 @@ class Misc { } /** - * Display a link - * @param $link An associative array of link parameters to print - * link = array( - * 'attr' => array( // list of A tag attribute - * 'attrname' => attribute value - * ... - * ), - * 'content' => The link text - * 'fields' => (optionnal) the data from which content and attr's values are obtained - * ); - * the special attribute 'href' might be a string or an array. If href is an array it - * will be generated by getActionUrl. See getActionUrl comment for array format. - */ - function printLink($link, $do_print = true) { - - if (!isset($link['fields'])) { - $link['fields'] = $_REQUEST; - } - - $tag = "<a "; - foreach ($link['attr'] as $attr => $value) { - if ($attr == 'href' and is_array($value)) { - $tag .= 'href="' . htmlentities($this->getActionUrl($value, $link['fields'])) . '" '; - } else { - $tag .= htmlentities($attr) . '="' . value($value, $link['fields'], 'html') . '" '; - } - } - $tag .= ">" . value($link['content'], $link['fields'], 'html') . "</a>\n"; - - if ($do_print) { - echo $tag; - } else { - return $tag; - } - } - - /** - * Display a list of links - * @param $links An associative array of links to print. See printLink function for - * the links array format. - * @param $class An optional class or list of classes seprated by a space - * WARNING: This field is NOT escaped! No user should be able to inject something here, use with care. - * @param boolean $do_print true to echo, false to return - */ - function printLinksList($links, $class = '', $do_print = true) { - - $list_html = "<ul class=\"{$class}\">\n"; - foreach ($links as $link) { - $list_html .= "\t<li>"; - $list_html .= $this->printLink($link, false); - $list_html .= "</li>\n"; - } - $list_html .= "</ul>\n"; - if ($do_print) { - echo $list_html; - } else { - return $list_html; - } - } - - /** - * Display navigation tabs - * @param $tabs The name of current section (Ex: intro, server, ...), or an array with tabs (Ex: sqledit.php doFind function) - * @param $activetab The name of the tab to be highlighted. - * @param $print if false, return html - */ - function printTabs($tabs, $activetab, $do_print = true) { - global $misc, $data, $lang; - - if (is_string($tabs)) { - $_SESSION['webdbLastTab'][$tabs] = $activetab; - $tabs = $this->getNavTabs($tabs); - } - $tabs_html = ''; - if (count($tabs) > 0) { - - $tabs_html .= "<table class=\"tabs\"><tr>\n"; - - # FIXME: don't count hidden tabs - $width = (int) (100 / count($tabs)) . '%'; - foreach ($tabs as $tab_id => $tab) { - - $tabs[$tab_id]['active'] = $active = ($tab_id == $activetab) ? ' active' : ''; - - $tabs[$tab_id]['width'] = $width; - - if (!isset($tab['hide']) || $tab['hide'] !== true) { - - $tabs[$tab_id]['tablink'] = htmlentities($this->getActionUrl($tab, $_REQUEST)); - - $tablink = '<a href="' . $tabs[$tab_id]['tablink'] . '">'; - - if (isset($tab['icon']) && $icon = $this->icon($tab['icon'])) { - $tabs[$tab_id]['iconurl'] = $icon; - $tablink .= "<span class=\"icon\"><img src=\"{$icon}\" alt=\"{$tab['title']}\" /></span>"; - } - - $tablink .= "<span class=\"label\">{$tab['title']}</span></a>"; - - $tabs_html .= "<td style=\"width: {$width}\" class=\"tab{$active}\">"; - - if (isset($tab['help'])) { - $tabs_html .= $this->printHelp($tablink, $tab['help'], false); - } else { - $tabs_html .= $tablink; - } - - $tabs_html .= "</td>\n"; - } - } - $tabs_html .= "</tr></table>\n"; - } - - if ($do_print) { - echo $tabs_html; - } else { - return $tabs_html; - } - - } - - /** * Retrieve the tab info for a specific tab bar. * @param $section The name of the tab bar. */ @@ -972,12 +885,12 @@ class Misc { $tabs = [ 'intro' => [ 'title' => $lang['strintroduction'], - 'url' => "intro", + 'url' => "intro.php", 'icon' => 'Introduction', ], 'servers' => [ 'title' => $lang['strservers'], - 'url' => "servers", + 'url' => "servers.php", 'icon' => 'Servers', ], ]; @@ -1159,6 +1072,13 @@ class Misc { 'help' => 'pg.view', 'icon' => 'Views', ], + 'matviews' => [ + 'title' => 'M ' . $lang['strviews'], + 'url' => 'materialized_views.php', + 'urlvars' => ['subject' => 'schema'], + 'help' => 'pg.matview', + 'icon' => 'MViews', + ], 'sequences' => [ 'title' => $lang['strsequences'], 'url' => 'sequences.php', @@ -1412,6 +1332,65 @@ class Misc { ]; break; + case 'matview': + $tabs = [ + 'columns' => [ + 'title' => $lang['strcolumns'], + 'url' => 'viewproperties.php', + 'urlvars' => ['subject' => 'matview', 'matview' => Decorator::field('matview')], + 'icon' => 'Columns', + 'branch' => true, + ], + 'browse' => [ + 'title' => $lang['strbrowse'], + 'icon' => 'Columns', + 'url' => 'display.php', + 'urlvars' => [ + 'action' => 'confselectrows', + 'return' => 'schema', + 'subject' => 'matview', + 'matview' => Decorator::field('matview'), + ], + 'branch' => true, + ], + 'select' => [ + 'title' => $lang['strselect'], + 'icon' => 'Search', + 'url' => 'views.php', + 'urlvars' => ['action' => 'confselectrows', 'matview' => Decorator::field('matview')], + 'help' => 'pg.sql.select', + ], + 'definition' => [ + 'title' => $lang['strdefinition'], + 'url' => 'viewproperties.php', + 'urlvars' => ['subject' => 'matview', 'matview' => Decorator::field('matview'), 'action' => 'definition'], + 'icon' => 'Definition', + ], + 'rules' => [ + 'title' => $lang['strrules'], + 'url' => 'rules.php', + 'urlvars' => ['subject' => 'matview', 'matview' => Decorator::field('matview')], + 'help' => 'pg.rule', + 'icon' => 'Rules', + 'branch' => true, + ], + 'privileges' => [ + 'title' => $lang['strprivileges'], + 'url' => 'privileges.php', + 'urlvars' => ['subject' => 'matview', 'matview' => Decorator::field('matview')], + 'help' => 'pg.privilege', + 'icon' => 'Privileges', + ], + 'export' => [ + 'title' => $lang['strexport'], + 'url' => 'viewproperties.php', + 'urlvars' => ['subject' => 'matview', 'matview' => Decorator::field('matview'), 'action' => 'export'], + 'icon' => 'Export', + 'hide' => false, + ], + ]; + break; + case 'function': $tabs = [ 'definition' => [ @@ -1473,15 +1452,15 @@ class Misc { $tabs = [ 'sql' => [ 'title' => $lang['strsql'], - 'url' => '/sqledit/sql', - 'urlvars' => ['subject' => 'schema'], + 'url' => '/src/views/sqledit.php', + 'urlvars' => ['action' => 'sql', 'subject' => 'schema'], 'help' => 'pg.sql', 'icon' => 'SqlEditor', ], 'find' => [ 'title' => $lang['strfind'], - 'url' => '/sqledit/find', - 'urlvars' => ['subject' => 'schema'], + 'url' => '/src/views/sqledit.php', + 'urlvars' => ['action' => 'find', 'subject' => 'schema'], 'icon' => 'Search', ], ]; @@ -1574,410 +1553,6 @@ class Misc { } /** - * [printTopbar description] - * @param bool $do_print true to print, false to return html - * @return string - */ - function printTopbar($do_print = true) { - - $lang = $this->lang; - $plugin_manager = $this->plugin_manager; - $appName = $this->appName; - $appVersion = $this->appVersion; - $appLangFiles = $this->appLangFiles; - - $server_info = $this->getServerInfo(); - $reqvars = $this->getRequestVars('table'); - - $topbar_html = "<div class=\"topbar\"><table style=\"width: 100%\"><tr><td>"; - - if ($server_info && isset($server_info['platform']) && isset($server_info['username'])) { - /* top left informations when connected */ - $topbar_html .= sprintf($lang['strtopbar'], - '<span class="platform">' . htmlspecialchars($server_info['platform']) . '</span>', - '<span class="host">' . htmlspecialchars((empty($server_info['host'])) ? 'localhost' : $server_info['host']) . '</span>', - '<span class="port">' . htmlspecialchars($server_info['port']) . '</span>', - '<span class="username">' . htmlspecialchars($server_info['username']) . '</span>'); - - $topbar_html .= "</td>"; - - /* top right informations when connected */ - - $toplinks = [ - 'sql' => [ - 'attr' => [ - 'href' => [ - 'url' => '/sqledit/sql', - 'urlvars' => $reqvars, - ], - 'target' => "sqledit", - 'id' => 'toplink_sql', - ], - 'content' => $lang['strsql'], - ], - 'history' => [ - 'attr' => [ - 'href' => [ - 'url' => '/history.php', - 'urlvars' => array_merge($reqvars, [ - 'action' => 'pophistory', - ]), - ], - 'id' => 'toplink_history', - ], - 'content' => $lang['strhistory'], - ], - 'find' => [ - 'attr' => [ - 'href' => [ - 'url' => '/sqledit/find', - 'urlvars' => $reqvars, - ], - 'target' => "sqledit", - 'id' => 'toplink_find', - ], - 'content' => $lang['strfind'], - ], - 'logout' => [ - 'attr' => [ - 'href' => [ - 'url' => '/src/views/servers/logout', - 'urlvars' => [ - 'logoutServer' => "{$server_info['host']}:{$server_info['port']}:{$server_info['sslmode']}", - ], - ], - 'id' => 'toplink_logout', - ], - 'content' => $lang['strlogout'], - ], - ]; - - // Toplink hook's place - $plugin_functions_parameters = [ - 'toplinks' => &$toplinks, - ]; - - $plugin_manager->do_hook('toplinks', $plugin_functions_parameters); - - $topbar_html .= "<td style=\"text-align: right\">"; - - $topbar_html .= $this->printLinksList($toplinks, 'toplink', [], false); - - $topbar_html .= "</td>"; - - $sql_window_id = htmlentities('sqledit:' . $this->server_id); - $history_window_id = htmlentities('history:' . $this->server_id); - - $topbar_html .= "<script type=\"text/javascript\"> - $('#toplink_sql').click(function() { - window.open($(this).attr('href'),'{$sql_window_id}','toolbar=no,width=700,height=500,resizable=yes,scrollbars=yes').focus(); - return false; - }); - - $('#toplink_history').click(function() { - window.open($(this).attr('href'),'{$history_window_id}','toolbar=no,width=700,height=500,resizable=yes,scrollbars=yes').focus(); - return false; - }); - - $('#toplink_find').click(function() { - window.open($(this).attr('href'),'{$sql_window_id}','toolbar=no,width=700,height=500,resizable=yes,scrollbars=yes').focus(); - return false; - }); - "; - - if (isset($_SESSION['sharedUsername'])) { - $topbar_html .= sprintf(" - $('#toplink_logout').click(function() { - return confirm('%s'); - });", str_replace("'", "\'", $lang['strconfdropcred'])); - } - - $topbar_html .= " - </script>"; - } else { - $topbar_html .= "<span class=\"appname\">{$appName}</span> <span class=\"version\">{$appVersion}</span>"; - } - /* - echo "<td style=\"text-align: right; width: 1%\">"; - - echo "<form method=\"get\"><select name=\"language\" onchange=\"this.form.submit()\">\n"; - $language = isset($_SESSION['webdbLanguage']) ? $_SESSION['webdbLanguage'] : 'english'; - foreach ($appLangFiles as $k => $v) { - echo "<option value=\"{$k}\"", - ($k == $language) ? ' selected="selected"' : '', - ">{$v}</option>\n"; - } - echo "</select>\n"; - echo "<noscript><input type=\"submit\" value=\"Set Language\"></noscript>\n"; - foreach ($_GET as $key => $val) { - if ($key == 'language') continue; - echo "<input type=\"hidden\" name=\"$key\" value=\"", htmlspecialchars($val), "\" />\n"; - } - echo "</form>\n"; - - echo "</td>"; - */ - $topbar_html .= "</tr></table></div>\n"; - - if ($do_print) { - echo $topbar_html; - } else { - return $topbar_html; - } - } - - /** - * Display a bread crumb trail. - * @param $do_print true to echo, false to return html - */ - function printTrail($trail = [], $do_print = true) { - $lang = $this->lang; - - $trail_html = $this->printTopbar(false); - - if (is_string($trail)) { - $trail = $this->getTrail($trail); - } - - $trail_html .= "<div class=\"trail\"><table><tr>"; - - foreach ($trail as $crumb) { - $trail_html .= "<td class=\"crumb\">"; - $crumblink = "<a"; - - if (isset($crumb['url'])) { - $crumblink .= " href=\"{$crumb['url']}\""; - } - - if (isset($crumb['title'])) { - $crumblink .= " title=\"{$crumb['title']}\""; - } - - $crumblink .= ">"; - - if (isset($crumb['title'])) { - $iconalt = $crumb['title']; - } else { - $iconalt = 'Database Root'; - } - - if (isset($crumb['icon']) && $icon = $this->icon($crumb['icon'])) { - $crumblink .= "<span class=\"icon\"><img src=\"{$icon}\" alt=\"{$iconalt}\" /></span>"; - } - - $crumblink .= "<span class=\"label\">" . htmlspecialchars($crumb['text']) . "</span></a>"; - - if (isset($crumb['help'])) { - $trail_html .= $this->printHelp($crumblink, $crumb['help'], false); - } else { - $trail_html .= $crumblink; - } - - $trail_html .= "{$lang['strseparator']}"; - $trail_html .= "</td>"; - } - - $trail_html .= "</tr></table></div>\n"; - if ($do_print) { - echo $trail_html; - } else { - return $trail_html; - } - } - - /** - * Create a bread crumb trail of the object hierarchy. - * @param $object The type of object at the end of the trail. - */ - function getTrail($subject = null) { - global $lang, $data, $appName, $plugin_manager; - - $trail = []; - $vars = ''; - $done = false; - - $trail['root'] = [ - 'text' => $appName, - 'url' => '/redirect/root', - 'icon' => 'Introduction', - ]; - - if ($subject == 'root') { - $done = true; - } - - if (!$done) { - $server_info = $this->getServerInfo(); - $trail['server'] = [ - 'title' => $lang['strserver'], - 'text' => $server_info['desc'], - 'url' => $this->getHREFSubject('server'), - 'help' => 'pg.server', - 'icon' => 'Server', - ]; - } - if ($subject == 'server') { - $done = true; - } - - if (isset($_REQUEST['database']) && !$done) { - $trail['database'] = [ - 'title' => $lang['strdatabase'], - 'text' => $_REQUEST['database'], - 'url' => $this->getHREFSubject('database'), - 'help' => 'pg.database', - 'icon' => 'Database', - ]; - } elseif (isset($_REQUEST['rolename']) && !$done) { - $trail['role'] = [ - 'title' => $lang['strrole'], - 'text' => $_REQUEST['rolename'], - 'url' => $this->getHREFSubject('role'), - 'help' => 'pg.role', - 'icon' => 'Roles', - ]; - } - if ($subject == 'database' || $subject == 'role') { - $done = true; - } - - if (isset($_REQUEST['schema']) && !$done) { - $trail['schema'] = [ - 'title' => $lang['strschema'], - 'text' => $_REQUEST['schema'], - 'url' => $this->getHREFSubject('schema'), - 'help' => 'pg.schema', - 'icon' => 'Schema', - ]; - } - if ($subject == 'schema') { - $done = true; - } - - if (isset($_REQUEST['table']) && !$done) { - $trail['table'] = [ - 'title' => $lang['strtable'], - 'text' => $_REQUEST['table'], - 'url' => $this->getHREFSubject('table'), - 'help' => 'pg.table', - 'icon' => 'Table', - ]; - } elseif (isset($_REQUEST['view']) && !$done) { - $trail['view'] = [ - 'title' => $lang['strview'], - 'text' => $_REQUEST['view'], - 'url' => $this->getHREFSubject('view'), - 'help' => 'pg.view', - 'icon' => 'View', - ]; - } elseif (isset($_REQUEST['ftscfg']) && !$done) { - $trail['ftscfg'] = [ - 'title' => $lang['strftsconfig'], - 'text' => $_REQUEST['ftscfg'], - 'url' => $this->getHREFSubject('ftscfg'), - 'help' => 'pg.ftscfg.example', - 'icon' => 'Fts', - ]; - } - if ($subject == 'table' || $subject == 'view' || $subject == 'ftscfg') { - $done = true; - } - - if (!$done && !is_null($subject)) { - switch ($subject) { - case 'function': - $trail[$subject] = [ - 'title' => $lang['str' . $subject], - 'text' => $_REQUEST[$subject], - 'url' => $this->getHREFSubject('function'), - 'help' => 'pg.function', - 'icon' => 'Function', - ]; - break; - case 'aggregate': - $trail[$subject] = [ - 'title' => $lang['straggregate'], - 'text' => $_REQUEST['aggrname'], - 'url' => $this->getHREFSubject('aggregate'), - 'help' => 'pg.aggregate', - 'icon' => 'Aggregate', - ]; - break; - case 'column': - $trail['column'] = [ - 'title' => $lang['strcolumn'], - 'text' => $_REQUEST['column'], - 'icon' => 'Column', - 'url' => $this->getHREFSubject('column'), - ]; - break; - default: - if (isset($_REQUEST[$subject])) { - switch ($subject) { - case 'domain':$icon = 'Domain'; - break; - case 'sequence':$icon = 'Sequence'; - break; - case 'type':$icon = 'Type'; - break; - case 'operator':$icon = 'Operator'; - break; - default:$icon = null; - break; - } - $trail[$subject] = [ - 'title' => $lang['str' . $subject], - 'text' => $_REQUEST[$subject], - 'help' => 'pg.' . $subject, - 'icon' => $icon, - ]; - } - } - } - - // Trail hook's place - $plugin_functions_parameters = [ - 'trail' => &$trail, - 'section' => $subject, - ]; - - $plugin_manager->do_hook('trail', $plugin_functions_parameters); - - return $trail; - } - - /** - * Display the navlinks - * - * @param $navlinks - An array with the the attributes and values that will be shown. See printLinksList for array format. - * @param $place - Place where the $navlinks are displayed. Like 'display-browse', where 'display' is the file (display.php) - * @param $env - Associative array of defined variables in the scope of the caller. - * Allows to give some environnement details to plugins. - * and 'browse' is the place inside that code (doBrowse). - * @param bool $do_print if true, print html, if false, return html - */ - function printNavLinks($navlinks, $place, $env = [], $do_print = true) { - $plugin_manager = $this->plugin_manager; - - // Navlinks hook's place - $plugin_functions_parameters = [ - 'navlinks' => &$navlinks, - 'place' => $place, - 'env' => $env, - ]; - $plugin_manager->do_hook('navlinks', $plugin_functions_parameters); - - if (count($navlinks) > 0) { - if ($do_print) { - $this->printLinksList($navlinks, 'navlink'); - } else { - return $this->printLinksList($navlinks, 'navlink', false); - } - - } - } - - /** * Do multi-page navigation. Displays the prev, next and page options. * @param $page - the page currently viewed * @param $pages - the maximum number of pages @@ -1985,7 +1560,7 @@ class Misc { * @param $max_width - the number of pages to make available at any one time (default = 20) */ function printPages($page, $pages, $gets, $max_width = 20) { - global $lang; + $lang = $this->lang; $window = 10; @@ -2129,54 +1704,6 @@ class Misc { } } - /** - * Returns URL given an action associative array. - * NOTE: this function does not html-escape, only url-escape - * @param $action An associative array of the follow properties: - * 'url' => The first part of the URL (before the ?) - * 'urlvars' => Associative array of (URL variable => field name) - * these are appended to the URL - * @param $fields Field data from which 'urlfield' and 'vars' are obtained. - */ - function getActionUrl(&$action, &$fields) { - $url = value($action['url'], $fields); - - if ($url === false) { - return ''; - } - - if (!empty($action['urlvars'])) { - $urlvars = value($action['urlvars'], $fields); - } else { - $urlvars = []; - } - - /* set server, database and schema parameter if not presents */ - if (isset($urlvars['subject'])) { - $subject = value($urlvars['subject'], $fields); - } else { - $subject = ''; - } - - if (isset($_REQUEST['server']) and !isset($urlvars['server']) and $subject != 'root') { - $urlvars['server'] = $_REQUEST['server']; - if (isset($_REQUEST['database']) and !isset($urlvars['database']) and $subject != 'server') { - $urlvars['database'] = $_REQUEST['database']; - if (isset($_REQUEST['schema']) and !isset($urlvars['schema']) and $subject != 'database') { - $urlvars['schema'] = $_REQUEST['schema']; - } - } - } - - $sep = '?'; - foreach ($urlvars as $var => $varfield) { - $url .= $sep . value_url($var, $fields) . '=' . value_url($varfield, $fields); - $sep = '&'; - } - //return '/src/views/' . $url; - return $url; - } - function getRequestVars($subject = '') { $v = []; if (!empty($subject)) { @@ -2195,273 +1722,6 @@ class Misc { return $v; } - function printUrlVars(&$vars, &$fields, $do_print = true) { - $url_vars_html = ''; - foreach ($vars as $var => $varfield) { - $url_vars_html .= "{$var}=" . urlencode($fields[$varfield]) . "&"; - } - if ($do_print) { - echo $url_vars_html; - } else { - return $url_vars_html; - } - } - - /** - * Display a table of data. - * @param $tabledata A set of data to be formatted, as returned by $data->getDatabases() etc. - * @param $columns An associative array of columns to be displayed: - * $columns = array( - * column_id => array( - * 'title' => Column heading, - * 'class' => The class to apply on the column cells, - * 'field' => Field name for $tabledata->fields[...], - * 'help' => Help page for this column, - * ), ... - * ); - * @param $actions Actions that can be performed on each object: - * $actions = array( - * * multi action support - * * parameters are serialized for each entries and given in $_REQUEST['ma'] - * 'multiactions' => array( - * 'keycols' => Associative array of (URL variable => field name), // fields included in the form - * 'url' => URL submission, - * 'default' => Default selected action in the form. - * if null, an empty action is added & selected - * ), - * * actions * - * action_id => array( - * 'title' => Action heading, - * 'url' => Static part of URL. Often we rely - * relative urls, usually the page itself (not '' !), or just a query string, - * 'vars' => Associative array of (URL variable => field name), - * 'multiaction' => Name of the action to execute. - * Add this action to the multi action form - * ), ... - * ); - * @param $place Place where the $actions are displayed. Like 'display-browse', where 'display' is the file (display.php) - * and 'browse' is the place inside that code (doBrowse). - * @param $nodata (optional) Message to display if data set is empty. - * @param $pre_fn (optional) Name of a function to call for each row, - * it will be passed two params: $rowdata and $actions, - * it may be used to derive new fields or modify actions. - * It can return an array of actions specific to the row, - * or if nothing is returned then the standard actions are used. - * (see tblproperties.php and constraints.php for examples) - * The function must not must not store urls because - * they are relative and won't work out of context. - */ - function printTable(&$tabledata, &$columns, &$actions, $place, $nodata = null, $pre_fn = null) { - - $data = $this->data; - $misc = $this; - $lang = $this->lang; - $plugin_manager = $this->plugin_manager; - - // Action buttons hook's place - $plugin_functions_parameters = [ - 'actionbuttons' => &$actions, - 'place' => $place, - ]; - $plugin_manager->do_hook('actionbuttons', $plugin_functions_parameters); - - if ($has_ma = isset($actions['multiactions'])) { - $ma = $actions['multiactions']; - } - $tablehtml = ''; - - unset($actions['multiactions']); - - if ($tabledata->recordCount() > 0) { - - // Remove the 'comment' column if they have been disabled - if (!$this->conf['show_comments']) { - unset($columns['comment']); - } - - if (isset($columns['comment'])) { - // Uncomment this for clipped comments. - // TODO: This should be a user option. - //$columns['comment']['params']['clip'] = true; - } - - if ($has_ma) { - $tablehtml .= "<script src=\"/js/multiactionform.js\" type=\"text/javascript\"></script>\n"; - $tablehtml .= "<form id=\"multi_form\" action=\"{$ma['url']}\" method=\"post\" enctype=\"multipart/form-data\">\n"; - if (isset($ma['vars'])) { - foreach ($ma['vars'] as $k => $v) { - $tablehtml .= "<input type=\"hidden\" name=\"$k\" value=\"$v\" />"; - } - } - - } - - $tablehtml .= "<table>\n"; - $tablehtml .= "<tr>\n"; - - // Handle cases where no class has been passed - if (isset($column['class'])) { - $class = $column['class'] !== '' ? " class=\"{$column['class']}\"" : ''; - } else { - $class = ''; - } - - // Display column headings - if ($has_ma) { - $tablehtml .= "<th></th>"; - } - - foreach ($columns as $column_id => $column) { - switch ($column_id) { - case 'actions': - if (sizeof($actions) > 0) { - $tablehtml .= "<th class=\"data\" colspan=\"" . count($actions) . "\">{$column['title']}</th>\n"; - } - - break; - default: - $tablehtml .= "<th class=\"data{$class}\">"; - if (isset($column['help'])) { - $tablehtml .= $this->printHelp($column['title'], $column['help'], false); - } else { - $tablehtml .= $column['title']; - } - - $tablehtml .= "</th>\n"; - break; - } - } - $tablehtml .= "</tr>\n"; - - // Display table rows - $i = 0; - while (!$tabledata->EOF) { - $id = ($i % 2) + 1; - - unset($alt_actions); - if (!is_null($pre_fn)) { - $alt_actions = $pre_fn($tabledata, $actions); - } - - if (!isset($alt_actions)) { - $alt_actions = &$actions; - } - - $tablehtml .= "<tr class=\"data{$id}\">\n"; - if ($has_ma) { - foreach ($ma['keycols'] as $k => $v) { - $a[$k] = $tabledata->fields[$v]; - } - - $tablehtml .= "<td>"; - $tablehtml .= "<input type=\"checkbox\" name=\"ma[]\" value=\"" . htmlentities(serialize($a), ENT_COMPAT, 'UTF-8') . "\" />"; - $tablehtml .= "</td>\n"; - } - - foreach ($columns as $column_id => $column) { - - // Apply default values for missing parameters - if (isset($column['url']) && !isset($column['vars'])) { - $column['vars'] = []; - } - - switch ($column_id) { - case 'actions': - foreach ($alt_actions as $action) { - if (isset($action['disable']) && $action['disable'] === true) { - $tablehtml .= "<td></td>\n"; - } else { - $tablehtml .= "<td class=\"opbutton{$id} {$class}\">"; - $action['fields'] = $tabledata->fields; - $tablehtml .= $this->printLink($action, false); - $tablehtml .= "</td>\n"; - } - } - break; - case 'comment': - $tablehtml .= "<td class='comment_cell'>"; - $val = value($column['field'], $tabledata->fields); - if (!is_null($val)) { - $tablehtml .= htmlentities($val); - } - $tablehtml .= "</td>"; - break; - default: - $tablehtml .= "<td{$class}>"; - $val = value($column['field'], $tabledata->fields); - if (!is_null($val)) { - if (isset($column['url'])) { - $tablehtml .= "<a href=\"{$column['url']}"; - $tablehtml .= $this->printUrlVars($column['vars'], $tabledata->fields, false); - $tablehtml .= "\">"; - } - $type = isset($column['type']) ? $column['type'] : null; - $params = isset($column['params']) ? $column['params'] : []; - $tablehtml .= $this->printVal($val, $type, $params); - if (isset($column['url'])) { - $tablehtml .= "</a>"; - } - - } - - $tablehtml .= "</td>\n"; - break; - } - } - $tablehtml .= "</tr>\n"; - - $tabledata->moveNext(); - $i++; - } - $tablehtml .= "</table>\n"; - - // Multi action table footer w/ options & [un]check'em all - if ($has_ma) { - // if default is not set or doesn't exist, set it to null - if (!isset($ma['default']) || !isset($actions[$ma['default']])) { - $ma['default'] = null; - } - - $tablehtml .= "<br />\n"; - $tablehtml .= "<table>\n"; - $tablehtml .= "<tr>\n"; - $tablehtml .= "<th class=\"data\" style=\"text-align: left\" colspan=\"3\">{$lang['stractionsonmultiplelines']}</th>\n"; - $tablehtml .= "</tr>\n"; - $tablehtml .= "<tr class=\"row1\">\n"; - $tablehtml .= "<td>"; - $tablehtml .= "<a href=\"#\" onclick=\"javascript:checkAll(true);\">{$lang['strselectall']}</a> / "; - $tablehtml .= "<a href=\"#\" onclick=\"javascript:checkAll(false);\">{$lang['strunselectall']}</a></td>\n"; - $tablehtml .= "<td> ---> </td>\n"; - $tablehtml .= "<td>\n"; - $tablehtml .= "\t<select name=\"action\">\n"; - if ($ma['default'] == null) { - $tablehtml .= "\t\t<option value=\"\">--</option>\n"; - } - - foreach ($actions as $k => $a) { - if (isset($a['multiaction'])) { - $tablehtml .= "\t\t<option value=\"{$a['multiaction']}\"" . ($ma['default'] == $k ? ' selected="selected"' : '') . ">{$a['content']}</option>\n"; - } - } - - $tablehtml .= "\t</select>\n"; - $tablehtml .= "<input type=\"submit\" value=\"{$lang['strexecute']}\" />\n"; - $tablehtml .= $this->getForm(); - $tablehtml .= "</td>\n"; - $tablehtml .= "</tr>\n"; - $tablehtml .= "</table>\n"; - $tablehtml .= '</form>'; - }; - - } else { - if (!is_null($nodata)) { - $tablehtml .= "<p>{$nodata}</p>\n"; - } - - } - return $tablehtml; - } - /** Produce XML data for the browser tree * @param $treedata A set of records to populate the tree. * @param $attrs Attributes for tree items @@ -2525,20 +1785,20 @@ class Misc { foreach ($treedata as $rec) { echo "<tree"; - echo value_xml_attr('text', $attrs['text'], $rec); - echo value_xml_attr('action', $attrs['action'], $rec); - echo value_xml_attr('src', $attrs['branch'], $rec); + echo Decorator::value_xml_attr('text', $attrs['text'], $rec); + echo Decorator::value_xml_attr('action', $attrs['action'], $rec); + echo Decorator::value_xml_attr('src', $attrs['branch'], $rec); - $icon = $this->icon(value($attrs['icon'], $rec)); - echo value_xml_attr('icon', $icon, $rec); - echo value_xml_attr('iconaction', $attrs['iconAction'], $rec); + $icon = $this->icon(Decorator::get_sanitized_value($attrs['icon'], $rec)); + echo Decorator::value_xml_attr('icon', $icon, $rec); + echo Decorator::value_xml_attr('iconaction', $attrs['iconAction'], $rec); if (!empty($attrs['openicon'])) { - $icon = $this->icon(value($attrs['openIcon'], $rec)); + $icon = $this->icon(Decorator::get_sanitized_value($attrs['openIcon'], $rec)); } - echo value_xml_attr('openicon', $icon, $rec); + echo Decorator::value_xml_attr('openicon', $icon, $rec); - echo value_xml_attr('tooltip', $attrs['toolTip'], $rec); + echo Decorator::value_xml_attr('tooltip', $attrs['toolTip'], $rec); echo " />\n"; } @@ -2601,7 +1861,8 @@ class Misc { * @return The escaped string */ function escapeShellArg($str) { - global $data, $lang; + $data = $this->getDatabaseAccessor(); + $lang = $this->lang; if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { // Due to annoying PHP bugs, shell arguments cannot be escaped @@ -2625,7 +1886,7 @@ class Misc { * @return The escaped string */ function escapeShellCmd($str) { - global $data; + $data = $this->getDatabaseAccessor(); if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { $data->fieldClean($str); @@ -2660,12 +1921,12 @@ class Misc { 'id' => $i, 'desc' => $group['desc'], 'icon' => 'Servers', - 'action' => url('/views/servers', + 'action' => Decorator::url('/views/servers', [ 'group' => Decorator::field('id'), ] ), - 'branch' => url('/tree/servers', + 'branch' => Decorator::url('/tree/servers', [ 'group' => $i, ] @@ -2680,12 +1941,12 @@ class Misc { 'id' => 'all', 'desc' => $lang['strallservers'], 'icon' => 'Servers', - 'action' => url('/views/servers', + 'action' => Decorator::url('/views/servers', [ 'group' => Decorator::field('id'), ] ), - 'branch' => url('/tree/servers', + 'branch' => Decorator::url('/tree/servers', [ 'group' => 'all', ] @@ -2744,9 +2005,9 @@ class Misc { ); if (isset($srvs[$server_id]['username'])) { $srvs[$server_id]['icon'] = 'Server'; - $srvs[$server_id]['branch'] = Decorator::branchurl('all_db.php', + $srvs[$server_id]['branch'] = Decorator::url('all_db.php', [ - + 'action' => 'tree', 'subject' => 'server', 'server' => Decorator::field('id'), ] @@ -2780,11 +2041,14 @@ class Misc { if ($server_id !== null) { $this->server_id = $server_id; + } else if ($this->server_info !== null) { + return $this->server_info; } // Check for the server in the logged-in list if (isset($_SESSION['webdbLogin'][$this->server_id])) { - return $_SESSION['webdbLogin'][$this->server_id]; + $this->server_info = $_SESSION['webdbLogin'][$this->server_id]; + return $this->server_info; } // Otherwise, look for it in the conf file @@ -2797,18 +2061,21 @@ class Misc { $this->setReloadBrowser(true); $this->setServerInfo(null, $info, $this->server_id); } + $this->server_info = $info; + return $this->server_info; - return $info; } } if ($server_id === null) { - - return null; + $this->server_info = null; + return $this->server_info; } else { + $this->server_info = null; // Unable to find a matching server, are we being hacked? echo $this->lang['strinvalidserverparam']; + exit; } } @@ -2822,7 +2089,7 @@ class Misc { * server. */ function setServerInfo($key, $value, $server_id = null) { - \PC::debug('setsetverinfo'); + //\PC::debug('setsetverinfo'); if ($server_id === null && isset($_REQUEST['server'])) { $server_id = $_REQUEST['server']; } @@ -2831,7 +2098,7 @@ class Misc { if ($value === null) { unset($_SESSION['webdbLogin'][$server_id]); } else { - \PC::debug(['server_id' => $server_id, 'value' => $value], 'webdbLogin'); + //\PC::debug(['server_id' => $server_id, 'value' => $value], 'webdbLogin null key'); $_SESSION['webdbLogin'][$server_id] = $value; } @@ -2839,7 +2106,7 @@ class Misc { if ($value === null) { unset($_SESSION['webdbLogin'][$server_id][$key]); } else { - \PC::debug(['server_id' => $server_id, 'key' => $key, 'value' => $value], 'webdbLogin'); + //\PC::debug(['server_id' => $server_id, 'key' => $key, 'value' => $value], __FILE__ . ' ' . __LINE__ . ' webdbLogin key ' . $key); $_SESSION['webdbLogin'][$server_id][$key] = $value; } @@ -2853,7 +2120,7 @@ class Misc { * @return $data->seSchema() on error */ function setCurrentSchema($schema) { - global $data; + $data = $this->getDatabaseAccessor(); $status = $data->setSchema($schema); if ($status != 0) { @@ -2982,7 +2249,7 @@ class Misc { * ) **/ function getAutocompleteFKProperties($table) { - global $data; + $data = $this->getDatabaseAccessor(); $fksprops = [ 'byconstr' => [], diff --git a/src/controllers/ACInsertController.php b/src/controllers/ACInsertController.php new file mode 100644 index 00000000..085130ed --- /dev/null +++ b/src/controllers/ACInsertController.php @@ -0,0 +1,111 @@ +<?php + +namespace PHPPgAdmin\Controller; + +/** + * Base controller class + */ +class ACInsertController extends BaseController { + public $_name = 'ACInsertController'; + + public function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + $action = $this->action; + + if (isset($_POST['offset'])) { + $offset = " OFFSET {$_POST['offset']}"; + } else { + $_POST['offset'] = 0; + $offset = " OFFSET 0"; + } + + $keynames = []; + foreach ($_POST['fkeynames'] as $k => $v) { + $fkeynames[$k] = html_entity_decode($v, ENT_QUOTES); + } + + $keyspos = array_combine($fkeynames, $_POST['keys']); + + $f_schema = html_entity_decode($_POST['f_schema'], ENT_QUOTES); + $data->fieldClean($f_schema); + $f_table = html_entity_decode($_POST['f_table'], ENT_QUOTES); + $data->fieldClean($f_table); + $f_attname = $fkeynames[$_POST['fattpos'][0]]; + $data->fieldClean($f_attname); + + $q = "SELECT * + FROM \"{$f_schema}\".\"{$f_table}\" + WHERE \"{$f_attname}\"::text LIKE '{$_POST['fvalue']}%' + ORDER BY \"{$f_attname}\" LIMIT 12 {$offset};"; + + $res = $data->selectSet($q); + + if (!$res->EOF) { + echo "<table class=\"ac_values\">"; + echo '<tr>'; + foreach (array_keys($res->fields) as $h) { + echo '<th>'; + + if (in_array($h, $fkeynames)) { + echo '<img src="' . $misc->icon('ForeignKey') . '" alt="[referenced key]" />'; + } + + echo htmlentities($h, ENT_QUOTES, 'UTF-8'), '</th>'; + + } + echo "</tr>\n"; + $i = 0; + while ((!$res->EOF) && ($i < 11)) { + $j = 0; + echo "<tr class=\"acline\">"; + foreach ($res->fields as $n => $v) { + $finfo = $res->fetchField($j++); + if (in_array($n, $fkeynames)) { + echo "<td><a href=\"javascript:void(0)\" class=\"fkval\" name=\"{$keyspos[$n]}\">", + $misc->printVal($v, $finfo->type, ['clip' => 'collapsed']), + "</a></td>"; + } else { + echo "<td><a href=\"javascript:void(0)\">", + $misc->printVal($v, $finfo->type, ['clip' => 'collapsed']), + "</a></td>"; + } + + } + echo "</tr>\n"; + $i++; + $res->moveNext(); + } + echo "</table>\n"; + + $page_tests = ''; + + $js = "<script type=\"text/javascript\">\n"; + + if ($_POST['offset']) { + echo "<a href=\"javascript:void(0)\" id=\"fkprev\"><< Prev</a>"; + $js .= "fkl_hasprev=true;\n"; + } else { + $js .= "fkl_hasprev=false;\n"; + } + + if ($res->recordCount() == 12) { + $js .= "fkl_hasnext=true;\n"; + echo " <a href=\"javascript:void(0)\" id=\"fknext\">Next >></a>"; + } else { + $js .= "fkl_hasnext=false;\n"; + } + + echo $js . "</script>"; + } else { + printf("<p>{$lang['strnofkref']}</p>", "\"{$_POST['f_schema']}\".\"{$_POST['f_table']}\".\"{$fkeynames[$_POST['fattpos']]}\""); + + if ($_POST['offset']) { + echo "<a href=\"javascript:void(0)\" class=\"fkprev\">Prev <<</a>"; + } + } + + } +} diff --git a/src/controllers/AdminTrait.php b/src/controllers/AdminTrait.php index b9cbf1b0..f35df87c 100644 --- a/src/controllers/AdminTrait.php +++ b/src/controllers/AdminTrait.php @@ -1,5 +1,6 @@ <?php namespace PHPPgAdmin\Controller; +use \PHPPgAdmin\Decorators\Decorator; trait AdminTrait { @@ -13,7 +14,7 @@ trait AdminTrait { $script = $this->script; $misc = $this->misc; $lang = $this->lang; - $data = $this->getDatabaseAccessor(); + $data = $misc->getDatabaseAccessor(); if (($type == 'table') && empty($_REQUEST['table']) && empty($_REQUEST['ma'])) { $this->doDefault($lang['strspecifytabletocluster']); @@ -22,7 +23,7 @@ trait AdminTrait { if ($confirm) { if (isset($_REQUEST['ma'])) { - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strclusterindex'], 'pg.index.cluster'); echo "<form action=\"/src/views/{$script}\" method=\"post\">\n"; @@ -33,7 +34,7 @@ trait AdminTrait { } } // END if multi cluster else { - $misc->printTrail($type); + $this->printTrail($type); $misc->printTitle($lang['strclusterindex'], 'pg.index.cluster'); echo "<form action=\"/src/views/{$script}\" method=\"post\">\n"; @@ -102,7 +103,7 @@ trait AdminTrait { $script = $this->script; $misc = $this->misc; $lang = $this->lang; - $data = $this->getDatabaseAccessor(); + $data = $misc->getDatabaseAccessor(); if (($type == 'table') && empty($_REQUEST['table']) && empty($_REQUEST['ma'])) { $this->doDefault($lang['strspecifytabletoreindex']); @@ -111,7 +112,7 @@ trait AdminTrait { if ($confirm) { if (isset($_REQUEST['ma'])) { - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strreindex'], 'pg.reindex'); echo "<form action=\"/src/views/{$script}\" method=\"post\">\n"; @@ -122,7 +123,7 @@ trait AdminTrait { } } // END if multi reindex else { - $misc->printTrail($type); + $this->printTrail($type); $misc->printTitle($lang['strreindex'], 'pg.reindex'); echo "<form action=\"/src/views/{$script}\" method=\"post\">\n"; @@ -181,10 +182,11 @@ trait AdminTrait { */ public function doAnalyze($type, $confirm = false) { $this->script = ($type == 'database') ? 'database.php' : 'tables.php'; - $script = $this->script; - $data = $this->data; - $misc = $this->misc; - $lang = $this->lang; + + $script = $this->script; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); if (($type == 'table') && empty($_REQUEST['table']) && empty($_REQUEST['ma'])) { $this->doDefault($lang['strspecifytabletoanalyze']); @@ -192,19 +194,21 @@ trait AdminTrait { } if ($confirm) { + if (isset($_REQUEST['ma'])) { - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['stranalyze'], 'pg.analyze'); echo "<form action=\"/src/views/{$script}\" method=\"post\">\n"; foreach ($_REQUEST['ma'] as $v) { $a = unserialize(htmlspecialchars_decode($v, ENT_QUOTES)); + \Kint::dump($a); echo "<p>", sprintf($lang['strconfanalyzetable'], $misc->printVal($a['table'])), "</p>\n"; echo "<input type=\"hidden\" name=\"table[]\" value=\"", htmlspecialchars($a['table']), "\" />\n"; } } // END if multi analyze else { - $misc->printTrail($type); + $this->printTrail($type); $misc->printTitle($lang['stranalyze'], 'pg.analyze'); echo "<form action=\"/src/views/{$script}\" method=\"post\">\n"; @@ -272,7 +276,7 @@ trait AdminTrait { if ($confirm) { if (isset($_REQUEST['ma'])) { - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strvacuum'], 'pg.vacuum'); echo "<form action=\"/src/views/{$script}\" method=\"post\">\n"; @@ -283,7 +287,7 @@ trait AdminTrait { } } else { // END if multi vacuum - $misc->printTrail($type); + $this->printTrail($type); $misc->printTitle($lang['strvacuum'], 'pg.vacuum'); echo "<form action=\"/src/views/{$script}\" method=\"post\">\n"; @@ -344,7 +348,7 @@ trait AdminTrait { $misc = $this->misc; $lang = $this->lang; - $data = $this->getDatabaseAccessor(); + $data = $misc->getDatabaseAccessor(); if (empty($_REQUEST['table'])) { $this->doAdmin($type, '', $lang['strspecifyeditvacuumtable']); @@ -354,7 +358,7 @@ trait AdminTrait { $script = ($type == 'database') ? 'database.php' : 'tables.php'; if ($confirm) { - $misc->printTrail($type); + $this->printTrail($type); $misc->printTitle(sprintf($lang['streditvacuumtable'], $misc->printVal($_REQUEST['table']))); $misc->printMsg(sprintf($msg, $misc->printVal($_REQUEST['table']))); @@ -460,7 +464,7 @@ trait AdminTrait { $misc = $this->misc; $lang = $this->lang; - $data = $this->getDatabaseAccessor(); + $data = $misc->getDatabaseAccessor(); if (empty($_REQUEST['table'])) { $this->doAdmin($type, '', $lang['strspecifydelvacuumtable']); @@ -468,8 +472,8 @@ trait AdminTrait { } if ($confirm) { - $misc->printTrail($type); - $misc->printTabs($type, 'admin'); + $this->printTrail($type); + $this->printTabs($type, 'admin'); $script = ($type == 'database') ? 'database.php' : 'tables.php'; @@ -517,10 +521,10 @@ trait AdminTrait { $misc = $this->misc; $lang = $this->lang; - $data = $this->getDatabaseAccessor(); + $data = $misc->getDatabaseAccessor(); - $misc->printTrail($type); - $misc->printTabs($type, 'admin'); + $this->printTrail($type); + $this->printTabs($type, 'admin'); $misc->printMsg($msg); if ($type == 'database') { @@ -648,37 +652,37 @@ trait AdminTrait { ], 'autovacuum_enabled' => [ 'title' => $lang['strenabled'], - 'field' => callback('enlight', ['autovacuum_enabled', $defaults['autovacuum']]), + 'field' => Decorator::callback('enlight', ['autovacuum_enabled', $defaults['autovacuum']]), 'type' => 'verbatim', ], 'autovacuum_vacuum_threshold' => [ 'title' => $lang['strvacuumbasethreshold'], - 'field' => callback('enlight', ['autovacuum_vacuum_threshold', $defaults['autovacuum_vacuum_threshold']]), + 'field' => Decorator::callback('enlight', ['autovacuum_vacuum_threshold', $defaults['autovacuum_vacuum_threshold']]), 'type' => 'verbatim', ], 'autovacuum_vacuum_scale_factor' => [ 'title' => $lang['strvacuumscalefactor'], - 'field' => callback('enlight', ['autovacuum_vacuum_scale_factor', $defaults['autovacuum_vacuum_scale_factor']]), + 'field' => Decorator::callback('enlight', ['autovacuum_vacuum_scale_factor', $defaults['autovacuum_vacuum_scale_factor']]), 'type' => 'verbatim', ], 'autovacuum_analyze_threshold' => [ 'title' => $lang['stranalybasethreshold'], - 'field' => callback('enlight', ['autovacuum_analyze_threshold', $defaults['autovacuum_analyze_threshold']]), + 'field' => Decorator::callback('enlight', ['autovacuum_analyze_threshold', $defaults['autovacuum_analyze_threshold']]), 'type' => 'verbatim', ], 'autovacuum_analyze_scale_factor' => [ 'title' => $lang['stranalyzescalefactor'], - 'field' => callback('enlight', ['autovacuum_analyze_scale_factor', $defaults['autovacuum_analyze_scale_factor']]), + 'field' => Decorator::callback('enlight', ['autovacuum_analyze_scale_factor', $defaults['autovacuum_analyze_scale_factor']]), 'type' => 'verbatim', ], 'autovacuum_vacuum_cost_delay' => [ 'title' => $lang['strvacuumcostdelay'], - 'field' => concat(callback('enlight', ['autovacuum_vacuum_cost_delay', $defaults['autovacuum_vacuum_cost_delay']]), 'ms'), + 'field' => Decorator::concat(Decorator::callback('enlight', ['autovacuum_vacuum_cost_delay', $defaults['autovacuum_vacuum_cost_delay']]), 'ms'), 'type' => 'verbatim', ], 'autovacuum_vacuum_cost_limit' => [ 'title' => $lang['strvacuumcostlimit'], - 'field' => callback('enlight', ['autovacuum_vacuum_cost_limit', $defaults['autovacuum_vacuum_cost_limit']]), + 'field' => Decorator::callback('enlight', ['autovacuum_vacuum_cost_limit', $defaults['autovacuum_vacuum_cost_limit']]), 'type' => 'verbatim', ], ]; @@ -725,7 +729,7 @@ trait AdminTrait { ); } - echo $misc->printTable($autovac, $columns, $actions, 'admin-admin', $lang['strnovacuumconf']); + echo $this->printTable($autovac, $columns, $actions, 'admin-admin', $lang['strnovacuumconf']); if (($type == 'table') and ($autovac->recordCount() == 0)) { echo "<br />"; diff --git a/src/controllers/AggregateController.php b/src/controllers/AggregateController.php index 9246bbd3..f852e498 100644 --- a/src/controllers/AggregateController.php +++ b/src/controllers/AggregateController.php @@ -9,9 +9,97 @@ use \PHPPgAdmin\Decorators\Decorator; class AggregateController extends BaseController { public $_name = 'AggregateController'; -/** - * Actually creates the new aggregate in the database - */ + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + + $misc->printHeader($lang['straggregates']); + $misc->printBody(); + + switch ($action) { + case 'create': + $this->doCreate(); + break; + case 'save_create': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveCreate(); + } + + break; + case 'alter': + $this->doAlter(); + break; + case 'save_alter': + if (isset($_POST['alter'])) { + $this->doSaveAlter(); + } else { + $this->doProperties(); + } + + break; + case 'drop': + if (isset($_POST['drop'])) { + $this->doDrop(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + default: + $this->doDefault(); + break; + case 'properties': + $this->doProperties(); + break; + } + + return $misc->printFooter(); + + } + + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $aggregates = $data->getAggregates(); + + $proto = Decorator::concat(Decorator::field('proname'), ' (', Decorator::field('proargtypes'), ')'); + $reqvars = $misc->getRequestVars('aggregate'); + + $attrs = [ + 'text' => $proto, + 'icon' => 'Aggregate', + 'toolTip' => Decorator::field('aggcomment'), + 'action' => Decorator::redirecturl('redirect.php', + $reqvars, + [ + 'action' => 'properties', + 'aggrname' => Decorator::field('proname'), + 'aggrtype' => Decorator::field('proargtypes'), + ] + ), + ]; + + return $misc->printTree($aggregates, $attrs, 'aggregates'); + } + + /** + * Actually creates the new aggregate in the database + */ public function doSaveCreate() { $conf = $this->conf; $misc = $this->misc; @@ -84,7 +172,7 @@ class AggregateController extends BaseController { $_REQUEST['aggrcomment'] = ''; } - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strcreateaggregate'], 'pg.aggregate.create'); $misc->printMsg($msg); @@ -158,7 +246,7 @@ class AggregateController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('aggregate'); + $this->printTrail('aggregate'); $misc->printTitle($lang['stralter'], 'pg.aggregate.alter'); $misc->printMsg($msg); @@ -205,7 +293,7 @@ class AggregateController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('aggregate'); + $this->printTrail('aggregate'); $misc->printTitle($lang['strdrop'], 'pg.aggregate.drop'); echo "<p>", sprintf($lang['strconfdropaggregate'], htmlspecialchars($_REQUEST['aggrname'])), "</p>\n"; @@ -240,7 +328,7 @@ class AggregateController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('aggregate'); + $this->printTrail('aggregate'); $misc->printTitle($lang['strproperties'], 'pg.aggregate'); $misc->printMsg($msg); @@ -326,7 +414,7 @@ class AggregateController extends BaseController { 'content' => $lang['strdrop'], ]; - $misc->printNavLinks($navlinks, 'aggregates-properties', get_defined_vars()); + $this->printNavLinks($navlinks, 'aggregates-properties', get_defined_vars()); } /** @@ -337,8 +425,8 @@ class AggregateController extends BaseController { $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('schema'); - $misc->printTabs('schema', 'aggregates'); + $this->printTrail('schema'); + $this->printTabs('schema', 'aggregates'); $misc->printMsg($msg); $aggregates = $data->getAggregates(); @@ -404,7 +492,7 @@ class AggregateController extends BaseController { unset($actions['alter']); } - echo $misc->printTable($aggregates, $columns, $actions, 'aggregates-aggregates', $lang['strnoaggregates']); + echo $this->printTable($aggregates, $columns, $actions, 'aggregates-aggregates', $lang['strnoaggregates']); $navlinks = [ 'create' => [ @@ -422,7 +510,7 @@ class AggregateController extends BaseController { 'content' => $lang['strcreateaggregate'], ], ]; - $misc->printNavLinks($navlinks, 'aggregates-aggregates', get_defined_vars()); + $this->printNavLinks($navlinks, 'aggregates-aggregates', get_defined_vars()); } } diff --git a/src/controllers/AllDBController.php b/src/controllers/AllDBController.php index f1237d84..bc3fecbc 100644 --- a/src/controllers/AllDBController.php +++ b/src/controllers/AllDBController.php @@ -7,10 +7,94 @@ use \PHPPgAdmin\Decorators\Decorator; * Base controller class */ class AllDBController extends BaseController { - public $_name = 'AllDBController'; -/** - * Display a form for alter and perform actual alter - */ + public $_name = 'AllDBController'; + public $table_place = 'all_db-databases'; + + public function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + + if ($action == 'tree') { + return $this->doTree(); + } + + $misc->printHeader($lang['strdatabases']); + $misc->printBody(); + + switch ($action) { + case 'export': + $this->doExport(); + break; + case 'save_create': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveCreate(); + } + + break; + case 'create': + $this->doCreate(); + break; + case 'drop': + if (isset($_REQUEST['drop'])) { + $this->doDrop(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop': + doDrop(true); + break; + case 'alter': + if (isset($_POST['oldname']) && isset($_POST['newname']) && !isset($_POST['cancel'])) { + $this->doAlter(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_alter': + $this->doAlter(true); + break; + default: + $this->doDefault(); + + break; + } + + return $misc->printFooter(); + + } + + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $databases = $data->getDatabases(); + + $reqvars = $misc->getRequestVars('database'); + + $attrs = [ + 'text' => Decorator::field('datname'), + 'icon' => 'Database', + 'toolTip' => Decorator::field('datcomment'), + 'action' => Decorator::redirecturl('redirect.php', $reqvars, ['database' => Decorator::field('datname')]), + 'branch' => Decorator::url('database.php', $reqvars, ['action' => 'tree', 'database' => Decorator::field('datname')]), + ]; + + return $misc->printTree($databases, $attrs, 'databases'); + } + + /** + * Display a form for alter and perform actual alter + */ public function doAlter($confirm) { $conf = $this->conf; $misc = $this->misc; @@ -18,7 +102,7 @@ class AllDBController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('database'); + $this->printTrail('database'); $misc->printTitle($lang['stralter'], 'pg.database.alter'); echo "<form action=\"/src/views/all_db.php\" method=\"post\">\n"; @@ -80,9 +164,9 @@ class AllDBController extends BaseController { } } -/** - * Show confirmation of drop and perform actual drop - */ + /** + * Show confirmation of drop and perform actual drop + */ public function doDrop($confirm) { $conf = $this->conf; $misc = $this->misc; @@ -96,7 +180,7 @@ class AllDBController extends BaseController { if ($confirm) { - $misc->printTrail('database'); + $this->printTrail('database'); $misc->printTitle($lang['strdrop'], 'pg.database.drop'); echo "<form action=\"/src/views/all_db.php\" method=\"post\">\n"; @@ -148,16 +232,16 @@ class AllDBController extends BaseController { } //END DROP } // END FUNCTION -/** - * Displays a screen where they can enter a new database - */ + /** + * Displays a screen where they can enter a new database + */ public function doCreate($msg = '') { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('server'); + $this->printTrail('server'); $misc->printTitle($lang['strcreatedatabase'], 'pg.database.create'); $misc->printMsg($msg); @@ -282,9 +366,9 @@ class AllDBController extends BaseController { echo "</form>\n"; } -/** - * Actually creates the new view in the database - */ + /** + * Actually creates the new view in the database + */ public function doSaveCreate() { $conf = $this->conf; $misc = $this->misc; @@ -327,17 +411,17 @@ class AllDBController extends BaseController { } } -/** - * Displays options for cluster download - */ + /** + * Displays options for cluster download + */ public function doExport($msg = '') { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('server'); - $misc->printTabs('server', 'export'); + $this->printTrail('server'); + $this->printTabs('server', 'export'); $misc->printMsg($msg); echo "<form action=\"/src/views/dbexport.php\" method=\"post\">\n"; @@ -386,8 +470,8 @@ class AllDBController extends BaseController { $misc = $this->misc; $lang = $this->lang; - $misc->printTrail('server'); - $misc->printTabs('server', 'databases'); + $this->printTrail('server'); + $this->printTabs('server', 'databases'); $misc->printMsg($msg); $data = $misc->getDatabaseAccessor(); $databases = $data->getDatabases(); @@ -498,7 +582,7 @@ class AllDBController extends BaseController { unset($actions['privileges']); } - echo $misc->printTable($databases, $columns, $actions, 'all_db-databases', $lang['strnodatabases']); + echo $this->printTable($databases, $columns, $actions, $this->table_place, $lang['strnodatabases']); $navlinks = [ 'create' => [ @@ -514,7 +598,7 @@ class AllDBController extends BaseController { 'content' => $lang['strcreatedatabase'], ], ]; - $misc->printNavLinks($navlinks, 'all_db-databases', get_defined_vars()); + $this->printNavLinks($navlinks, $this->table_place, get_defined_vars()); } diff --git a/src/controllers/BaseController.php b/src/controllers/BaseController.php index 6c5de796..7326fd7f 100644 --- a/src/controllers/BaseController.php +++ b/src/controllers/BaseController.php @@ -6,40 +6,136 @@ namespace PHPPgAdmin\Controller; * Base controller class */ class BaseController { - - private $_connection = null; - private $_no_db_connection = false; - private $_reload_browser = false; - private $app = null; - private $data = null; - private $database = null; - private $server_id = null; - public $appLangFiles = []; - public $appThemes = []; - public $appName = ''; - public $appVersion = ''; - public $form = ''; - public $href = ''; - public $lang = []; - public $_name = 'BaseController'; + private $container = null; + private $_connection = null; + private $app = null; + private $data = null; + private $database = null; + private $server_id = null; + public $appLangFiles = []; + public $appThemes = []; + public $appName = ''; + public $appVersion = ''; + public $form = ''; + public $href = ''; + public $lang = []; + public $action = ''; + public $_name = 'BaseController'; + public $_title = 'base'; + private $table_controller = null; + private $trail_controller = null; + public $msg = ''; /* Constructor */ function __construct(\Slim\Container $container) { - + $this->container = $container; $this->lang = $container->get('lang'); $this->conf = $container->get('conf'); $this->view = $container->get('view'); $this->plugin_manager = $container->get('plugin_manager'); - $this->appName = $container->get('settings')['appName']; - $this->appVersion = $container->get('settings')['appVersion']; + $this->msg = $container->get('msg'); $this->appLangFiles = $container->get('appLangFiles'); $this->misc = $container->get('misc'); $this->appThemes = $container->get('appThemes'); + $this->action = $container->get('action'); + + $msg = $container->get('msg'); + if ($this->misc->getNoDBConnection() === false) { + if ($this->misc->getServerId() === null) { + echo $lang['strnoserversupplied']; + exit; + } + $_server_info = $this->misc->getServerInfo(); + // Redirect to the login form if not logged in + if (!isset($_server_info['username'])) { + + $login_controller = new \PHPPgAdmin\Controller\LoginController($container); + echo $login_controller->doLoginForm($msg); + + exit; + } + } + + //\PC::debug(['name' => $this->_name, 'no_db_connection' => $this->misc->getNoDBConnection()], 'instanced controller'); + } + + public function getContainer() { + return $this->container; + } + + private function getTableController() { + if ($this->table_controller === null) { + $this->table_controller = new \PHPPgAdmin\XHtml\HTMLTableController($this->getContainer()); + } + return $this->table_controller; + } + + private function getNavbarController() { + if ($this->trail_controller === null) { + $this->trail_controller = new \PHPPgAdmin\XHtml\HTMLNavbarController($this->getContainer()); + } + + return $this->trail_controller; + } + /** + * Instances an HTMLTable and returns its html content + * @param [type] &$tabledata [description] + * @param [type] &$columns [description] + * @param [type] &$actions [description] + * @param [type] $place [description] + * @param [type] $nodata [description] + * @param [type] $pre_fn [description] + * @return [type] [description] + */ + function printTable(&$tabledata, &$columns, &$actions, $place, $nodata = null, $pre_fn = null) { + $html_table = $this->getTableController(); + return $html_table->printTable($tabledata, $columns, $actions, $place, $nodata, $pre_fn); + } + + function printTrail($trail = [], $do_print = true) { + $html_trail = $this->getNavbarController(); + return $html_trail->printTrail($trail, $do_print); + } + + function printNavLinks($navlinks, $place, $env = [], $do_print = true) { + $html_trail = $this->getNavbarController(); + return $html_trail->printNavLinks($navlinks, $place, $env, $do_print); + } + + function printTabs($tabs, $activetab, $do_print = true) { + $html_trail = $this->getNavbarController(); + return $html_trail->printTabs($tabs, $activetab, $do_print); + } + function getLastTabURL($section) { + $html_trail = $this->getNavbarController(); + return $html_trail->getLastTabURL($section); + } + + function printLink($link, $do_print = true) { + $html_trail = $this->getNavbarController(); + return $html_trail->printLink($link, $do_print); + } + + public function render() { + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + + $misc->printHeader($lang[$this->_title]); + $misc->printBody(); + + switch ($action) { + default: + $this->doDefault(); + break; + } - \PC::debug($this->_name, 'instanced controller'); + $misc->printFooter(); } public function doDefault() { - return $this; + $html = '<div><h2>Section title</h2> <p>Main content</p></div>'; + echo $html; + return $html; } }
\ No newline at end of file diff --git a/src/controllers/BrowserController.php b/src/controllers/BrowserController.php new file mode 100644 index 00000000..8b6ec841 --- /dev/null +++ b/src/controllers/BrowserController.php @@ -0,0 +1,46 @@ +<?php + +namespace PHPPgAdmin\Controller; + +/** + * Base controller class + */ +class BrowserController extends BaseController { + public $_name = 'BrowserController'; + + /* Constructor */ + function __construct(\Slim\Container $container) { + $this->misc = $container->get('misc'); + + $this->misc->setNoDBConnection(true); + $this->misc->setNoBottomLink(true); + + parent::__construct($container); + } + public function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $viewVars = $this->lang; + $viewVars['appName'] = $this->misc->appName; + $viewVars['icon'] = [ + 'blank' => $this->misc->icon('blank'), + 'I' => $this->misc->icon('I'), + 'L' => $this->misc->icon('L'), + 'Lminus' => $this->misc->icon('Lminus'), + 'Loading' => $this->misc->icon('Loading'), + 'Lplus' => $this->misc->icon('Lplus'), + 'ObjectNotFound' => $this->misc->icon('ObjectNotFound'), + 'Refresh' => $this->misc->icon('Refresh'), + 'Servers' => $this->misc->icon('Servers'), + 'T' => $this->misc->icon('T'), + 'Tminus' => $this->misc->icon('Tminus'), + 'Tplus' => $this->misc->icon('Tplus'), + + ]; + + echo $this->view->fetch('browser.twig', $viewVars); + + } +} diff --git a/src/controllers/CastController.php b/src/controllers/CastController.php index bed5d1ff..e54453f0 100644 --- a/src/controllers/CastController.php +++ b/src/controllers/CastController.php @@ -18,17 +18,17 @@ class CastController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - function renderCastContext($val) { - global $lang; + $renderCastContext = function ($val) use ($lang) { + switch ($val) { case 'e':return $lang['strno']; case 'a':return $lang['strinassignment']; default:return $lang['stryes']; } - } + }; - $misc->printTrail('database'); - $misc->printTabs('database', 'casts'); + $this->printTrail('database'); + $this->printTabs('database', 'casts'); $misc->printMsg($msg); $casts = $data->getCasts(); @@ -51,7 +51,7 @@ class CastController extends BaseController { 'title' => $lang['strimplicit'], 'field' => Decorator::field('castcontext'), 'type' => 'callback', - 'params' => ['function' => 'renderCastContext', 'align' => 'center'], + 'params' => ['function' => $renderCastContext, 'align' => 'center'], ], 'comment' => [ 'title' => $lang['strcomment'], @@ -61,6 +61,54 @@ class CastController extends BaseController { $actions = []; - echo $misc->printTable($casts, $columns, $actions, 'casts-casts', $lang['strnocasts']); + echo $this->printTable($casts, $columns, $actions, 'casts-casts', $lang['strnocasts']); } + + public function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + $data = $misc->getDatabaseAccessor(); + + $misc->printHeader($lang['strcasts']); + $misc->printBody(); + + switch ($action) { + + default: + $cast_controller->doDefault(); + break; + } + + return $misc->printFooter(); + + } + +/** + * Generate XML for the browser tree. + */ + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $casts = $data->getCasts(); + + $proto = Decorator::concat(Decorator::field('castsource'), ' AS ', Decorator::field('casttarget')); + + $attrs = [ + 'text' => $proto, + 'icon' => 'Cast', + ]; + + return $misc->printTree($casts, $attrs, 'casts'); + + } + } diff --git a/src/controllers/ColPropertyController.php b/src/controllers/ColPropertyController.php index c83c2a53..fe18f8e8 100644 --- a/src/controllers/ColPropertyController.php +++ b/src/controllers/ColPropertyController.php @@ -7,8 +7,48 @@ use \PHPPgAdmin\Decorators\Decorator; * Base controller class */ class ColPropertyController extends BaseController { - public $_name = 'ColPropertyController'; + public $_name = 'ColPropertyController'; + public $tableName = ''; + public $table_place = 'colproperties-colproperties'; + + public function render() { + if (isset($_REQUEST['table'])) { + $this->tableName = &$_REQUEST['table']; + } elseif (isset($_REQUEST['view'])) { + $this->tableName = &$_REQUEST['view']; + } else { + die($lang['strnotableprovided']); + } + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + $data = $misc->getDatabaseAccessor(); + + $misc->printHeader($lang['strtables'] . ' - ' . $this->tableName); + $misc->printBody(); + + if (isset($_REQUEST['view'])) { + $this->doDefault(null, false); + } else { + switch ($action) { + case 'properties': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doAlter(); + } + + break; + default: + $this->doDefault(); + break; + } + } + + $misc->printFooter(); + } /** * Displays a screen where they can alter a column */ @@ -24,7 +64,7 @@ class ColPropertyController extends BaseController { switch ($_REQUEST['stage']) { case 1: - $misc->printTrail('column'); + $this->printTrail('column'); $misc->printTitle($lang['stralter'], 'pg.column.alter'); $misc->printMsg($msg); @@ -162,7 +202,7 @@ class ColPropertyController extends BaseController { if ($status == 0) { if ($_REQUEST['column'] != $_REQUEST['field']) { $_REQUEST['column'] = $_REQUEST['field']; - $_reload_browser = true; + $misc->setReloadBrowser(true); } $this->doDefault($lang['strcolumnaltered']); } else { @@ -185,8 +225,6 @@ class ColPropertyController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - global $tableName; - $attPre = function (&$rowdata) use ($data) { $rowdata->fields['+type'] = $data->formatType($rowdata->fields['type'], $rowdata->fields['atttypmod']); @@ -196,16 +234,16 @@ class ColPropertyController extends BaseController { $msg .= "<br/>{$lang['strnoobjects']}"; } - $misc->printTrail('column'); + $this->printTrail('column'); //$misc->printTitle($lang['strcolprop']); - $misc->printTabs('column', 'properties'); + $this->printTabs('column', 'properties'); $misc->printMsg($msg); if (!empty($_REQUEST['column'])) { // Get table - $tdata = $data->getTable($tableName); + $tdata = $data->getTable($this->tableName); // Get columns - $attrs = $data->getTableAttributes($tableName, $_REQUEST['column']); + $attrs = $data->getTableAttributes($this->tableName, $_REQUEST['column']); // Show comment if any if ($attrs->fields['comment'] !== null) { @@ -237,12 +275,12 @@ class ColPropertyController extends BaseController { } $actions = []; - echo $misc->printTable($attrs, $column, $actions, 'colproperties-colproperties', null, $attPre); + echo $this->printTable($attrs, $column, $actions, $this->table_place, null, $attPre); echo "<br />\n"; $f_attname = $_REQUEST['column']; - $f_table = $tableName; + $f_table = $this->tableName; $f_schema = $data->_schema; $data->fieldClean($f_attname); $data->fieldClean($f_table); @@ -265,7 +303,7 @@ class ColPropertyController extends BaseController { 'server' => $_REQUEST['server'], 'database' => $_REQUEST['database'], 'schema' => $_REQUEST['schema'], - 'table' => $tableName, + 'table' => $this->tableName, 'column' => $_REQUEST['column'], 'return' => 'column', 'query' => $query, @@ -283,7 +321,7 @@ class ColPropertyController extends BaseController { 'server' => $_REQUEST['server'], 'database' => $_REQUEST['database'], 'schema' => $_REQUEST['schema'], - 'table' => $tableName, + 'table' => $this->tableName, 'column' => $_REQUEST['column'], ], ], @@ -299,7 +337,7 @@ class ColPropertyController extends BaseController { 'server' => $_REQUEST['server'], 'database' => $_REQUEST['database'], 'schema' => $_REQUEST['schema'], - 'table' => $tableName, + 'table' => $this->tableName, 'column' => $_REQUEST['column'], ], ], @@ -319,7 +357,7 @@ class ColPropertyController extends BaseController { 'server' => $_REQUEST['server'], 'database' => $_REQUEST['database'], 'schema' => $_REQUEST['schema'], - 'view' => $tableName, + 'view' => $this->tableName, 'column' => $_REQUEST['column'], 'return' => 'column', 'query' => $query, @@ -331,7 +369,7 @@ class ColPropertyController extends BaseController { ]; } - $misc->printNavLinks($navlinks, 'colproperties-colproperties', get_defined_vars()); + $this->printNavLinks($navlinks, $this->table_place, get_defined_vars()); } } diff --git a/src/controllers/ConstraintController.php b/src/controllers/ConstraintController.php index dd02702f..99addc7f 100644 --- a/src/controllers/ConstraintController.php +++ b/src/controllers/ConstraintController.php @@ -9,9 +9,100 @@ use \PHPPgAdmin\Decorators\Decorator; class ConstraintController extends BaseController { public $_name = 'ConstraintController'; -/** - * Confirm and then actually add a FOREIGN KEY constraint - */ + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + + $misc->printHeader($lang['strtables'] . ' - ' . $_REQUEST['table'] . ' - ' . $lang['strconstraints'], + "<script src=\"/js/indexes.js\" type=\"text/javascript\"></script>"); + + if ($action == 'add_unique_key' || $action == 'save_add_unique_key' + || $action == 'add_primary_key' || $action == 'save_add_primary_key' + || $action == 'add_foreign_key' || $action == 'save_add_foreign_key') { + echo "<body onload=\"init();\">"; + } else { + $misc->printBody(); + } + + switch ($action) { + case 'add_foreign_key': + $this->addForeignKey(1); + break; + case 'save_add_foreign_key': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->addForeignKey($_REQUEST['stage']); + } + + break; + case 'add_unique_key': + $this->addPrimaryOrUniqueKey('unique', true); + break; + case 'save_add_unique_key': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->addPrimaryOrUniqueKey('unique', false); + } + + break; + case 'add_primary_key': + $this->addPrimaryOrUniqueKey('primary', true); + break; + case 'save_add_primary_key': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->addPrimaryOrUniqueKey('primary', false); + } + + break; + case 'add_check': + $this->addCheck(true); + break; + case 'save_add_check': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->addCheck(false); + } + + break; + case 'save_create': + $this->doSaveCreate(); + break; + case 'create': + $this->doCreate(); + break; + case 'drop': + if (isset($_POST['drop'])) { + $this->doDrop(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + default: + $this->doDefault(); + break; + } + + $misc->printFooter(); + + } + /** + * Confirm and then actually add a FOREIGN KEY constraint + */ function addForeignKey($stage, $msg = '') { $conf = $this->conf; $misc = $this->misc; @@ -60,7 +151,7 @@ class ConstraintController extends BaseController { $_REQUEST['target'] = unserialize($_REQUEST['target']); - $misc->printTrail('table'); + $this->printTrail('table'); $misc->printTitle($lang['straddfk'], 'pg.constraint.foreign_key'); $misc->printMsg($msg); @@ -182,7 +273,7 @@ class ConstraintController extends BaseController { } break; default: - $misc->printTrail('table'); + $this->printTrail('table'); $misc->printTitle($lang['straddfk'], 'pg.constraint.foreign_key'); $misc->printMsg($msg); @@ -270,7 +361,7 @@ class ConstraintController extends BaseController { $_POST['tablespace'] = ''; } - $misc->printTrail('table'); + $this->printTrail('table'); switch ($type) { case 'primary': @@ -410,7 +501,7 @@ class ConstraintController extends BaseController { } if ($confirm) { - $misc->printTrail('table'); + $this->printTrail('table'); $misc->printTitle($lang['straddcheck'], 'pg.constraint.check'); $misc->printMsg($msg); @@ -459,7 +550,7 @@ class ConstraintController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('constraint'); + $this->printTrail('constraint'); $misc->printTitle($lang['strdrop'], 'pg.constraint.drop'); echo "<p>", sprintf($lang['strconfdropconstraint'], $misc->printVal($_REQUEST['constraint']), @@ -505,8 +596,8 @@ class ConstraintController extends BaseController { } }; - $misc->printTrail('table'); - $misc->printTabs('table', 'constraints'); + $this->printTrail('table'); + $this->printTabs('table', 'constraints'); $misc->printMsg($msg); $constraints = $data->getConstraints($_REQUEST['table']); @@ -547,7 +638,7 @@ class ConstraintController extends BaseController { ], ]; - echo $misc->printTable($constraints, $columns, $actions, 'constraints-constraints', $lang['strnoconstraints'], $cnPre); + echo $this->printTable($constraints, $columns, $actions, 'constraints-constraints', $lang['strnoconstraints'], $cnPre); $navlinks = [ 'addcheck' => [ @@ -611,6 +702,6 @@ class ConstraintController extends BaseController { 'content' => $lang['straddfk'], ], ]; - $misc->printNavLinks($navlinks, 'constraints-constraints', get_defined_vars()); + $this->printNavLinks($navlinks, 'constraints-constraints', get_defined_vars()); } } diff --git a/src/controllers/ConversionController.php b/src/controllers/ConversionController.php index a33243a8..bfad2dcb 100644 --- a/src/controllers/ConversionController.php +++ b/src/controllers/ConversionController.php @@ -8,9 +8,67 @@ use \PHPPgAdmin\Decorators\Decorator; */ class ConversionController extends BaseController { public $_name = 'ConversionController'; -/** - * Show default list of conversions in the database - */ + + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + + $misc->printHeader($lang['strconversions']); + $misc->printBody(); + + switch ($action) { + default: + $this->doDefault(); + break; + } + + return $misc->printFooter(); + + } + + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $constraints = $data->getConstraints($_REQUEST['table']); + + $reqvars = $misc->getRequestVars('schema'); + + function getIcon($f) { + switch ($f['contype']) { + case 'u': + return 'UniqueConstraint'; + case 'c': + return 'CheckConstraint'; + case 'f': + return 'ForeignKey'; + case 'p': + return 'PrimaryKey'; + + } + } + + $attrs = [ + 'text' => Decorator::field('conname'), + 'icon' => Decorator::callback('getIcon'), + ]; + + return $misc->printTree($constraints, $attrs, 'constraints'); + + } + + /** + * Show default list of conversions in the database + */ public function doDefault($msg = '') { $conf = $this->conf; @@ -18,8 +76,8 @@ class ConversionController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('schema'); - $misc->printTabs('schema', 'conversions'); + $this->printTrail('schema'); + $this->printTabs('schema', 'conversions'); $misc->printMsg($msg); $conversions = $data->getconversions(); @@ -50,6 +108,6 @@ class ConversionController extends BaseController { $actions = []; - echo $misc->printTable($conversions, $columns, $actions, 'conversions-conversions', $lang['strnoconversions']); + echo $this->printTable($conversions, $columns, $actions, 'conversions-conversions', $lang['strnoconversions']); } } diff --git a/src/controllers/DBExportController.php b/src/controllers/DBExportController.php new file mode 100644 index 00000000..68d342af --- /dev/null +++ b/src/controllers/DBExportController.php @@ -0,0 +1,170 @@ +<?php + +namespace PHPPgAdmin\Controller; + +/** + * Base controller class + */ +class DBExportController extends BaseController { + public $_name = 'DBExportController'; + + /* Constructor */ + function __construct(\Slim\Container $container) { + parent::__construct($container); + + // Prevent timeouts on large exports (non-safe mode only) + if (!ini_get('safe_mode')) { + set_time_limit(0); + } + } + + public function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + $action = $this->action; + + // Include application functions + $f_schema = $f_object = ''; + $misc->setNoOutput(true); + + // Are we doing a cluster-wide dump or just a per-database dump + $dumpall = ($_REQUEST['subject'] == 'server'); + + // Check that database dumps are enabled. + if ($misc->isDumpEnabled($dumpall)) { + + $server_info = $misc->getServerInfo(); + + // Get the path of the pg_dump/pg_dumpall executable + $exe = $misc->escapeShellCmd($server_info[$dumpall ? 'pg_dumpall_path' : 'pg_dump_path']); + + // Obtain the pg_dump version number and check if the path is good + $version = []; + preg_match("/(\d+(?:\.\d+)?)(?:\.\d+)?.*$/", exec($exe . " --version"), $version); + + if (empty($version)) { + if ($dumpall) { + printf($lang['strbadpgdumpallpath'], $server_info['pg_dumpall_path']); + } else { + printf($lang['strbadpgdumppath'], $server_info['pg_dump_path']); + } + + return; + } + + // Make it do a download, if necessary + switch ($_REQUEST['output']) { + case 'show': + header('Content-Type: text/plain'); + break; + case 'download': + // Set headers. MSIE is totally broken for SSL downloading, so + // we need to have it download in-place as plain text + if (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE') && isset($_SERVER['HTTPS'])) { + header('Content-Type: text/plain'); + } else { + header('Content-Type: application/download'); + header('Content-Disposition: attachment; filename=dump.sql'); + } + break; + case 'gzipped': + // MSIE in SSL mode cannot do this - it should never get to this point + header('Content-Type: application/download'); + header('Content-Disposition: attachment; filename=dump.sql.gz'); + break; + } + + // Set environmental variables that pg_dump uses + putenv('PGPASSWORD=' . $server_info['password']); + putenv('PGUSER=' . $server_info['username']); + $hostname = $server_info['host']; + if ($hostname !== null && $hostname != '') { + putenv('PGHOST=' . $hostname); + } + $port = $server_info['port']; + if ($port !== null && $port != '') { + putenv('PGPORT=' . $port); + } + + // Build command for executing pg_dump. '-i' means ignore version differences. + $cmd = $exe . " -i"; + + // we are PG 7.4+, so we always have a schema + if (isset($_REQUEST['schema'])) { + $f_schema = $_REQUEST['schema']; + $data->fieldClean($f_schema); + } + + // Check for a specified table/view + switch ($_REQUEST['subject']) { + case 'schema': + // This currently works for 8.2+ (due to the orthoganl -t -n issue introduced then) + $cmd .= " -n " . $misc->escapeShellArg("\"{$f_schema}\""); + break; + case 'table': + case 'view': + $f_object = $_REQUEST[$_REQUEST['subject']]; + $data->fieldClean($f_object); + + // Starting in 8.2, -n and -t are orthagonal, so we now schema qualify + // the table name in the -t argument and quote both identifiers + if (((float) $version[1]) >= 8.2) { + $cmd .= " -t " . $misc->escapeShellArg("\"{$f_schema}\".\"{$f_object}\""); + } else { + // If we are 7.4 or higher, assume they are using 7.4 pg_dump and + // set dump schema as well. Also, mixed case dumping has been fixed + // then.. + $cmd .= " -t " . $misc->escapeShellArg($f_object) + . " -n " . $misc->escapeShellArg($f_schema); + } + } + + // Check for GZIP compression specified + if ($_REQUEST['output'] == 'gzipped' && !$dumpall) { + $cmd .= " -Z 9"; + } + + switch ($_REQUEST['what']) { + case 'dataonly': + $cmd .= ' -a'; + if ($_REQUEST['d_format'] == 'sql') { + $cmd .= ' --inserts'; + } elseif (isset($_REQUEST['d_oids'])) { + $cmd .= ' -o'; + } + + break; + case 'structureonly': + $cmd .= ' -s'; + if (isset($_REQUEST['s_clean'])) { + $cmd .= ' -c'; + } + + break; + case 'structureanddata': + if ($_REQUEST['sd_format'] == 'sql') { + $cmd .= ' --inserts'; + } elseif (isset($_REQUEST['sd_oids'])) { + $cmd .= ' -o'; + } + + if (isset($_REQUEST['sd_clean'])) { + $cmd .= ' -c'; + } + + break; + } + + if (!$dumpall) { + putenv('PGDATABASE=' . $_REQUEST['database']); + } + + // Execute command and return the output to the screen + passthru($cmd); + } + + } + +} diff --git a/src/controllers/DataExportController.php b/src/controllers/DataExportController.php new file mode 100644 index 00000000..d414c517 --- /dev/null +++ b/src/controllers/DataExportController.php @@ -0,0 +1,399 @@ +<?php + +namespace PHPPgAdmin\Controller; + +/** + * Base controller class + */ +class DataExportController extends BaseController { + public $_name = 'DataExportController'; + public $extensions = [ + 'sql' => 'sql', + 'copy' => 'sql', + 'csv' => 'csv', + 'tab' => 'txt', + 'html' => 'html', + 'xml' => 'xml', + ]; + + /* Constructor */ + function __construct(\Slim\Container $container) { + parent::__construct($container); + + // Prevent timeouts on large exports (non-safe mode only) + if (!ini_get('safe_mode')) { + set_time_limit(0); + } + } + + public function render() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + $action = $this->action; + + // if (!isset($_REQUEST['table']) && !isset($_REQUEST['query'])) + // What must we do in this case? Maybe redirect to the homepage? + + // If format is set, then perform the export + if (isset($_REQUEST['what'])) { + + // Include application functions + $misc->setNoOutput(true); + + switch ($_REQUEST['what']) { + case 'dataonly': + // Check to see if they have pg_dump set up and if they do, use that + // instead of custom dump code + if ($misc->isDumpEnabled() + && ($_REQUEST['d_format'] == 'copy' || $_REQUEST['d_format'] == 'sql')) { + + $dbexport_controller = new \PHPPgAdmin\Controller\DBExportController($this->getContainer()); + return $dbexport_controller->render(); + + } else { + $format = $_REQUEST['d_format']; + $oids = isset($_REQUEST['d_oids']); + } + break; + case 'structureonly': + // Check to see if they have pg_dump set up and if they do, use that + // instead of custom dump code + if ($misc->isDumpEnabled()) { + $dbexport_controller = new \PHPPgAdmin\Controller\DBExportController($this->getContainer()); + return $dbexport_controller->render(); + } else { + $clean = isset($_REQUEST['s_clean']); + } + + break; + case 'structureanddata': + // Check to see if they have pg_dump set up and if they do, use that + // instead of custom dump code + if ($misc->isDumpEnabled()) { + $dbexport_controller = new \PHPPgAdmin\Controller\DBExportController($this->getContainer()); + return $dbexport_controller->render(); + } else { + $format = $_REQUEST['sd_format']; + $clean = isset($_REQUEST['sd_clean']); + $oids = isset($_REQUEST['sd_oids']); + } + break; + } + + // Make it do a download, if necessary + if ($_REQUEST['output'] == 'download') { + // Set headers. MSIE is totally broken for SSL downloading, so + // we need to have it download in-place as plain text + if (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE') && isset($_SERVER['HTTPS'])) { + header('Content-Type: text/plain'); + } else { + header('Content-Type: application/download'); + + if (isset($extensions[$format])) { + $ext = $extensions[$format]; + } else { + $ext = 'txt'; + } + + header('Content-Disposition: attachment; filename=dump.' . $ext); + } + } else { + header('Content-Type: text/plain'); + } + + if (isset($_REQUEST['query'])) { + $_REQUEST['query'] = trim(urldecode($_REQUEST['query'])); + } + + // Set the schema search path + if (isset($_REQUEST['search_path'])) { + $data->setSearchPath(array_map('trim', explode(',', $_REQUEST['search_path']))); + } + + // Set up the dump transaction + $status = $data->beginDump(); + + // If the dump is not dataonly then dump the structure prefix + if ($_REQUEST['what'] != 'dataonly') { + echo $data->getTableDefPrefix($_REQUEST['table'], $clean); + } + + // If the dump is not structureonly then dump the actual data + if ($_REQUEST['what'] != 'structureonly') { + // Get database encoding + $dbEncoding = $data->getDatabaseEncoding(); + + // Set fetch mode to NUM so that duplicate field names are properly returned + $data->conn->setFetchMode(ADODB_FETCH_NUM); + + // Execute the query, if set, otherwise grab all rows from the table + if (isset($_REQUEST['table'])) { + $rs = $data->dumpRelation($_REQUEST['table'], $oids); + } else { + $rs = $data->conn->Execute($_REQUEST['query']); + } + + if ($format == 'copy') { + $data->fieldClean($_REQUEST['table']); + echo "COPY \"{$_REQUEST['table']}\""; + if ($oids) { + echo " WITH OIDS"; + } + + echo " FROM stdin;\n"; + while (!$rs->EOF) { + $first = true; + while (list($k, $v) = each($rs->fields)) { + // Escape value + $v = $data->escapeBytea($v); + + // We add an extra escaping slash onto octal encoded characters + $v = preg_replace('/\\\\([0-7]{3})/', '\\\\\1', $v); + if ($first) { + echo (is_null($v)) ? '\\N' : $v; + $first = false; + } else { + echo "\t", (is_null($v)) ? '\\N' : $v; + } + + } + echo "\n"; + $rs->moveNext(); + } + echo "\\.\n"; + } elseif ($format == 'html') { + echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n"; + echo "<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n"; + echo "<head>\r\n"; + echo "\t<title></title>\r\n"; + echo "\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\r\n"; + echo "</head>\r\n"; + echo "<body>\r\n"; + echo "<table class=\"phppgadmin\">\r\n"; + echo "\t<tr>\r\n"; + if (!$rs->EOF) { + // Output header row + $j = 0; + foreach ($rs->fields as $k => $v) { + $finfo = $rs->fetchField($j++); + if ($finfo->name == $data->id && !$oids) { + continue; + } + + echo "\t\t<th>", $misc->printVal($finfo->name, true), "</th>\r\n"; + } + } + echo "\t</tr>\r\n"; + while (!$rs->EOF) { + echo "\t<tr>\r\n"; + $j = 0; + foreach ($rs->fields as $k => $v) { + $finfo = $rs->fetchField($j++); + if ($finfo->name == $data->id && !$oids) { + continue; + } + + echo "\t\t<td>", $misc->printVal($v, true, $finfo->type), "</td>\r\n"; + } + echo "\t</tr>\r\n"; + $rs->moveNext(); + } + echo "</table>\r\n"; + echo "</body>\r\n"; + echo "</html>\r\n"; + } elseif ($format == 'xml') { + echo "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"; + echo "<data>\n"; + if (!$rs->EOF) { + // Output header row + $j = 0; + echo "\t<header>\n"; + foreach ($rs->fields as $k => $v) { + $finfo = $rs->fetchField($j++); + $name = htmlspecialchars($finfo->name); + $type = htmlspecialchars($finfo->type); + echo "\t\t<column name=\"{$name}\" type=\"{$type}\" />\n"; + } + echo "\t</header>\n"; + } + echo "\t<records>\n"; + while (!$rs->EOF) { + $j = 0; + echo "\t\t<row>\n"; + foreach ($rs->fields as $k => $v) { + $finfo = $rs->fetchField($j++); + $name = htmlspecialchars($finfo->name); + if (!is_null($v)) { + $v = htmlspecialchars($v); + } + + echo "\t\t\t<column name=\"{$name}\"", (is_null($v) ? ' null="null"' : ''), ">{$v}</column>\n"; + } + echo "\t\t</row>\n"; + $rs->moveNext(); + } + echo "\t</records>\n"; + echo "</data>\n"; + } elseif ($format == 'sql') { + $data->fieldClean($_REQUEST['table']); + while (!$rs->EOF) { + echo "INSERT INTO \"{$_REQUEST['table']}\" ("; + $first = true; + $j = 0; + foreach ($rs->fields as $k => $v) { + $finfo = $rs->fetchField($j++); + $k = $finfo->name; + // SQL (INSERT) format cannot handle oids + // if ($k == $data->id) continue; + // Output field + $data->fieldClean($k); + if ($first) { + echo "\"{$k}\""; + } else { + echo ", \"{$k}\""; + } + + if (!is_null($v)) { + // Output value + // addCSlashes converts all weird ASCII characters to octal representation, + // EXCEPT the 'special' ones like \r \n \t, etc. + $v = addCSlashes($v, "\0..\37\177..\377"); + // We add an extra escaping slash onto octal encoded characters + $v = preg_replace('/\\\\([0-7]{3})/', '\\\1', $v); + // Finally, escape all apostrophes + $v = str_replace("'", "''", $v); + } + if ($first) { + $values = (is_null($v) ? 'NULL' : "'{$v}'"); + $first = false; + } else { + $values .= ', ' . ((is_null($v) ? 'NULL' : "'{$v}'")); + } + + } + echo ") VALUES ({$values});\n"; + $rs->moveNext(); + } + } else { + switch ($format) { + case 'tab': + $sep = "\t"; + break; + case 'csv': + default: + $sep = ','; + break; + } + if (!$rs->EOF) { + // Output header row + $first = true; + foreach ($rs->fields as $k => $v) { + $finfo = $rs->fetchField($k); + $v = $finfo->name; + if (!is_null($v)) { + $v = str_replace('"', '""', $v); + } + + if ($first) { + echo "\"{$v}\""; + $first = false; + } else { + echo "{$sep}\"{$v}\""; + } + + } + echo "\r\n"; + } + while (!$rs->EOF) { + $first = true; + foreach ($rs->fields as $k => $v) { + if (!is_null($v)) { + $v = str_replace('"', '""', $v); + } + + if ($first) { + echo (is_null($v)) ? "\"\\N\"" : "\"{$v}\""; + $first = false; + } else { + echo is_null($v) ? "{$sep}\"\\N\"" : "{$sep}\"{$v}\""; + } + + } + echo "\r\n"; + $rs->moveNext(); + } + } + } + + // If the dump is not dataonly then dump the structure suffix + if ($_REQUEST['what'] != 'dataonly') { + // Set fetch mode back to ASSOC for the table suffix to work + $data->conn->setFetchMode(ADODB_FETCH_ASSOC); + echo $data->getTableDefSuffix($_REQUEST['table']); + } + + // Finish the dump transaction + $status = $data->endDump(); + } else { + return $this->doDefault(); + } + } + + public function doDefault($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + $action = $this->action; + + if (!isset($_REQUEST['query']) or empty($_REQUEST['query'])) { + $_REQUEST['query'] = $_SESSION['sqlquery']; + } + + $misc->printHeader($lang['strexport']); + $misc->printBody(); + $misc->printTrail(isset($_REQUEST['subject']) ? $_REQUEST['subject'] : 'database'); + $misc->printTitle($lang['strexport']); + if (isset($msg)) { + $misc->printMsg($msg); + } + + echo "<form action=\"/src/views/dataexport.php\" method=\"post\">\n"; + echo "<table>\n"; + echo "<tr><th class=\"data\">{$lang['strformat']}:</th><td><select name=\"d_format\">\n"; + // COPY and SQL require a table + if (isset($_REQUEST['table'])) { + echo "<option value=\"copy\">COPY</option>\n"; + echo "<option value=\"sql\">SQL</option>\n"; + } + echo "<option value=\"csv\">CSV</option>\n"; + echo "<option value=\"tab\">{$lang['strtabbed']}</option>\n"; + echo "<option value=\"html\">XHTML</option>\n"; + echo "<option value=\"xml\">XML</option>\n"; + echo "</select></td></tr>"; + echo "</table>\n"; + + echo "<h3>{$lang['stroptions']}</h3>\n"; + echo "<p><input type=\"radio\" id=\"output1\" name=\"output\" value=\"show\" checked=\"checked\" /><label for=\"output1\">{$lang['strshow']}</label>\n"; + echo "<br/><input type=\"radio\" id=\"output2\" name=\"output\" value=\"download\" /><label for=\"output2\">{$lang['strdownload']}</label></p>\n"; + + echo "<p><input type=\"hidden\" name=\"action\" value=\"export\" />\n"; + echo "<input type=\"hidden\" name=\"what\" value=\"dataonly\" />\n"; + if (isset($_REQUEST['table'])) { + echo "<input type=\"hidden\" name=\"table\" value=\"", htmlspecialchars($_REQUEST['table']), "\" />\n"; + } + echo "<input type=\"hidden\" name=\"query\" value=\"", htmlspecialchars(urlencode($_REQUEST['query'])), "\" />\n"; + if (isset($_REQUEST['search_path'])) { + echo "<input type=\"hidden\" name=\"search_path\" value=\"", htmlspecialchars($_REQUEST['search_path']), "\" />\n"; + } + echo $misc->form; + echo "<input type=\"submit\" value=\"{$lang['strexport']}\" /></p>\n"; + echo "</form>\n"; + + $misc->printFooter(); + + } +} diff --git a/src/controllers/DataImportController.php b/src/controllers/DataImportController.php new file mode 100644 index 00000000..35422112 --- /dev/null +++ b/src/controllers/DataImportController.php @@ -0,0 +1,310 @@ +<?php + +namespace PHPPgAdmin\Controller; + +/** + * Base controller class + */ +class DataImportController extends BaseController { + public $_name = 'DataImportController'; + + /* Constructor */ + function __construct(\Slim\Container $container) { + parent::__construct($container); + + // Prevent timeouts on large exports (non-safe mode only) + if (!ini_get('safe_mode')) { + set_time_limit(0); + } + } + + public function render() { + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + $data = $misc->getDatabaseAccessor(); + + $misc->printHeader($lang['strimport']); + $misc->printTrail('table'); + $misc->printTabs('table', 'import'); + + // Default state for XML parser + $state = 'XML'; + $curr_col_name = null; + $curr_col_val = null; + $curr_col_null = false; + $curr_row = []; + + /** + * Character data handler for XML import feature + */ + $_charHandler = function ($parser, $cdata) use (&$state, &$curr_col_val) { + if ($state == 'COLUMN') { + $curr_col_val .= $cdata; + } + }; + + /** + * Open tag handler for XML import feature + */ + $_startElement = function ($parser, $name, $attrs) use ($data, $misc, &$state, &$curr_col_name, &$curr_col_null) { + + switch ($name) { + case 'DATA': + if ($state != 'XML') { + $data->rollbackTransaction(); + $misc->printMsg($lang['strimporterror']); + exit; + } + $state = 'DATA'; + break; + case 'HEADER': + if ($state != 'DATA') { + $data->rollbackTransaction(); + $misc->printMsg($lang['strimporterror']); + exit; + } + $state = 'HEADER'; + break; + case 'RECORDS': + if ($state != 'READ_HEADER') { + $data->rollbackTransaction(); + $misc->printMsg($lang['strimporterror']); + exit; + } + $state = 'RECORDS'; + break; + case 'ROW': + if ($state != 'RECORDS') { + $data->rollbackTransaction(); + $misc->printMsg($lang['strimporterror']); + exit; + } + $state = 'ROW'; + $curr_row = []; + break; + case 'COLUMN': + // We handle columns in rows + if ($state == 'ROW') { + $state = 'COLUMN'; + $curr_col_name = $attrs['NAME']; + $curr_col_null = isset($attrs['NULL']); + } + // And we ignore columns in headers and fail in any other context + elseif ($state != 'HEADER') { + $data->rollbackTransaction(); + $misc->printMsg($lang['strimporterror']); + exit; + } + break; + default: + // An unrecognised tag means failure + $data->rollbackTransaction(); + $misc->printMsg($lang['strimporterror']); + exit; + } + }; + + /** + * Close tag handler for XML import feature + */ + $_endElement = function ($parser, $name) use ($data, $misc, &$state, &$curr_col_name, &$curr_col_null, &$curr_col_val) { + + switch ($name) { + case 'DATA': + $state = 'READ_DATA'; + break; + case 'HEADER': + $state = 'READ_HEADER'; + break; + case 'RECORDS': + $state = 'READ_RECORDS'; + break; + case 'ROW': + // Build value map in order to insert row into table + $fields = []; + $vars = []; + $nulls = []; + $format = []; + $types = []; + $i = 0; + foreach ($curr_row as $k => $v) { + $fields[$i] = $k; + // Check for nulls + if ($v === null) { + $nulls[$i] = 'on'; + } + + // Add to value array + $vars[$i] = $v; + // Format is always VALUE + $format[$i] = 'VALUE'; + // Type is always text + $types[$i] = 'text'; + $i++; + } + $status = $data->insertRow($_REQUEST['table'], $fields, $vars, $nulls, $format, $types); + if ($status != 0) { + $data->rollbackTransaction(); + $misc->printMsg($lang['strimporterror']); + exit; + } + $curr_row = []; + $state = 'RECORDS'; + break; + case 'COLUMN': + $curr_row[$curr_col_name] = ($curr_col_null ? null : $curr_col_val); + $curr_col_name = null; + $curr_col_val = null; + $curr_col_null = false; + $state = 'ROW'; + break; + default: + // An unrecognised tag means failure + $data->rollbackTransaction(); + $misc->printMsg($lang['strimporterror']); + exit; + } + }; + + // Check that file is specified and is an uploaded file + if (isset($_FILES['source']) && is_uploaded_file($_FILES['source']['tmp_name']) && is_readable($_FILES['source']['tmp_name'])) { + + $fd = fopen($_FILES['source']['tmp_name'], 'r'); + // Check that file was opened successfully + if ($fd !== false) { + $null_array = self::loadNULLArray(); + $status = $data->beginTransaction(); + if ($status != 0) { + $misc->printMsg($lang['strimporterror']); + exit; + } + + // If format is set to 'auto', then determine format automatically from file name + if ($_REQUEST['format'] == 'auto') { + $extension = substr(strrchr($_FILES['source']['name'], '.'), 1); + switch ($extension) { + case 'csv': + $_REQUEST['format'] = 'csv'; + break; + case 'txt': + $_REQUEST['format'] = 'tab'; + break; + case 'xml': + $_REQUEST['format'] = 'xml'; + break; + default: + $data->rollbackTransaction(); + $misc->printMsg($lang['strimporterror-fileformat']); + exit; + } + } + + // Do different import technique depending on file format + switch ($_REQUEST['format']) { + case 'csv': + case 'tab': + // XXX: Length of CSV lines limited to 100k + $csv_max_line = 100000; + // Set delimiter to tabs or commas + if ($_REQUEST['format'] == 'csv') { + $csv_delimiter = ','; + } else { + $csv_delimiter = "\t"; + } + + // Get first line of field names + $fields = fgetcsv($fd, $csv_max_line, $csv_delimiter); + $row = 2; //We start on the line AFTER the field names + while ($line = fgetcsv($fd, $csv_max_line, $csv_delimiter)) { + // Build value map + $t_fields = []; + $vars = []; + $nulls = []; + $format = []; + $types = []; + $i = 0; + foreach ($fields as $f) { + // Check that there is a column + if (!isset($line[$i])) { + $misc->printMsg(sprintf($lang['strimporterrorline-badcolumnnum'], $row)); + exit; + } + $t_fields[$i] = $f; + + // Check for nulls + if (self::determineNull($line[$i], $null_array)) { + $nulls[$i] = 'on'; + } + // Add to value array + $vars[$i] = $line[$i]; + // Format is always VALUE + $format[$i] = 'VALUE'; + // Type is always text + $types[$i] = 'text'; + $i++; + } + + $status = $data->insertRow($_REQUEST['table'], $t_fields, $vars, $nulls, $format, $types); + if ($status != 0) { + $data->rollbackTransaction(); + $misc->printMsg(sprintf($lang['strimporterrorline'], $row)); + exit; + } + $row++; + } + break; + case 'xml': + $parser = xml_parser_create(); + xml_set_element_handler($parser, $_startElement, $_endElement); + xml_set_character_data_handler($parser, $_charHandler); + + while (!feof($fd)) { + $line = fgets($fd, 4096); + xml_parse($parser, $line); + } + + xml_parser_free($parser); + break; + default: + // Unknown type + $data->rollbackTransaction(); + $misc->printMsg($lang['strinvalidparam']); + exit; + } + + $status = $data->endTransaction(); + if ($status != 0) { + $misc->printMsg($lang['strimporterror']); + exit; + } + fclose($fd); + + $misc->printMsg($lang['strfileimported']); + } else { + // File could not be opened + $misc->printMsg($lang['strimporterror']); + } + } else { + // Upload went wrong + $misc->printMsg($lang['strimporterror-uploadedfile']); + } + + $misc->printFooter(); + } + + public static function loadNULLArray() { + $array = []; + if (isset($_POST['allowednulls'])) { + foreach ($_POST['allowednulls'] as $null_char) { + $array[] = $null_char; + } + + } + return $array; + } + + public static function determineNull($field, $null_array) { + return in_array($field, $null_array); + } +} diff --git a/src/controllers/DatabaseController.php b/src/controllers/DatabaseController.php index b3f47e40..9500153b 100644 --- a/src/controllers/DatabaseController.php +++ b/src/controllers/DatabaseController.php @@ -8,8 +8,9 @@ use \PHPPgAdmin\Decorators\Decorator; */ class DatabaseController extends BaseController { use AdminTrait; - public $script = 'database.php'; - public $_name = 'DatabaseController'; + public $script = 'database.php'; + public $_name = 'DatabaseController'; + public $table_place = 'database-variables'; function _highlight($string, $term) { return str_replace($term, "<b>{$term}</b>", $string); @@ -50,8 +51,8 @@ class DatabaseController extends BaseController { $_REQUEST['filter'] = ''; } - $misc->printTrail('database'); - $misc->printTabs('database', 'find'); + $this->printTrail('database'); + $this->printTabs('database', 'find'); $misc->printMsg($msg); echo "<form action=\"/src/views/database.php\" method=\"post\">\n"; @@ -319,8 +320,8 @@ class DatabaseController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('database'); - $misc->printTabs('database', 'export'); + $this->printTrail('database'); + $this->printTabs('database', 'export'); $misc->printMsg($msg); echo "<form action=\"/src/views/dbexport.php\" method=\"post\">\n"; @@ -376,8 +377,8 @@ class DatabaseController extends BaseController { // Fetch the variables from the database $variables = $data->getVariables(); - $misc->printTrail('database'); - $misc->printTabs('database', 'variables'); + $this->printTrail('database'); + $this->printTabs('database', 'variables'); $columns = [ 'variable' => [ @@ -392,7 +393,7 @@ class DatabaseController extends BaseController { $actions = []; - echo $misc->printTable($variables, $columns, $actions, 'database-variables', $lang['strnodata']); + echo $this->printTable($variables, $columns, $actions, $this->table_place, $lang['strnodata']); } /** @@ -405,8 +406,8 @@ class DatabaseController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('database'); - $misc->printTabs('database', 'processes'); + $this->printTrail('database'); + $this->printTabs('database', 'processes'); $misc->printMsg($msg); if (strlen($msg) === 0) { @@ -450,7 +451,7 @@ class DatabaseController extends BaseController { $actions = []; - echo $misc->printTable($prep_xacts, $columns, $actions, 'database-processes-preparedxacts', $lang['strnodata']); + echo $this->printTable($prep_xacts, $columns, $actions, 'database-processes-preparedxacts', $lang['strnodata']); } // Fetch the processes from the database @@ -529,7 +530,7 @@ class DatabaseController extends BaseController { unset($columns['actions']); } - echo $misc->printTable($processes, $columns, $actions, 'database-processes', $lang['strnodata']); + echo $this->printTable($processes, $columns, $actions, 'database-processes', $lang['strnodata']); if ($isAjax) { exit; @@ -583,7 +584,7 @@ class DatabaseController extends BaseController { } $actions = []; - echo $misc->printTable($variables, $columns, $actions, 'database-locks', $lang['strnodata']); + echo $this->printTable($variables, $columns, $actions, 'database-locks', $lang['strnodata']); if ($isAjax) { exit; @@ -600,8 +601,8 @@ class DatabaseController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('database'); - $misc->printTabs('database', 'locks'); + $this->printTrail('database'); + $this->printTabs('database', 'locks'); echo "<br /><a id=\"control\" href=\"\"><img src=\"" . $misc->icon('Refresh') . "\" alt=\"{$lang['strrefresh']}\" title=\"{$lang['strrefresh']}\"/> {$lang['strrefresh']}</a>"; @@ -624,8 +625,8 @@ class DatabaseController extends BaseController { $_REQUEST['paginate'] = 'on'; } - $misc->printTrail('database'); - $misc->printTabs('database', 'sql'); + $this->printTrail('database'); + $this->printTabs('database', 'sql'); echo "<p>{$lang['strentersql']}</p>\n"; echo "<form action=\"/src/views/sql.php\" method=\"post\" enctype=\"multipart/form-data\">\n"; echo "<p>{$lang['strsql']}<br />\n"; @@ -651,4 +652,110 @@ class DatabaseController extends BaseController { // Default focus $misc->setFocus('forms[0].query'); } + + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $data = $misc->getDatabaseAccessor(); + + $reqvars = $misc->getRequestVars('database'); + + $tabs = $misc->getNavTabs('database'); + + $items = $misc->adjustTabsForTree($tabs); + //\PC::debug($reqvars, 'reqvars'); + $attrs = [ + 'text' => Decorator::field('title'), + 'icon' => Decorator::field('icon'), + 'action' => Decorator::actionurl(Decorator::field('url'), $reqvars, Decorator::field('urlvars', [])), + 'branch' => Decorator::url(Decorator::field('url'), $reqvars, Decorator::field('urlvars'), ['action' => 'tree']), + ]; + + return $misc->printTree($items, $attrs, 'database'); + + } + + public function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + $data = $misc->getDatabaseAccessor(); + + if ($action == 'tree') { + return $this->doTree(); + } + + if ($action == 'refresh_locks') { + return $this->currentLocks(true); + } + + if ($action == 'refresh_processes') { + return $this->currentProcesses(true); + } + $scripts = ''; + /* normal flow */ + if ($action == 'locks' || $action == 'processes') { + $scripts .= "<script src=\"/js/database.js\" type=\"text/javascript\"></script>"; + + $refreshTime = $conf['ajax_refresh'] * 1000; + + $scripts .= "<script type=\"text/javascript\">\n"; + $scripts .= "var Database = {\n"; + $scripts .= "ajax_time_refresh: {$refreshTime},\n"; + $scripts .= "str_start: {text:'{$lang['strstart']}',icon: '" . $misc->icon('Execute') . "'},\n"; + $scripts .= "str_stop: {text:'{$lang['strstop']}',icon: '" . $misc->icon('Stop') . "'},\n"; + $scripts .= "load_icon: '" . $misc->icon('Loading') . "',\n"; + $scripts .= "server:'{$_REQUEST['server']}',\n"; + $scripts .= "dbname:'{$_REQUEST['database']}',\n"; + $scripts .= "action:'refresh_{$action}',\n"; + $scripts .= "errmsg: '" . str_replace("'", "\'", $lang['strconnectionfail']) . "'\n"; + $scripts .= "};\n"; + $scripts .= "</script>\n"; + } + + $misc->printHeader($lang['strdatabase'], $scripts); + $misc->printBody(); + + switch ($action) { + case 'find': + if (isset($_REQUEST['term'])) { + $this->doFind(false); + } else { + $this->doFind(true); + } + + break; + case 'sql': + $this->doSQL(); + break; + case 'variables': + $this->doVariables(); + break; + case 'processes': + $this->doProcesses(); + break; + case 'locks': + $this->doLocks(); + break; + case 'export': + $this->doExport(); + break; + case 'signal': + $this->doSignal(); + break; + default: + if ($this->adminActions($action, 'database') === false) { + $this->doSQL(); + } + + break; + } + + $misc->printFooter(); + + } } diff --git a/src/controllers/DisplayController.php b/src/controllers/DisplayController.php new file mode 100644 index 00000000..11d3292e --- /dev/null +++ b/src/controllers/DisplayController.php @@ -0,0 +1,1007 @@ +<?php + +namespace PHPPgAdmin\Controller; + +/** + * Base controller class + */ +class DisplayController extends BaseController { + public $_name = 'DisplayController'; + + /* Constructor */ + function __construct(\Slim\Container $container) { + parent::__construct($container); + + // Prevent timeouts on large exports (non-safe mode only) + if (!ini_get('safe_mode')) { + set_time_limit(0); + } + } + + /** + * Show confirmation of edit and perform actual update + */ + function doEditRow($confirm, $msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + if (is_array($_REQUEST['key'])) { + $key = $_REQUEST['key']; + } else { + $key = unserialize(urldecode($_REQUEST['key'])); + } + + if ($confirm) { + $this->printTrail($_REQUEST['subject']); + $misc->printTitle($lang['streditrow']); + $misc->printMsg($msg); + + $attrs = $data->getTableAttributes($_REQUEST['table']); + $rs = $data->browseRow($_REQUEST['table'], $key); + + if (($conf['autocomplete'] != 'disable')) { + $fksprops = $misc->getAutocompleteFKProperties($_REQUEST['table']); + if ($fksprops !== false) { + echo $fksprops['code']; + } + + } else { + $fksprops = false; + } + + echo "<form action=\"/src/views/display.php\" method=\"post\" id=\"ac_form\">\n"; + $elements = 0; + $error = true; + if ($rs->recordCount() == 1 && $attrs->recordCount() > 0) { + echo "<table>\n"; + + // Output table header + echo "<tr><th class=\"data\">{$lang['strcolumn']}</th><th class=\"data\">{$lang['strtype']}</th>"; + echo "<th class=\"data\">{$lang['strformat']}</th>\n"; + echo "<th class=\"data\">{$lang['strnull']}</th><th class=\"data\">{$lang['strvalue']}</th></tr>"; + + $i = 0; + while (!$attrs->EOF) { + + $attrs->fields['attnotnull'] = $data->phpBool($attrs->fields['attnotnull']); + $id = (($i % 2) == 0 ? '1' : '2'); + + // Initialise variables + if (!isset($_REQUEST['format'][$attrs->fields['attname']])) { + $_REQUEST['format'][$attrs->fields['attname']] = 'VALUE'; + } + + echo "<tr class=\"data{$id}\">\n"; + echo "<td style=\"white-space:nowrap;\">", $misc->printVal($attrs->fields['attname']), "</td>"; + echo "<td style=\"white-space:nowrap;\">\n"; + echo $misc->printVal($data->formatType($attrs->fields['type'], $attrs->fields['atttypmod'])); + echo "<input type=\"hidden\" name=\"types[", htmlspecialchars($attrs->fields['attname']), "]\" value=\"", + htmlspecialchars($attrs->fields['type']), "\" /></td>"; + $elements++; + echo "<td style=\"white-space:nowrap;\">\n"; + echo "<select name=\"format[", htmlspecialchars($attrs->fields['attname']), "]\">\n"; + echo "<option value=\"VALUE\"", ($_REQUEST['format'][$attrs->fields['attname']] == 'VALUE') ? ' selected="selected"' : '', ">{$lang['strvalue']}</option>\n"; + echo "<option value=\"EXPRESSION\"", ($_REQUEST['format'][$attrs->fields['attname']] == 'EXPRESSION') ? ' selected="selected"' : '', ">{$lang['strexpression']}</option>\n"; + echo "</select>\n</td>\n"; + $elements++; + echo "<td style=\"white-space:nowrap;\">"; + // Output null box if the column allows nulls (doesn't look at CHECKs or ASSERTIONS) + if (!$attrs->fields['attnotnull']) { + // Set initial null values + if ($_REQUEST['action'] == 'confeditrow' && $rs->fields[$attrs->fields['attname']] === null) { + $_REQUEST['nulls'][$attrs->fields['attname']] = 'on'; + } + echo "<label><span><input type=\"checkbox\" name=\"nulls[{$attrs->fields['attname']}]\"", + isset($_REQUEST['nulls'][$attrs->fields['attname']]) ? ' checked="checked"' : '', " /></span></label></td>\n"; + $elements++; + } else { + echo " </td>"; + } + + echo "<td id=\"row_att_{$attrs->fields['attnum']}\" style=\"white-space:nowrap;\">"; + + $extras = []; + + // If the column allows nulls, then we put a JavaScript action on the data field to unset the + // NULL checkbox as soon as anything is entered in the field. We use the $elements variable to + // keep track of which element offset we're up to. We can't refer to the null checkbox by name + // as it contains '[' and ']' characters. + if (!$attrs->fields['attnotnull']) { + $extras['onChange'] = 'elements[' . ($elements - 1) . '].checked = false;'; + } + + if (($fksprops !== false) && isset($fksprops['byfield'][$attrs->fields['attnum']])) { + $extras['id'] = "attr_{$attrs->fields['attnum']}"; + $extras['autocomplete'] = 'off'; + } + + echo $data->printField("values[{$attrs->fields['attname']}]", $rs->fields[$attrs->fields['attname']], $attrs->fields['type'], $extras); + + echo "</td>"; + $elements++; + echo "</tr>\n"; + $i++; + $attrs->moveNext(); + } + echo "</table>\n"; + + $error = false; + } elseif ($rs->recordCount() != 1) { + echo "<p>{$lang['strrownotunique']}</p>\n"; + } else { + echo "<p>{$lang['strinvalidparam']}</p>\n"; + } + + echo "<input type=\"hidden\" name=\"action\" value=\"editrow\" />\n"; + echo $misc->form; + if (isset($_REQUEST['table'])) { + echo "<input type=\"hidden\" name=\"table\" value=\"", htmlspecialchars($_REQUEST['table']), "\" />\n"; + } + + if (isset($_REQUEST['subject'])) { + echo "<input type=\"hidden\" name=\"subject\" value=\"", htmlspecialchars($_REQUEST['subject']), "\" />\n"; + } + + if (isset($_REQUEST['query'])) { + echo "<input type=\"hidden\" name=\"query\" value=\"", htmlspecialchars($_REQUEST['query']), "\" />\n"; + } + + if (isset($_REQUEST['count'])) { + echo "<input type=\"hidden\" name=\"count\" value=\"", htmlspecialchars($_REQUEST['count']), "\" />\n"; + } + + if (isset($_REQUEST['return'])) { + echo "<input type=\"hidden\" name=\"return\" value=\"", htmlspecialchars($_REQUEST['return']), "\" />\n"; + } + + echo "<input type=\"hidden\" name=\"page\" value=\"", htmlspecialchars($_REQUEST['page']), "\" />\n"; + echo "<input type=\"hidden\" name=\"sortkey\" value=\"", htmlspecialchars($_REQUEST['sortkey']), "\" />\n"; + echo "<input type=\"hidden\" name=\"sortdir\" value=\"", htmlspecialchars($_REQUEST['sortdir']), "\" />\n"; + echo "<input type=\"hidden\" name=\"strings\" value=\"", htmlspecialchars($_REQUEST['strings']), "\" />\n"; + echo "<input type=\"hidden\" name=\"key\" value=\"", htmlspecialchars(urlencode(serialize($key))), "\" />\n"; + echo "<p>"; + if (!$error) { + echo "<input type=\"submit\" name=\"save\" accesskey=\"r\" value=\"{$lang['strsave']}\" />\n"; + } + + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n"; + + if ($fksprops !== false) { + if ($conf['autocomplete'] != 'default off') { + echo "<input type=\"checkbox\" id=\"no_ac\" value=\"1\" checked=\"checked\" /><label for=\"no_ac\">{$lang['strac']}</label>\n"; + } else { + echo "<input type=\"checkbox\" id=\"no_ac\" value=\"0\" /><label for=\"no_ac\">{$lang['strac']}</label>\n"; + } + + } + + echo "</p>\n"; + echo "</form>\n"; + } else { + if (!isset($_POST['values'])) { + $_POST['values'] = []; + } + + if (!isset($_POST['nulls'])) { + $_POST['nulls'] = []; + } + + $status = $data->editRow($_POST['table'], $_POST['values'], $_POST['nulls'], + $_POST['format'], $_POST['types'], $key); + if ($status == 0) { + $this->doBrowse($lang['strrowupdated']); + } elseif ($status == -2) { + $this->doEditRow(true, $lang['strrownotunique']); + } else { + $this->doEditRow(true, $lang['strrowupdatedbad']); + } + + } + + } + + /** + * Show confirmation of drop and perform actual drop + */ + function doDelRow($confirm) { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + if ($confirm) { + $this->printTrail($_REQUEST['subject']); + $misc->printTitle($lang['strdeleterow']); + + $rs = $data->browseRow($_REQUEST['table'], $_REQUEST['key']); + + echo "<form action=\"/src/views/display.php\" method=\"post\">\n"; + echo $misc->form; + + if ($rs->recordCount() == 1) { + echo "<p>{$lang['strconfdeleterow']}</p>\n"; + + $fkinfo = []; + echo "<table><tr>"; + $this->printTableHeaderCells($rs, false, true); + echo "</tr>"; + echo "<tr class=\"data1\">\n"; + $this->printTableRowCells($rs, $fkinfo, true); + echo "</tr>\n"; + echo "</table>\n"; + echo "<br />\n"; + + echo "<input type=\"hidden\" name=\"action\" value=\"delrow\" />\n"; + echo "<input type=\"submit\" name=\"yes\" value=\"{$lang['stryes']}\" />\n"; + echo "<input type=\"submit\" name=\"no\" value=\"{$lang['strno']}\" />\n"; + } elseif ($rs->recordCount() != 1) { + echo "<p>{$lang['strrownotunique']}</p>\n"; + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n"; + } else { + echo "<p>{$lang['strinvalidparam']}</p>\n"; + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n"; + } + if (isset($_REQUEST['table'])) { + echo "<input type=\"hidden\" name=\"table\" value=\"", htmlspecialchars($_REQUEST['table']), "\" />\n"; + } + + if (isset($_REQUEST['subject'])) { + echo "<input type=\"hidden\" name=\"subject\" value=\"", htmlspecialchars($_REQUEST['subject']), "\" />\n"; + } + + if (isset($_REQUEST['query'])) { + echo "<input type=\"hidden\" name=\"query\" value=\"", htmlspecialchars($_REQUEST['query']), "\" />\n"; + } + + if (isset($_REQUEST['count'])) { + echo "<input type=\"hidden\" name=\"count\" value=\"", htmlspecialchars($_REQUEST['count']), "\" />\n"; + } + + if (isset($_REQUEST['return'])) { + echo "<input type=\"hidden\" name=\"return\" value=\"", htmlspecialchars($_REQUEST['return']), "\" />\n"; + } + + echo "<input type=\"hidden\" name=\"page\" value=\"", htmlspecialchars($_REQUEST['page']), "\" />\n"; + echo "<input type=\"hidden\" name=\"sortkey\" value=\"", htmlspecialchars($_REQUEST['sortkey']), "\" />\n"; + echo "<input type=\"hidden\" name=\"sortdir\" value=\"", htmlspecialchars($_REQUEST['sortdir']), "\" />\n"; + echo "<input type=\"hidden\" name=\"strings\" value=\"", htmlspecialchars($_REQUEST['strings']), "\" />\n"; + echo "<input type=\"hidden\" name=\"key\" value=\"", htmlspecialchars(urlencode(serialize($_REQUEST['key']))), "\" />\n"; + echo "</form>\n"; + } else { + $status = $data->deleteRow($_POST['table'], unserialize(urldecode($_POST['key']))); + if ($status == 0) { + $this->doBrowse($lang['strrowdeleted']); + } elseif ($status == -2) { + $this->doBrowse($lang['strrownotunique']); + } else { + $this->doBrowse($lang['strrowdeletedbad']); + } + + } + + } + + /** + * build & return the FK information data structure + * used when deciding if a field should have a FK link or not + * @return [type] [description] + */ + function &getFKInfo() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + // Get the foreign key(s) information from the current table + $fkey_information = ['byconstr' => [], 'byfield' => []]; + + if (isset($_REQUEST['table'])) { + $constraints = $data->getConstraintsWithFields($_REQUEST['table']); + if ($constraints->recordCount() > 0) { + + $fkey_information['common_url'] = $misc->getHREF('schema') . '&subject=table'; + + /* build the FK constraints data structure */ + while (!$constraints->EOF) { + $constr = &$constraints->fields; + if ($constr['contype'] == 'f') { + + if (!isset($fkey_information['byconstr'][$constr['conid']])) { + $fkey_information['byconstr'][$constr['conid']] = [ + 'url_data' => 'table=' . urlencode($constr['f_table']) . '&schema=' . urlencode($constr['f_schema']), + 'fkeys' => [], + 'consrc' => $constr['consrc'], + ]; + } + + $fkey_information['byconstr'][$constr['conid']]['fkeys'][$constr['p_field']] = $constr['f_field']; + + if (!isset($fkey_information['byfield'][$constr['p_field']])) { + $fkey_information['byfield'][$constr['p_field']] = []; + } + + $fkey_information['byfield'][$constr['p_field']][] = $constr['conid']; + } + $constraints->moveNext(); + } + } + } + + return $fkey_information; + } + + /** + * Print table header cells + * @param $args - associative array for sort link parameters + * + */ + function printTableHeaderCells(&$rs, $args, $withOid) { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + $j = 0; + + foreach ($rs->fields as $k => $v) { + + if (($k === $data->id) && (!($withOid && $conf['show_oids']))) { + $j++; + continue; + } + $finfo = $rs->fetchField($j); + + if ($args === false) { + echo "<th class=\"data\">", $misc->printVal($finfo->name), "</th>\n"; + } else { + $args['page'] = $_REQUEST['page']; + $args['sortkey'] = $j + 1; + // Sort direction opposite to current direction, unless it's currently '' + $args['sortdir'] = ( + $_REQUEST['sortdir'] == 'asc' + and $_REQUEST['sortkey'] == ($j + 1) + ) ? 'desc' : 'asc'; + + $sortLink = http_build_query($args); + + echo "<th class=\"data\"><a href=\"?{$sortLink}\">" + , $misc->printVal($finfo->name); + if ($_REQUEST['sortkey'] == ($j + 1)) { + if ($_REQUEST['sortdir'] == 'asc') { + echo '<img src="' . $misc->icon('RaiseArgument') . '" alt="asc">'; + } else { + echo '<img src="' . $misc->icon('LowerArgument') . '" alt="desc">'; + } + + } + echo "</a></th>\n"; + } + $j++; + } + + reset($rs->fields); + } + + /* Print data-row cells */ + function printTableRowCells(&$rs, &$fkey_information, $withOid) { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + $j = 0; + + if (!isset($_REQUEST['strings'])) { + $_REQUEST['strings'] = 'collapsed'; + } + + foreach ($rs->fields as $k => $v) { + $finfo = $rs->fetchField($j++); + + if (($k === $data->id) && (!($withOid && $conf['show_oids']))) { + continue; + } elseif ($v !== null && $v == '') { + echo "<td> </td>"; + } else { + echo "<td style=\"white-space:nowrap;\">"; + + if (($v !== null) && isset($fkey_information['byfield'][$k])) { + foreach ($fkey_information['byfield'][$k] as $conid) { + + $query_params = $fkey_information['byconstr'][$conid]['url_data']; + + foreach ($fkey_information['byconstr'][$conid]['fkeys'] as $p_field => $f_field) { + $query_params .= '&' . urlencode("fkey[{$f_field}]") . '=' . urlencode($rs->fields[$p_field]); + } + + /* $fkey_information['common_url'] is already urlencoded */ + $query_params .= '&' . $fkey_information['common_url']; + echo "<div style=\"display:inline-block;\">"; + echo "<a class=\"fk fk_" . htmlentities($conid, ENT_QUOTES, 'UTF-8') . "\" href=\"display.php?{$query_params}\">"; + echo "<img src=\"" . $misc->icon('ForeignKey') . "\" style=\"vertical-align:middle;\" alt=\"[fk]\" title=\"" + . htmlentities($fkey_information['byconstr'][$conid]['consrc'], ENT_QUOTES, 'UTF-8') + . "\" />"; + echo "</a>"; + echo "</div>"; + } + echo $misc->printVal($v, $finfo->type, ['null' => true, 'clip' => ($_REQUEST['strings'] == 'collapsed'), 'class' => 'fk_value']); + } else { + echo $misc->printVal($v, $finfo->type, ['null' => true, 'clip' => ($_REQUEST['strings'] == 'collapsed')]); + } + echo "</td>"; + } + } + } + + /* Print the FK row, used in ajax requests */ + function doBrowseFK() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $ops = []; + foreach ($_REQUEST['fkey'] as $x => $y) { + $ops[$x] = '='; + } + $query = $data->getSelectSQL($_REQUEST['table'], [], $_REQUEST['fkey'], $ops); + $_REQUEST['query'] = $query; + + $fkinfo = $this->getFKInfo(); + + $max_pages = 1; + // Retrieve page from query. $max_pages is returned by reference. + $rs = $data->browseQuery('SELECT', $_REQUEST['table'], $_REQUEST['query'], + null, null, 1, 1, $max_pages); + + echo "<a href=\"\" style=\"display:table-cell;\" class=\"fk_delete\"><img alt=\"[delete]\" src=\"" . $misc->icon('Delete') . "\" /></a>\n"; + echo "<div style=\"display:table-cell;\">"; + + if (is_object($rs) && $rs->recordCount() > 0) { + /* we are browsing a referenced table here + * we should show OID if show_oids is true + * so we give true to withOid in functions bellow + */ + + echo "<table><tr>"; + $this->printTableHeaderCells($rs, false, true); + echo "</tr>"; + echo "<tr class=\"data1\">\n"; + $this->printTableRowCells($rs, $fkinfo, true); + echo "</tr>\n"; + echo "</table>\n"; + } else { + echo $lang['strnodata']; + } + + echo "</div>"; + + exit; + } + + /** + * Displays requested data + */ + function doBrowse($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $plugin_manager = $this->plugin_manager; + $data = $misc->getDatabaseAccessor(); + + $save_history = false; + // If current page is not set, default to first page + if (!isset($_REQUEST['page'])) { + $_REQUEST['page'] = 1; + } + + if (!isset($_REQUEST['nohistory'])) { + $save_history = true; + } + + if (isset($_REQUEST['subject'])) { + $subject = $_REQUEST['subject']; + if (isset($_REQUEST[$subject])) { + $object = $_REQUEST[$subject]; + } + + } else { + $subject = ''; + } + + $this->printTrail(isset($subject) ? $subject : 'database'); + $this->printTabs($subject, 'browse'); + + /* This code is used when browsing FK in pure-xHTML (without js) */ + if (isset($_REQUEST['fkey'])) { + $ops = []; + foreach ($_REQUEST['fkey'] as $x => $y) { + $ops[$x] = '='; + } + $query = $data->getSelectSQL($_REQUEST['table'], [], $_REQUEST['fkey'], $ops); + $_REQUEST['query'] = $query; + } + + if (isset($object)) { + if (isset($_REQUEST['query'])) { + $_SESSION['sqlquery'] = $_REQUEST['query']; + $misc->printTitle($lang['strselect']); + $type = 'SELECT'; + } else { + $type = 'TABLE'; + } + } else { + $misc->printTitle($lang['strqueryresults']); + /*we comes from sql.php, $_SESSION['sqlquery'] has been set there */ + $type = 'QUERY'; + } + + $misc->printMsg($msg); + + // If 'sortkey' is not set, default to '' + if (!isset($_REQUEST['sortkey'])) { + $_REQUEST['sortkey'] = ''; + } + + // If 'sortdir' is not set, default to '' + if (!isset($_REQUEST['sortdir'])) { + $_REQUEST['sortdir'] = ''; + } + + // If 'strings' is not set, default to collapsed + if (!isset($_REQUEST['strings'])) { + $_REQUEST['strings'] = 'collapsed'; + } + + // Fetch unique row identifier, if this is a table browse request. + if (isset($object)) { + $key = $data->getRowIdentifier($object); + } else { + $key = []; + } + + // Set the schema search path + if (isset($_REQUEST['search_path'])) { + if ($data->setSearchPath(array_map('trim', explode(',', $_REQUEST['search_path']))) != 0) { + return; + } + } + + // Retrieve page from query. $max_pages is returned by reference. + $rs = $data->browseQuery($type, + isset($object) ? $object : null, + isset($_SESSION['sqlquery']) ? $_SESSION['sqlquery'] : null, + $_REQUEST['sortkey'], $_REQUEST['sortdir'], $_REQUEST['page'], + $conf['max_rows'], $max_pages); + + $fkey_information = $this->getFKInfo(); + + // Build strings for GETs in array + $_gets = [ + 'server' => $_REQUEST['server'], + 'database' => $_REQUEST['database'], + ]; + + if (isset($_REQUEST['schema'])) { + $_gets['schema'] = $_REQUEST['schema']; + } + + if (isset($object)) { + $_gets[$subject] = $object; + } + + if (isset($subject)) { + $_gets['subject'] = $subject; + } + + if (isset($_REQUEST['query'])) { + $_gets['query'] = $_REQUEST['query']; + } + + if (isset($_REQUEST['count'])) { + $_gets['count'] = $_REQUEST['count']; + } + + if (isset($_REQUEST['return'])) { + $_gets['return'] = $_REQUEST['return']; + } + + if (isset($_REQUEST['search_path'])) { + $_gets['search_path'] = $_REQUEST['search_path']; + } + + if (isset($_REQUEST['table'])) { + $_gets['table'] = $_REQUEST['table']; + } + + if (isset($_REQUEST['sortkey'])) { + $_gets['sortkey'] = $_REQUEST['sortkey']; + } + + if (isset($_REQUEST['sortdir'])) { + $_gets['sortdir'] = $_REQUEST['sortdir']; + } + + if (isset($_REQUEST['nohistory'])) { + $_gets['nohistory'] = $_REQUEST['nohistory']; + } + + $_gets['strings'] = $_REQUEST['strings']; + + if ($save_history && is_object($rs) && ($type == 'QUERY')) //{ + { + $misc->saveScriptHistory($_REQUEST['query']); + } + + echo '<form method="POST" action="' . $_SERVER['REQUEST_URI'] . '"><textarea width="90%" name="query" rows="5" cols="100" resizable="true">'; + if (isset($_REQUEST['query'])) { + $query = $_REQUEST['query']; + } else { + $query = "SELECT * FROM {$_REQUEST['schema']}"; + if ($_REQUEST['subject'] == 'view') { + $query = "{$query}.{$_REQUEST['view']};"; + } else { + $query = "{$query}.{$_REQUEST['table']};"; + } + } + //$query = isset($_REQUEST['query'])? $_REQUEST['query'] : "select * from {$_REQUEST['schema']}.{$_REQUEST['table']};"; + echo $query; + echo '</textarea><br><input type="submit"/></form>'; + + if (is_object($rs) && $rs->recordCount() > 0) { + // Show page navigation + $misc->printPages($_REQUEST['page'], $max_pages, $_gets); + + echo "<table id=\"data\">\n<tr>"; + + // Check that the key is actually in the result set. This can occur for select + // operations where the key fields aren't part of the select. XXX: We should + // be able to support this, somehow. + foreach ($key as $v) { + // If a key column is not found in the record set, then we + // can't use the key. + if (!in_array($v, array_keys($rs->fields))) { + $key = []; + break; + } + } + + $buttons = [ + 'edit' => [ + 'content' => $lang['stredit'], + 'attr' => [ + 'href' => [ + 'url' => 'display.php', + 'urlvars' => array_merge([ + 'action' => 'confeditrow', + 'strings' => $_REQUEST['strings'], + 'page' => $_REQUEST['page'], + ], $_gets), + ], + ], + ], + 'delete' => [ + 'content' => $lang['strdelete'], + 'attr' => [ + 'href' => [ + 'url' => 'display.php', + 'urlvars' => array_merge([ + 'action' => 'confdelrow', + 'strings' => $_REQUEST['strings'], + 'page' => $_REQUEST['page'], + ], $_gets), + ], + ], + ], + ]; + $actions = [ + 'actionbuttons' => &$buttons, + 'place' => 'display-browse', + ]; + $plugin_manager->do_hook('actionbuttons', $actions); + + foreach (array_keys($actions['actionbuttons']) as $action) { + $actions['actionbuttons'][$action]['attr']['href']['urlvars'] = array_merge( + $actions['actionbuttons'][$action]['attr']['href']['urlvars'], + $_gets + ); + } + + $edit_params = isset($actions['actionbuttons']['edit']) ? + $actions['actionbuttons']['edit'] : []; + $delete_params = isset($actions['actionbuttons']['delete']) ? + $actions['actionbuttons']['delete'] : []; + + // Display edit and delete actions if we have a key + $colspan = count($buttons); + if ($colspan > 0 and count($key) > 0) { + echo "<th colspan=\"{$colspan}\" class=\"data\">{$lang['stractions']}</th>\n"; + } + + /* we show OIDs only if we are in TABLE or SELECT type browsing */ + $this->printTableHeaderCells($rs, $_gets, isset($object)); + + echo "</tr>\n"; + + $i = 0; + reset($rs->fields); + while (!$rs->EOF) { + $id = (($i % 2) == 0 ? '1' : '2'); + echo "<tr class=\"data{$id}\">\n"; + // Display edit and delete links if we have a key + if ($colspan > 0 and count($key) > 0) { + $keys_array = []; + $has_nulls = false; + foreach ($key as $v) { + if ($rs->fields[$v] === null) { + $has_nulls = true; + break; + } + $keys_array["key[{$v}]"] = $rs->fields[$v]; + } + if ($has_nulls) { + echo "<td colspan=\"{$colspan}\"> </td>\n"; + } else { + + if (isset($actions['actionbuttons']['edit'])) { + $actions['actionbuttons']['edit'] = $edit_params; + $actions['actionbuttons']['edit']['attr']['href']['urlvars'] = array_merge( + $actions['actionbuttons']['edit']['attr']['href']['urlvars'], + $keys_array + ); + } + + if (isset($actions['actionbuttons']['delete'])) { + $actions['actionbuttons']['delete'] = $delete_params; + $actions['actionbuttons']['delete']['attr']['href']['urlvars'] = array_merge( + $actions['actionbuttons']['delete']['attr']['href']['urlvars'], + $keys_array + ); + } + + foreach ($actions['actionbuttons'] as $action) { + echo "<td class=\"opbutton{$id}\">"; + $this->printLink($action); + echo "</td>\n"; + } + } + } + + $this->printTableRowCells($rs, $fkey_information, isset($object)); + + echo "</tr>\n"; + $rs->moveNext(); + $i++; + } + echo "</table>\n"; + + echo "<p>", $rs->recordCount(), " {$lang['strrows']}</p>\n"; + // Show page navigation + $misc->printPages($_REQUEST['page'], $max_pages, $_gets); + } else { + echo "<p>{$lang['strnodata']}</p>\n"; + } + + // Navigation links + $navlinks = []; + + $fields = [ + 'server' => $_REQUEST['server'], + 'database' => $_REQUEST['database'], + ]; + + if (isset($_REQUEST['schema'])) { + $fields['schema'] = $_REQUEST['schema']; + } + + // Return + if (isset($_REQUEST['return'])) { + $urlvars = $misc->getSubjectParams($_REQUEST['return']); + + $navlinks['back'] = [ + 'attr' => [ + 'href' => [ + 'url' => $urlvars['url'], + 'urlvars' => $urlvars['params'], + ], + ], + 'content' => $lang['strback'], + ]; + } + + // Edit SQL link + if ($type == 'QUERY') { + $navlinks['edit'] = [ + 'attr' => [ + 'href' => [ + 'url' => 'database.php', + 'urlvars' => array_merge($fields, [ + 'action' => 'sql', + 'paginate' => 'on', + ]), + ], + ], + 'content' => $lang['streditsql'], + ]; + } + + // Expand/Collapse + if ($_REQUEST['strings'] == 'expanded') { + $navlinks['collapse'] = [ + 'attr' => [ + 'href' => [ + 'url' => 'display.php', + 'urlvars' => array_merge( + $_gets, + [ + 'strings' => 'collapsed', + 'page' => $_REQUEST['page'], + ]), + ], + ], + 'content' => $lang['strcollapse'], + ]; + } else { + $navlinks['collapse'] = [ + 'attr' => [ + 'href' => [ + 'url' => 'display.php', + 'urlvars' => array_merge( + $_gets, + [ + 'strings' => 'expanded', + 'page' => $_REQUEST['page'], + ]), + ], + ], + 'content' => $lang['strexpand'], + ]; + } + + // Create view and download + if (isset($_REQUEST['query']) && isset($rs) && is_object($rs) && $rs->recordCount() > 0) { + + // Report views don't set a schema, so we need to disable create view in that case + if (isset($_REQUEST['schema'])) { + + $navlinks['createview'] = [ + 'attr' => [ + 'href' => [ + 'url' => 'views.php', + 'urlvars' => array_merge($fields, [ + 'action' => 'create', + 'formDefinition' => $_REQUEST['query'], + ]), + ], + ], + 'content' => $lang['strcreateview'], + ]; + } + + $urlvars = []; + if (isset($_REQUEST['search_path'])) { + $urlvars['search_path'] = $_REQUEST['search_path']; + } + + $navlinks['download'] = [ + 'attr' => [ + 'href' => [ + 'url' => 'dataexport.php', + 'urlvars' => array_merge($fields, $urlvars), + ], + ], + 'content' => $lang['strdownload'], + ]; + } + + // Insert + if (isset($object) && (isset($subject) && $subject == 'table')) { + $navlinks['insert'] = [ + 'attr' => [ + 'href' => [ + 'url' => 'tables.php', + 'urlvars' => array_merge($fields, [ + 'action' => 'confinsertrow', + 'table' => $object, + ]), + ], + ], + 'content' => $lang['strinsert'], + ]; + } + + // Refresh + $navlinks['refresh'] = [ + 'attr' => [ + 'href' => [ + 'url' => 'display.php', + 'urlvars' => array_merge( + $_gets, + [ + 'strings' => $_REQUEST['strings'], + 'page' => $_REQUEST['page'], + ]), + ], + ], + 'content' => $lang['strrefresh'], + ]; + + $this->printNavLinks($navlinks, 'display-browse', get_defined_vars()); + } + + public function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $plugin_manager = $this->plugin_manager; + $data = $misc->getDatabaseAccessor(); + $action = $this->action; + + /* shortcuts: this function exit the script for ajax purpose */ + if ($action == 'dobrowsefk') { + $this->doBrowseFK(); + } + + $scripts = "<script src=\"/js/display.js\" type=\"text/javascript\"></script>"; + + $scripts .= "<script type=\"text/javascript\">\n"; + $scripts .= "var Display = {\n"; + $scripts .= "errmsg: '" . str_replace("'", "\'", $lang['strconnectionfail']) . "'\n"; + $scripts .= "};\n"; + $scripts .= "</script>\n"; + + // Set the title based on the subject of the request + if (isset($_REQUEST['subject']) && isset($_REQUEST[$_REQUEST['subject']])) { + if ($_REQUEST['subject'] == 'table') { + $misc->printHeader( + $lang['strtables'] . ': ' . $_REQUEST[$_REQUEST['subject']], + $scripts + ); + } else if ($_REQUEST['subject'] == 'view') { + $misc->printHeader( + $lang['strviews'] . ': ' . $_REQUEST[$_REQUEST['subject']], + $scripts + ); + } else if ($_REQUEST['subject'] == 'column') { + $misc->printHeader( + $lang['strcolumn'] . ': ' . $_REQUEST[$_REQUEST['subject']], + $scripts + ); + } + } else { + $misc->printHeader($lang['strqueryresults']); + } + + $misc->printBody(); + + switch ($action) { + case 'editrow': + if (isset($_POST['save'])) { + $this->doEditRow(false); + } else { + $this->doBrowse(); + } + + break; + case 'confeditrow': + $this->doEditRow(true); + break; + case 'delrow': + if (isset($_POST['yes'])) { + $this->doDelRow(false); + } else { + $this->doBrowse(); + } + + break; + case 'confdelrow': + $this->doDelRow(true); + break; + default: + $this->doBrowse(); + break; + } + + $misc->printFooter(); + } +} diff --git a/src/controllers/DomainController.php b/src/controllers/DomainController.php index 22f8a841..5da54b4b 100644 --- a/src/controllers/DomainController.php +++ b/src/controllers/DomainController.php @@ -9,6 +9,116 @@ use \PHPPgAdmin\Decorators\Decorator; class DomainController extends BaseController { public $_name = 'DomainController'; + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + + $misc->printHeader($lang['strdomains']); + $misc->printBody(); + + switch ($action) { + case 'add_check': + $this->addCheck(true); + break; + case 'save_add_check': + if (isset($_POST['cancel'])) { + $this->doProperties(); + } else { + $this->addCheck(false); + } + + break; + case 'drop_con': + if (isset($_POST['drop'])) { + $this->doDropConstraint(false); + } else { + $this->doProperties(); + } + + break; + case 'confirm_drop_con': + $this->doDropConstraint(true); + break; + case 'save_create': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveCreate(); + } + + break; + case 'create': + $this->doCreate(); + break; + case 'drop': + if (isset($_POST['drop'])) { + $this->doDrop(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + case 'save_alter': + if (isset($_POST['alter'])) { + $this->doSaveAlter(); + } else { + $this->doProperties(); + } + + break; + case 'alter': + $this->doAlter(); + break; + case 'properties': + $this->doProperties(); + break; + default: + $this->doDefault(); + break; + } + + return $misc->printFooter(); + } + +/** + * Generate XML for the browser tree. + */ + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $domains = $data->getDomains(); + + $reqvars = $misc->getRequestVars('domain'); + + $attrs = [ + 'text' => Decorator::field('domname'), + 'icon' => 'Domain', + 'toolTip' => Decorator::field('domcomment'), + 'action' => Decorator::actionurl('domains.php', + $reqvars, + [ + 'action' => 'properties', + 'domain' => Decorator::field('domname'), + ] + ), + ]; + + return $misc->printTree($domains, $attrs, 'domains'); + } + /** * Function to save after altering a domain */ @@ -37,7 +147,7 @@ class DomainController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('domain'); + $this->printTrail('domain'); $misc->printTitle($lang['stralter'], 'pg.domain.alter'); $misc->printMsg($msg); @@ -110,7 +220,7 @@ class DomainController extends BaseController { } if ($confirm) { - $misc->printTrail('domain'); + $this->printTrail('domain'); $misc->printTitle($lang['straddcheck'], 'pg.constraint.check'); $misc->printMsg($msg); @@ -159,7 +269,7 @@ class DomainController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('domain'); + $this->printTrail('domain'); $misc->printTitle($lang['strdrop'], 'pg.constraint.drop'); $misc->printMsg($msg); @@ -195,7 +305,7 @@ class DomainController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('domain'); + $this->printTrail('domain'); $misc->printTitle($lang['strproperties'], 'pg.domain'); $misc->printMsg($msg); @@ -258,7 +368,7 @@ class DomainController extends BaseController { ], ]; - echo $misc->printTable($domaincons, $columns, $actions, 'domains-properties', $lang['strnodata']); + echo $this->printTable($domaincons, $columns, $actions, 'domains-properties', $lang['strnodata']); } } else { echo "<p>{$lang['strnodata']}</p>\n"; @@ -314,7 +424,7 @@ class DomainController extends BaseController { ]; } - $misc->printNavLinks($navlinks, 'domains-properties', get_defined_vars()); + $this->printNavLinks($navlinks, 'domains-properties', get_defined_vars()); } /** @@ -327,7 +437,7 @@ class DomainController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('domain'); + $this->printTrail('domain'); $misc->printTitle($lang['strdrop'], 'pg.domain.drop'); echo "<p>", sprintf($lang['strconfdropdomain'], $misc->printVal($_REQUEST['domain'])), "</p>\n"; @@ -386,7 +496,7 @@ class DomainController extends BaseController { $types = $data->getTypes(true); - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strcreatedomain'], 'pg.domain.create'); $misc->printMsg($msg); @@ -472,8 +582,8 @@ class DomainController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('schema'); - $misc->printTabs('schema', 'domains'); + $this->printTrail('schema'); + $this->printTabs('schema', 'domains'); $misc->printMsg($msg); $domains = $data->getDomains(); @@ -543,7 +653,7 @@ class DomainController extends BaseController { unset($actions['alter']); } - echo $misc->printTable($domains, $columns, $actions, 'domains-domains', $lang['strnodomains']); + echo $this->printTable($domains, $columns, $actions, 'domains-domains', $lang['strnodomains']); $navlinks = [ 'create' => [ @@ -561,6 +671,6 @@ class DomainController extends BaseController { 'content' => $lang['strcreatedomain'], ], ]; - $misc->printNavLinks($navlinks, 'domains-domains', get_defined_vars()); + $this->printNavLinks($navlinks, 'domains-domains', get_defined_vars()); } } diff --git a/src/controllers/FulltextController.php b/src/controllers/FulltextController.php index 0649166f..d3c74b9d 100644 --- a/src/controllers/FulltextController.php +++ b/src/controllers/FulltextController.php @@ -9,15 +9,214 @@ use \PHPPgAdmin\Decorators\Decorator; class FulltextController extends BaseController { public $_name = 'FulltextController'; + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } else if ($action == 'subtree') { + return $this->doSubTree($_REQUEST['what']); + } + + $misc->printHeader($lang['strschemas']); + $misc->printBody(); + + if (isset($_POST['cancel'])) { + if (isset($_POST['prev_action'])) { + $action = $_POST['prev_action']; + } else { + $action = ''; + } + } + + switch ($action) { + case 'createconfig': + if (isset($_POST['create'])) { + $this->doSaveCreateConfig(); + } else { + $this->doCreateConfig(); + } + + break; + case 'alterconfig': + if (isset($_POST['alter'])) { + $this->doSaveAlterConfig(); + } else { + $this->doAlterConfig(); + } + + break; + case 'dropconfig': + if (isset($_POST['drop'])) { + $this->doDropConfig(false); + } else { + $this->doDropConfig(true); + } + + break; + case 'viewconfig': + $this->doViewConfig($_REQUEST['ftscfg']); + break; + case 'viewparsers': + $this->doViewParsers(); + break; + case 'viewdicts': + $this->doViewDicts(); + break; + case 'createdict': + if (isset($_POST['create'])) { + $this->doSaveCreateDict(); + } else { + doCreateDict(); + } + + break; + case 'alterdict': + if (isset($_POST['alter'])) { + $this->doSaveAlterDict(); + } else { + $this->doAlterDict(); + } + + break; + case 'dropdict': + if (isset($_POST['drop'])) { + $this->doDropDict(false); + } else { + $this->doDropDict(true); + } + + break; + case 'dropmapping': + if (isset($_POST['drop'])) { + $this->doDropMapping(false); + } else { + $this->doDropMapping(true); + } + + break; + case 'altermapping': + if (isset($_POST['alter'])) { + $this->doSaveAlterMapping(); + } else { + $this->doAlterMapping(); + } + + break; + case 'addmapping': + if (isset($_POST['add'])) { + $this->doSaveAddMapping(); + } else { + $this->doAddMapping(); + } + + break; + + default: + $this->doDefault(); + break; + } + + return $misc->printFooter(); + + } + + /** + * Generate XML for the browser tree. + */ + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $tabs = $misc->getNavTabs('fulltext'); + $items = $misc->adjustTabsForTree($tabs); + + $reqvars = $misc->getRequestVars('ftscfg'); + + $attrs = [ + 'text' => Decorator::field('title'), + 'icon' => Decorator::field('icon'), + 'action' => Decorator::actionurl('fulltext.php', + $reqvars, + field('urlvars') + ), + 'branch' => Decorator::url('fulltext.php', + $reqvars, + [ + 'action' => 'subtree', + 'what' => Decorator::field('icon'), // IZ: yeah, it's ugly, but I do not want to change navigation tabs arrays + ] + ), + ]; + + return $misc->printTree($items, $attrs, 'fts'); + } + + function doSubTree($what) { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + switch ($what) { + case 'FtsCfg': + $items = $data->getFtsConfigurations(false); + $urlvars = ['action' => 'viewconfig', 'ftscfg' => Decorator::field('name')]; + break; + case 'FtsDict': + $items = $data->getFtsDictionaries(false); + $urlvars = ['action' => 'viewdicts']; + break; + case 'FtsParser': + $items = $data->getFtsParsers(false); + $urlvars = ['action' => 'viewparsers']; + break; + default: + return; + } + + $reqvars = $misc->getRequestVars('ftscfg'); + + $attrs = [ + 'text' => Decorator::field('name'), + 'icon' => $what, + 'toolTip' => Decorator::field('comment'), + 'action' => Decorator::actionurl('fulltext.php', + $reqvars, + $urlvars + ), + 'branch' => Decorator::ifempty(Decorator::field('branch'), + '', + url('fulltext.php', + $reqvars, + [ + 'action' => 'subtree', + 'ftscfg' => Decorator::field('name'), + ] + ) + ), + ]; + + return $misc->printTree($items, $attrs, strtolower($what)); + + } + public function doDefault($msg = '') { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('schema'); - $misc->printTabs('schema', 'fulltext'); - $misc->printTabs('fulltext', 'ftsconfigs'); + $this->printTrail('schema'); + $this->printTabs('schema', 'fulltext'); + $this->printTabs('fulltext', 'ftsconfigs'); $misc->printMsg($msg); $cfgs = $data->getFtsConfigurations(false); @@ -69,7 +268,7 @@ class FulltextController extends BaseController { ], ]; - echo $misc->printTable($cfgs, $columns, $actions, 'fulltext-fulltext', $lang['strftsnoconfigs']); + echo $this->printTable($cfgs, $columns, $actions, 'fulltext-fulltext', $lang['strftsnoconfigs']); $navlinks = [ 'createconf' => [ @@ -88,7 +287,7 @@ class FulltextController extends BaseController { ], ]; - $misc->printNavLinks($navlinks, 'fulltext-fulltext', get_defined_vars()); + $this->printNavLinks($navlinks, 'fulltext-fulltext', get_defined_vars()); } public function doDropConfig($confirm) { @@ -98,7 +297,7 @@ class FulltextController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('ftscfg'); + $this->printTrail('ftscfg'); $misc->printTitle($lang['strdrop'], 'pg.ftscfg.drop'); echo "<p>", sprintf($lang['strconfdropftsconfig'], $misc->printVal($_REQUEST['ftscfg'])), "</p>\n"; @@ -132,7 +331,7 @@ class FulltextController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('ftscfg'); // TODO: change to smth related to dictionary + $this->printTrail('ftscfg'); // TODO: change to smth related to dictionary $misc->printTitle($lang['strdrop'], 'pg.ftsdict.drop'); echo "<p>", sprintf($lang['strconfdropftsdict'], $misc->printVal($_REQUEST['ftsdict'])), "</p>\n"; @@ -199,7 +398,7 @@ class FulltextController extends BaseController { // Fetch all FTS parsers from the database $ftsparsers = $data->getFtsParsers(); - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strftscreateconfig'], 'pg.ftscfg.create'); $misc->printMsg($msg); @@ -324,7 +523,7 @@ class FulltextController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('ftscfg'); + $this->printTrail('ftscfg'); $misc->printTitle($lang['stralter'], 'pg.ftscfg.alter'); $misc->printMsg($msg); @@ -403,9 +602,9 @@ class FulltextController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('schema'); - $misc->printTabs('schema', 'fulltext'); - $misc->printTabs('fulltext', 'ftsparsers'); + $this->printTrail('schema'); + $this->printTabs('schema', 'fulltext'); + $this->printTabs('fulltext', 'ftsparsers'); $misc->printMsg($msg); $parsers = $data->getFtsParsers(false); @@ -427,7 +626,7 @@ class FulltextController extends BaseController { $actions = []; - echo $misc->printTable($parsers, $columns, $actions, 'fulltext-viewparsers', $lang['strftsnoparsers']); + echo $this->printTable($parsers, $columns, $actions, 'fulltext-viewparsers', $lang['strftsnoparsers']); //TODO: navlink to "create parser" } @@ -441,9 +640,9 @@ class FulltextController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('schema'); - $misc->printTabs('schema', 'fulltext'); - $misc->printTabs('fulltext', 'ftsdicts'); + $this->printTrail('schema'); + $this->printTabs('schema', 'fulltext'); + $this->printTabs('fulltext', 'ftsdicts'); $misc->printMsg($msg); $dicts = $data->getFtsDictionaries(false); @@ -493,7 +692,7 @@ class FulltextController extends BaseController { ], ]; - echo $misc->printTable($dicts, $columns, $actions, 'fulltext-viewdicts', $lang['strftsnodicts']); + echo $this->printTable($dicts, $columns, $actions, 'fulltext-viewdicts', $lang['strftsnodicts']); $navlinks = [ 'createdict' => [ @@ -512,7 +711,7 @@ class FulltextController extends BaseController { ], ]; - $misc->printNavLinks($navlinks, 'fulltext-viewdicts', get_defined_vars()); + $this->printNavLinks($navlinks, 'fulltext-viewdicts', get_defined_vars()); } /** @@ -524,9 +723,9 @@ class FulltextController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('ftscfg'); - $misc->printTabs('schema', 'fulltext'); - $misc->printTabs('fulltext', 'ftsconfigs'); + $this->printTrail('ftscfg'); + $this->printTabs('schema', 'fulltext'); + $this->printTabs('fulltext', 'ftsconfigs'); $misc->printMsg($msg); echo "<h3>{$lang['strftsconfigmap']}</h3>\n"; @@ -588,7 +787,7 @@ class FulltextController extends BaseController { ]; - echo $misc->printTable($map, $columns, $actions, 'fulltext-viewconfig', $lang['strftsemptymap']); + echo $this->printTable($map, $columns, $actions, 'fulltext-viewconfig', $lang['strftsemptymap']); $navlinks = [ 'addmapping' => [ @@ -608,7 +807,7 @@ class FulltextController extends BaseController { ], ]; - $misc->printNavLinks($navlinks, 'fulltext-viewconfig', get_defined_vars()); + $this->printNavLinks($navlinks, 'fulltext-viewconfig', get_defined_vars()); } /** @@ -655,7 +854,7 @@ class FulltextController extends BaseController { // Fetch all FTS dictionaries from the database $ftstpls = $data->getFtsDictionaryTemplates(); - $misc->printTrail('schema'); + $this->printTrail('schema'); // TODO: create doc links $misc->printTitle($lang['strftscreatedict'], 'pg.ftsdict.create'); $misc->printMsg($msg); @@ -796,7 +995,7 @@ class FulltextController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('ftscfg'); // TODO: change to smth related to dictionary + $this->printTrail('ftscfg'); // TODO: change to smth related to dictionary $misc->printTitle($lang['stralter'], 'pg.ftsdict.alter'); $misc->printMsg($msg); @@ -881,7 +1080,7 @@ class FulltextController extends BaseController { } if ($confirm) { - $misc->printTrail('ftscfg'); // TODO: proper breadcrumbs + $this->printTrail('ftscfg'); // TODO: proper breadcrumbs $misc->printTitle($lang['strdrop'], 'pg.ftscfg.alter'); echo "<form action=\"/src/views/fulltext.php\" method=\"post\">\n"; @@ -932,7 +1131,7 @@ class FulltextController extends BaseController { $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('ftscfg'); + $this->printTrail('ftscfg'); $misc->printTitle($lang['stralter'], 'pg.ftscfg.alter'); $misc->printMsg($msg); @@ -1034,7 +1233,7 @@ class FulltextController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('ftscfg'); + $this->printTrail('ftscfg'); $misc->printTitle($lang['stralter'], 'pg.ftscfg.alter'); $misc->printMsg($msg); diff --git a/src/controllers/FunctionController.php b/src/controllers/FunctionController.php index 0100aef6..be32e03d 100644 --- a/src/controllers/FunctionController.php +++ b/src/controllers/FunctionController.php @@ -7,11 +7,105 @@ use \PHPPgAdmin\Decorators\Decorator; * Base controller class */ class FunctionController extends BaseController { - public $_name = 'FunctionController'; + public $_name = 'FunctionController'; + public $table_place = 'functions-functions'; -/** - * Function to save after editing a function - */ + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + $data = $misc->getDatabaseAccessor(); + + $misc->printHeader($lang['strfunctions'], null, true, 'datatables_header.twig'); + $misc->printBody(); + + switch ($action) { + case 'save_create': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveCreate(); + } + + break; + case 'create': + $this->doCreate(); + break; + case 'drop': + if (isset($_POST['drop'])) { + $this->doDrop(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + case 'save_edit': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveEdit(); + } + + break; + case 'edit': + $this->doEdit(); + break; + case 'properties': + $this->doProperties(); + break; + default: + $this->doDefault(); + break; + } + + $misc->printFooter(); + + } + + /** + * Generate XML for the browser tree. + */ + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $funcs = $data->getFunctions(); + + $proto = Decorator::concat(Decorator::field('proname'), ' (', Decorator::field('proarguments'), ')'); + + $reqvars = $misc->getRequestVars('function'); + + $attrs = [ + 'text' => $proto, + 'icon' => 'Function', + 'toolTip' => Decorator::field('procomment'), + 'action' => Decorator::redirecturl('redirect.php', + $reqvars, + [ + 'action' => 'properties', + 'function' => $proto, + 'function_oid' => Decorator::field('prooid'), + ] + ), + ]; + + return $misc->printTree($funcs, $attrs, 'functions'); + } + + /** + * Function to save after editing a function + */ public function doSaveEdit() { $conf = $this->conf; @@ -54,16 +148,16 @@ class FunctionController extends BaseController { } } -/** - * Function to allow editing of a Function - */ + /** + * Function to allow editing of a Function + */ public function doEdit($msg = '') { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('function'); + $this->printTrail('function'); $misc->printTitle($lang['stralter'], 'pg.function.alter'); $misc->printMsg($msg); @@ -290,22 +384,23 @@ class FunctionController extends BaseController { } -/** - * Show read only properties of a function - */ + /** + * Show read only properties of a function + */ public function doProperties($msg = '') { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('function'); + $this->printTrail('function'); $misc->printTitle($lang['strproperties'], 'pg.function'); $misc->printMsg($msg); $funcdata = $data->getFunction($_REQUEST['function_oid']); if ($funcdata->recordCount() > 0) { + // Deal with named parameters if ($data->hasNamedParams()) { if (isset($funcdata->fields['proallarguments'])) { @@ -324,15 +419,20 @@ class FunctionController extends BaseController { if (isset($modes_arr[$i])) { switch ($modes_arr[$i]) { - case 'i':$args .= " IN "; + case 'i': + $args .= " IN "; break; - case 'o':$args .= " OUT "; + case 'o': + $args .= " OUT "; break; - case 'b':$args .= " INOUT "; + case 'b': + $args .= " INOUT "; break; - case 'v':$args .= " VARIADIC "; + case 'v': + $args .= " VARIADIC "; break; - case 't':$args .= " TABLE "; + case 't': + $args .= " TABLE "; break; } } @@ -378,11 +478,13 @@ class FunctionController extends BaseController { echo "<tr><th class=\"data\" colspan=\"4\">{$lang['strlinksymbol']}</th></tr>\n"; echo "<tr><td class=\"data1\" colspan=\"4\">", $misc->printVal($funcdata->fields['prosrc']), "</td></tr>\n"; } else { - include_once BASE_PATH . '/src/highlight.php'; + $highlight = new \PHPPgAdmin\Highlight(); + echo "<tr><th class=\"data\" colspan=\"4\">{$lang['strdefinition']}</th></tr>\n"; // Check to see if we have syntax highlighting for this language - if (isset($data->langmap[$funcdata->fields['prolanguage']])) { - $temp = syntax_highlight(htmlspecialchars($funcdata->fields['prosrc']), $data->langmap[$funcdata->fields['prolanguage']]); + if (array_key_exists($fnlang, $data->langmap)) { + + $temp = $highlight->syntax_highlight(htmlspecialchars($funcdata->fields['prosrc']), $data->langmap[$fnlang]); $tag = 'prenoescape'; } else { $temp = $funcdata->fields['prosrc']; @@ -465,12 +567,12 @@ class FunctionController extends BaseController { ], ]; - $misc->printNavLinks($navlinks, 'functions-properties', get_defined_vars()); + $this->printNavLinks($navlinks, 'functions-properties', get_defined_vars()); } -/** - * Show confirmation of drop and perform actual drop - */ + /** + * Show confirmation of drop and perform actual drop + */ public function doDrop($confirm) { $conf = $this->conf; $misc = $this->misc; @@ -483,7 +585,7 @@ class FunctionController extends BaseController { } if ($confirm) { - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strdrop'], 'pg.function.drop'); echo "<form action=\"/src/views/functions.php\" method=\"post\">\n"; @@ -546,16 +648,16 @@ class FunctionController extends BaseController { } -/** - * Displays a screen where they can enter a new function - */ + /** + * Displays a screen where they can enter a new function + */ public function doCreate($msg = '', $szJS = "") { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('schema'); + $this->printTrail('schema'); if (!isset($_POST['formFunction'])) { $_POST['formFunction'] = ''; } @@ -813,9 +915,9 @@ class FunctionController extends BaseController { echo $szJS; } -/** - * Actually creates the new function in the database - */ + /** + * Actually creates the new function in the database + */ public function doSaveCreate() { $conf = $this->conf; $misc = $this->misc; @@ -941,17 +1043,17 @@ class FunctionController extends BaseController { return $szTypes . $szModes; } -/** - * Show default list of functions in the database - */ + /** + * Show default list of functions in the database + */ public function doDefault($msg = '') { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('schema'); - $misc->printTabs('schema', 'functions'); + $this->printTrail('schema'); + $this->printTabs('schema', 'functions'); $misc->printMsg($msg); $funcs = $data->getFunctions(); @@ -1031,7 +1133,7 @@ class FunctionController extends BaseController { ], ]; - echo $misc->printTable($funcs, $columns, $actions, 'functions-functions', $lang['strnofunctions']); + echo $this->printTable($funcs, $columns, $actions, $this->table_place, $lang['strnofunctions']); $navlinks = [ 'createpl' => [ @@ -1080,6 +1182,9 @@ class FunctionController extends BaseController { ], ]; - $misc->printNavLinks($navlinks, 'functions-functions', get_defined_vars()); + $this->printNavLinks($navlinks, 'functions-functions', get_defined_vars()); + + echo $this->view->fetch('table_list_footer.twig', ['table_class' => $this->table_place]); } + } diff --git a/src/controllers/GroupController.php b/src/controllers/GroupController.php index 9895fca0..b91f4a55 100644 --- a/src/controllers/GroupController.php +++ b/src/controllers/GroupController.php @@ -37,7 +37,7 @@ class GroupController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('group'); + $this->printTrail('group'); $misc->printTitle($lang['strdropmember'], 'pg.group.alter'); echo "<p>", sprintf($lang['strconfdropmember'], $misc->printVal($_REQUEST['user']), $misc->printVal($_REQUEST['group'])), "</p>\n"; @@ -74,7 +74,7 @@ class GroupController extends BaseController { $_POST['user'] = ''; } - $misc->printTrail('group'); + $this->printTrail('group'); $misc->printTitle($lang['strproperties'], 'pg.group'); $misc->printMsg($msg); @@ -108,7 +108,7 @@ class GroupController extends BaseController { ], ]; - echo $misc->printTable($groupdata, $columns, $actions, 'groups-members', $lang['strnousers']); + echo $this->printTable($groupdata, $columns, $actions, 'groups-members', $lang['strnousers']); } // Display form for adding a user to the group @@ -127,7 +127,7 @@ class GroupController extends BaseController { echo "<input type=\"hidden\" name=\"action\" value=\"add_member\" />\n"; echo "</form>\n"; - $misc->printNavLinks(['showall' => [ + $this->printNavLinks(['showall' => [ 'attr' => [ 'href' => [ 'url' => 'groups.php', @@ -150,7 +150,7 @@ class GroupController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('group'); + $this->printTrail('group'); $misc->printTitle($lang['strdrop'], 'pg.group.drop'); echo "<p>", sprintf($lang['strconfdropgroup'], $misc->printVal($_REQUEST['group'])), "</p>\n"; @@ -192,7 +192,7 @@ class GroupController extends BaseController { // Fetch a list of all users in the cluster $users = $data->getUsers(); - $misc->printTrail('server'); + $this->printTrail('server'); $misc->printTitle($lang['strcreategroup'], 'pg.group.create'); $misc->printMsg($msg); @@ -258,8 +258,8 @@ class GroupController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('server'); - $misc->printTabs('server', 'groups'); + $this->printTrail('server'); + $this->printTabs('server', 'groups'); $misc->printMsg($msg); $groups = $data->getGroups(); @@ -291,9 +291,9 @@ class GroupController extends BaseController { ], ]; - echo $misc->printTable($groups, $columns, $actions, 'groups-properties', $lang['strnogroups']); + echo $this->printTable($groups, $columns, $actions, 'groups-properties', $lang['strnogroups']); - $misc->printNavLinks(['create' => [ + $this->printNavLinks(['create' => [ 'attr' => [ 'href' => [ 'url' => 'groups.php', diff --git a/src/controllers/HistoryController.php b/src/controllers/HistoryController.php index ecd5d6e3..57761063 100644 --- a/src/controllers/HistoryController.php +++ b/src/controllers/HistoryController.php @@ -80,7 +80,7 @@ class HistoryController extends BaseController { ], ]; - echo $misc->printTable($history, $columns, $actions, 'history-history', $lang['strnohistory']); + echo $this->printTable($history, $columns, $actions, 'history-history', $lang['strnohistory']); } else { echo "<p>{$lang['strnohistory']}</p>\n"; } @@ -131,7 +131,7 @@ class HistoryController extends BaseController { ]; } - $misc->printNavLinks($navlinks, 'history-history', get_defined_vars()); + $this->printNavLinks($navlinks, 'history-history', get_defined_vars()); } public function doDelHistory($qid, $confirm) { diff --git a/src/controllers/IndexController.php b/src/controllers/IndexController.php index 69cc0655..9d521479 100644 --- a/src/controllers/IndexController.php +++ b/src/controllers/IndexController.php @@ -9,6 +9,101 @@ use \PHPPgAdmin\Decorators\Decorator; class IndexController extends BaseController { public $_name = 'IndexController'; + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + + $misc->printHeader($lang['strindexes'], "<script src=\"/js/indexes.js\" type=\"text/javascript\"></script>"); + + if ($action == 'create_index' || $action == 'save_create_index') { + echo "<body onload=\"init();\">"; + } else { + $misc->printBody(); + } + + switch ($action) { + case 'cluster_index': + if (isset($_POST['cluster'])) { + $this->doClusterIndex(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_cluster_index': + $this->doClusterIndex(true); + break; + case 'reindex': + $this->doReindex(); + break; + case 'save_create_index': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveCreateIndex(); + } + + break; + case 'create_index': + $this->doCreateIndex(); + break; + case 'drop_index': + if (isset($_POST['drop'])) { + $this->doDropIndex(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop_index': + $this->doDropIndex(true); + break; + default: + $this->doDefault(); + break; + } + + return $misc->printFooter(); + + } + + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $indexes = $data->getIndexes($_REQUEST['table']); + + $reqvars = $misc->getRequestVars('table'); + + function getIcon($f) { + if ($f['indisprimary'] == 't') { + return 'PrimaryKey'; + } + + if ($f['indisunique'] == 't') { + return 'UniqueConstraint'; + } + + return 'Index'; + } + + $attrs = [ + 'text' => Decorator::field('indname'), + 'icon' => Decorator::callback('getIcon'), + ]; + + return $misc->printTree($indexes, $attrs, 'indexes'); + } + /** * Show confirmation of cluster index and perform actual cluster */ @@ -22,7 +117,7 @@ class IndexController extends BaseController { // Default analyze to on $_REQUEST['analyze'] = true; - $misc->printTrail('index'); + $this->printTrail('index'); $misc->printTitle($lang['strclusterindex'], 'pg.index.cluster'); echo "<p>", sprintf($lang['strconfcluster'], $misc->printVal($_REQUEST['index'])), "</p>\n"; @@ -107,7 +202,7 @@ class IndexController extends BaseController { $tablespaces = $data->getTablespaces(); } - $misc->printTrail('table'); + $this->printTrail('table'); $misc->printTitle($lang['strcreateindex'], 'pg.index.create'); $misc->printMsg($msg); @@ -247,7 +342,7 @@ class IndexController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('index'); + $this->printTrail('index'); $misc->printTitle($lang['strdrop'], 'pg.index.drop'); echo "<p>", sprintf($lang['strconfdropindex'], $misc->printVal($_REQUEST['index'])), "</p>\n"; @@ -293,8 +388,8 @@ class IndexController extends BaseController { return $actions; }; - $misc->printTrail('table'); - $misc->printTabs('table', 'indexes'); + $this->printTrail('table'); + $this->printTabs('table', 'indexes'); $misc->printMsg($msg); $indexes = $data->getIndexes($_REQUEST['table']); @@ -370,9 +465,9 @@ class IndexController extends BaseController { ], ]; - echo $misc->printTable($indexes, $columns, $actions, 'indexes-indexes', $lang['strnoindexes'], $indPre); + echo $this->printTable($indexes, $columns, $actions, 'indexes-indexes', $lang['strnoindexes'], $indPre); - $misc->printNavLinks([ + $this->printNavLinks([ 'create' => [ 'attr' => [ 'href' => [ diff --git a/src/controllers/InfoController.php b/src/controllers/InfoController.php index 899f0d35..7598d21e 100644 --- a/src/controllers/InfoController.php +++ b/src/controllers/InfoController.php @@ -9,17 +9,17 @@ use \PHPPgAdmin\Decorators\Decorator; class InfoController extends BaseController { public $_name = 'InfoController'; -/** - * List all the information on the table - */ + /** + * List all the information on the table + */ public function doDefault($msg = '') { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('table'); - $misc->printTabs('table', 'info'); + $this->printTrail('table'); + $this->printTabs('table', 'info'); $misc->printMsg($msg); // common params for printVal @@ -82,7 +82,7 @@ class InfoController extends BaseController { ], ]; - echo $misc->printTable($referrers, $columns, $actions, 'info-referrers', $lang['strnodata']); + echo $this->printTable($referrers, $columns, $actions, 'info-referrers', $lang['strnodata']); } // Parent tables @@ -118,7 +118,7 @@ class InfoController extends BaseController { ], ]; - echo $misc->printTable($parents, $columns, $actions, 'info-parents', $lang['strnodata']); + echo $this->printTable($parents, $columns, $actions, 'info-parents', $lang['strnodata']); } // Child tables @@ -154,7 +154,7 @@ class InfoController extends BaseController { ], ]; - echo $misc->printTable($children, $columns, $actions, 'info-children', $lang['strnodata']); + echo $this->printTable($children, $columns, $actions, 'info-children', $lang['strnodata']); } @@ -343,4 +343,5 @@ class InfoController extends BaseController { } } } + } diff --git a/src/controllers/IntroController.php b/src/controllers/IntroController.php index 77143ee3..607bdb0e 100644 --- a/src/controllers/IntroController.php +++ b/src/controllers/IntroController.php @@ -8,6 +8,14 @@ namespace PHPPgAdmin\Controller; class IntroController extends BaseController { public $_name = 'IntroController'; + /* Constructor */ + function __construct(\Slim\Container $container) { + $this->misc = $container->get('misc'); + + $this->misc->setNoDBConnection(true); + parent::__construct($container); + + } /** * Intro screen * @@ -27,9 +35,9 @@ class IntroController extends BaseController { $misc->setNoDBConnection(true); - $intro_html = $misc->printTrail('root', false); + $intro_html = $this->printTrail('root', false); - $intro_html .= $misc->printTabs('root', 'intro', false); + $intro_html .= $this->printTabs('root', 'intro', false); $intro_html .= "<h1> $appName $appVersion (PHP " . phpversion() . ')</h1>'; @@ -83,4 +91,26 @@ class IntroController extends BaseController { echo $intro_html; } + + public function render() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + + $misc->setNoDBConnection(true); + $misc->printHeader($lang['strintro']); + $misc->printBody(); + + switch ($action) { + + default: + $this->doDefault(); + break; + } + + $misc->printFooter(); + + } }
\ No newline at end of file diff --git a/src/controllers/LangController.php b/src/controllers/LangController.php index f3b09c98..56a76b33 100644 --- a/src/controllers/LangController.php +++ b/src/controllers/LangController.php @@ -9,17 +9,39 @@ use \PHPPgAdmin\Decorators\Decorator; class LangController extends BaseController { public $_name = 'LangController'; -/** - * Show default list of languages in the database - */ + public function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + + $misc->printHeader($lang['strlanguages']); + $misc->printBody(); + + switch ($action) { + default: + $this->doDefault(); + break; + } + + $misc->printFooter(); + + } + + /** + * Show default list of languages in the database + */ public function doDefault($msg = '') { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('database'); - $misc->printTabs('database', 'languages'); + $this->printTrail('database'); + $this->printTabs('database', 'languages'); $misc->printMsg($msg); $languages = $data->getLanguages(); @@ -42,6 +64,28 @@ class LangController extends BaseController { $actions = []; - echo $misc->printTable($languages, $columns, $actions, 'languages-languages', $lang['strnolanguages']); + echo $this->printTable($languages, $columns, $actions, 'languages-languages', $lang['strnolanguages']); + } + + /** + * Generate XML for the browser tree. + */ + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $languages = $data->getLanguages(); + + $attrs = [ + 'text' => Decorator::field('lanname'), + 'icon' => 'Language', + ]; + + return $misc->printTree($languages, $attrs, 'languages'); + } + } diff --git a/src/controllers/LoginController.php b/src/controllers/LoginController.php new file mode 100644 index 00000000..55d02949 --- /dev/null +++ b/src/controllers/LoginController.php @@ -0,0 +1,139 @@ +<?php + +namespace PHPPgAdmin\Controller; + +/** + * Login controller class + */ +class LoginController extends BaseController { + + private $container = null; + private $_connection = null; + private $app = null; + private $data = null; + private $database = null; + private $server_id = null; + public $appLangFiles = []; + public $appThemes = []; + public $appName = ''; + public $appVersion = ''; + public $form = ''; + public $href = ''; + public $lang = []; + public $action = ''; + public $_name = 'LoginController'; + public $_title = 'strlogin'; + + /* Constructor */ + function __construct(\Slim\Container $container) { + $this->misc = $container->get('misc'); + + $this->misc->setNoDBConnection(true); + parent::__construct($container); + + } + + function doLoginForm($msg = '') { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $misc->setNoDBConnection(true); + + $login_html = $misc->printHeader($lang[$this->_title], null, false); + $login_html .= $misc->printBody(false); + $login_html .= $this->printTrail('root', false); + + if (!empty($_POST)) { + $vars = &$_POST; + } else { + $vars = &$_GET; + } + foreach ($_REQUEST as $key => $val) { + if (strpos($key, '?') !== FALSE) { + $namexploded = explode('?', $key); + $_REQUEST[$namexploded[1]] = htmlspecialchars($val); + } + } + + $server_info = $misc->getServerInfo($_REQUEST['server']); + $title = sprintf($lang['strlogintitle'], $server_info['desc']); + \PC::debug($title, 'title'); + $printTitle = $misc->printTitle($title, null, false); + \PC::debug($printTitle, 'printTitle'); + + $login_html .= $printTitle; + + if (isset($msg)) { + $login_html .= $misc->printMsg($msg, false); + } + + $login_html .= '<form id="login_form" method="post" name="login_form">'; + + $md5_server = md5($_REQUEST['server']); + // Pass request vars through form (is this a security risk???) + foreach ($vars as $key => $val) { + if (substr($key, 0, 5) == 'login') { + continue; + } + if (strpos($key, '?') !== FALSE) { + $key = explode('?', $key)[1]; + } + + $login_html .= '<input type="hidden" name="' . htmlspecialchars($key) . '" value="' . htmlspecialchars($val) . '" />' . "\n"; + } + + $login_html .= '<input type="hidden" name="loginServer" value="' . htmlspecialchars($_REQUEST['server']) . '" />'; + $login_html .= '<table class="navbar" border="0" cellpadding="5" cellspacing="3">'; + $login_html .= '<tr>'; + $login_html .= '<td>' . $lang['strusername'] . '</td>'; + $loginusername = isset($_POST['loginUsername']) ? htmlspecialchars($_POST['loginUsername']) : ''; + + $login_html .= '<td><input type="text" name="loginUsername" value="' . $loginusername . '" size="24" /></td>'; + $login_html .= '</tr>'; + $login_html .= '<tr>'; + $login_html .= '<td>' . $lang['strpassword'] . '</td>'; + $login_html .= '<td><input id="loginPassword" type="password" name="loginPassword_' . $md5_server . '" size="24" /></td>'; + $login_html .= '</tr>'; + $login_html .= '</table>'; + if (sizeof($conf['servers']) > 1) { + $checked = isset($_POST['loginShared']) ? 'checked="checked"' : ''; + $login_html .= '<p><input type="checkbox" id="loginShared" name="loginShared" ' . $checked . ' />'; + $login_html .= '<label for="loginShared">' . $lang['strtrycred'] . '</label></p>'; + } + $login_html .= '<p><input type="submit" name="loginSubmit" value="' . $lang['strlogin'] . '" /></p>'; + $login_html .= '</form>'; + + $login_html .= '<script type="text/javascript">'; + $login_html .= ' var uname = document.login_form.loginUsername;'; + $login_html .= ' var pword = document.login_form.loginPassword_' . $md5_server . ';'; + $login_html .= ' if (uname.value == "") {'; + $login_html .= ' uname.focus();'; + $login_html .= ' } else {'; + $login_html .= ' pword.focus();'; + $login_html .= ' }'; + $login_html .= '</script>'; + + // Output footer + $login_html .= $misc->printFooter(false); + return $login_html; + + } + + public function render() { + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + + $misc->setNoDBConnection(true); + + switch ($action) { + default: + echo $this->doLoginForm(); + break; + } + + } + +}
\ No newline at end of file diff --git a/src/controllers/MaterializedViewController.php b/src/controllers/MaterializedViewController.php new file mode 100644 index 00000000..34904c10 --- /dev/null +++ b/src/controllers/MaterializedViewController.php @@ -0,0 +1,833 @@ +<?php + +namespace PHPPgAdmin\Controller; +use \PHPPgAdmin\Decorators\Decorator; + +/** + * Base controller class + */ +class MaterializedViewController extends BaseController { + public $script = 'materialized_materialized_views.php'; + public $_name = 'MaterializedViewController'; + public $table_place = 'matviews-matviews'; + /** + * Ask for select parameters and perform select + */ + public function doSelectRows($confirm, $msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + if ($confirm) { + $this->printTrail('view'); + $this->printTabs('view', 'select'); + $misc->printMsg($msg); + + $attrs = $data->getTableAttributes($_REQUEST['view']); + + echo '<form action="/src/views/' . $this->script . '" method="post" id="selectform">'; + echo "\n"; + + if ($attrs->recordCount() > 0) { + // JavaScript for select all feature + echo "<script type=\"text/javascript\">\n"; + echo "//<![CDATA[\n"; + echo " function selectAll() {\n"; + echo " for (var i=0; i<document.getElementById('selectform').elements.length; i++) {\n"; + echo " var e = document.getElementById('selectform').elements[i];\n"; + echo " if (e.name.indexOf('show') == 0) { \n "; + echo " e.checked = document.getElementById('selectform').selectall.checked;\n"; + echo " }\n"; + echo " }\n"; + echo " }\n"; + echo "//]]>\n"; + echo "</script>\n"; + + echo "<table>\n"; + + // Output table header + echo "<tr><th class=\"data\">{$lang['strshow']}</th><th class=\"data\">{$lang['strcolumn']}</th>"; + echo "<th class=\"data\">{$lang['strtype']}</th><th class=\"data\">{$lang['stroperator']}</th>"; + echo "<th class=\"data\">{$lang['strvalue']}</th></tr>"; + + $i = 0; + while (!$attrs->EOF) { + $attrs->fields['attnotnull'] = $data->phpBool($attrs->fields['attnotnull']); + // Set up default value if there isn't one already + if (!isset($_REQUEST['values'][$attrs->fields['attname']])) { + $_REQUEST['values'][$attrs->fields['attname']] = null; + } + + if (!isset($_REQUEST['ops'][$attrs->fields['attname']])) { + $_REQUEST['ops'][$attrs->fields['attname']] = null; + } + + // Continue drawing row + $id = (($i % 2) == 0 ? '1' : '2'); + echo "<tr class=\"data{$id}\">\n"; + echo "<td style=\"white-space:nowrap;\">"; + echo "<input type=\"checkbox\" name=\"show[", htmlspecialchars($attrs->fields['attname']), "]\"", + isset($_REQUEST['show'][$attrs->fields['attname']]) ? ' checked="checked"' : '', " /></td>"; + echo "<td style=\"white-space:nowrap;\">", $misc->printVal($attrs->fields['attname']), "</td>"; + echo "<td style=\"white-space:nowrap;\">", $misc->printVal($data->formatType($attrs->fields['type'], $attrs->fields['atttypmod'])), "</td>"; + echo "<td style=\"white-space:nowrap;\">"; + echo "<select name=\"ops[{$attrs->fields['attname']}]\">\n"; + foreach (array_keys($data->selectOps) as $v) { + echo "<option value=\"", htmlspecialchars($v), "\"", ($v == $_REQUEST['ops'][$attrs->fields['attname']]) ? ' selected="selected"' : '', + ">", htmlspecialchars($v), "</option>\n"; + } + echo "</select></td>\n"; + echo "<td style=\"white-space:nowrap;\">", $data->printField("values[{$attrs->fields['attname']}]", + $_REQUEST['values'][$attrs->fields['attname']], $attrs->fields['type']), "</td>"; + echo "</tr>\n"; + $i++; + $attrs->moveNext(); + } + // Select all checkbox + echo "<tr><td colspan=\"5\"><input type=\"checkbox\" id=\"selectall\" name=\"selectall\" accesskey=\"a\" onclick=\"javascript:selectAll()\" /><label for=\"selectall\">{$lang['strselectallfields']}</label></td></tr>"; + echo "</table>\n"; + } else { + echo "<p>{$lang['strinvalidparam']}</p>\n"; + } + + echo "<p><input type=\"hidden\" name=\"action\" value=\"selectrows\" />\n"; + echo "<input type=\"hidden\" name=\"view\" value=\"", htmlspecialchars($_REQUEST['view']), "\" />\n"; + echo "<input type=\"hidden\" name=\"subject\" value=\"view\" />\n"; + echo $misc->form; + echo "<input type=\"submit\" name=\"select\" accesskey=\"r\" value=\"{$lang['strselect']}\" />\n"; + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; + echo "</form>\n"; + return; + } else { + if (!isset($_POST['show'])) { + $_POST['show'] = []; + } + + if (!isset($_POST['values'])) { + $_POST['values'] = []; + } + + if (!isset($_POST['nulls'])) { + $_POST['nulls'] = []; + } + + // Verify that they haven't supplied a value for unary operators + foreach ($_POST['ops'] as $k => $v) { + if ($data->selectOps[$v] == 'p' && $_POST['values'][$k] != '') { + $this->doSelectRows(true, $lang['strselectunary']); + return; + } + } + + if (sizeof($_POST['show']) == 0) { + return $this->doSelectRows(true, $lang['strselectneedscol']); + } else { + // Generate query SQL + $query = $data->getSelectSQL($_REQUEST['view'], array_keys($_POST['show']), $_POST['values'], $_POST['ops']); + + $_REQUEST['query'] = $query; + $_REQUEST['return'] = "schema"; + + $misc->setNoOutput(true); + + $display_controller = new DisplayController($this->getContainer()); + + return $display_controller->render(); + } + } + + } + + /** + * Show confirmation of drop and perform actual drop + */ + public function doDrop($confirm) { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + if (empty($_REQUEST['view']) && empty($_REQUEST['ma'])) { + $this->doDefault($lang['strspecifyviewtodrop']); + exit(); + } + + if ($confirm) { + $this->printTrail('view'); + $misc->printTitle($lang['strdrop'], 'pg.view.drop'); + + echo "<form action=\"/src/views/materialized_views.php\" method=\"post\">\n"; + + //If multi drop + if (isset($_REQUEST['ma'])) { + foreach ($_REQUEST['ma'] as $v) { + $a = unserialize(htmlspecialchars_decode($v, ENT_QUOTES)); + echo "<p>", sprintf($lang['strconfdropview'], $misc->printVal($a['view'])), "</p>\n"; + echo '<input type="hidden" name="view[]" value="', htmlspecialchars($a['view']), "\" />\n"; + } + } else { + echo "<p>", sprintf($lang['strconfdropview'], $misc->printVal($_REQUEST['view'])), "</p>\n"; + echo "<input type=\"hidden\" name=\"view\" value=\"", htmlspecialchars($_REQUEST['view']), "\" />\n"; + } + + echo "<input type=\"hidden\" name=\"action\" value=\"drop\" />\n"; + + echo $misc->form; + echo "<p><input type=\"checkbox\" id=\"cascade\" name=\"cascade\" /> <label for=\"cascade\">{$lang['strcascade']}</label></p>\n"; + echo "<input type=\"submit\" name=\"drop\" value=\"{$lang['strdrop']}\" />\n"; + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n"; + echo "</form>\n"; + } else { + if (is_array($_POST['view'])) { + $msg = ''; + $status = $data->beginTransaction(); + if ($status == 0) { + foreach ($_POST['view'] as $s) { + $status = $data->dropView($s, isset($_POST['cascade'])); + if ($status == 0) { + $msg .= sprintf('%s: %s<br />', htmlentities($s, ENT_QUOTES, 'UTF-8'), $lang['strviewdropped']); + } else { + $data->endTransaction(); + $this->doDefault(sprintf('%s%s: %s<br />', $msg, htmlentities($s, ENT_QUOTES, 'UTF-8'), $lang['strviewdroppedbad'])); + return; + } + } + } + if ($data->endTransaction() == 0) { + // Everything went fine, back to the Default page.... + $this->misc->setReloadBrowser(true); + $this->doDefault($msg); + } else { + $this->doDefault($lang['strviewdroppedbad']); + } + + } else { + $status = $data->dropView($_POST['view'], isset($_POST['cascade'])); + if ($status == 0) { + $this->misc->setReloadBrowser(true); + $this->doDefault($lang['strviewdropped']); + } else { + $this->doDefault($lang['strviewdroppedbad']); + } + + } + } + + } + + /** + * Sets up choices for table linkage, and which fields to select for the view we're creating + */ + public function doSetParamsCreate($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + // Check that they've chosen tables for the view definition + if (!isset($_POST['formTables'])) { + $this->doWizardCreate($lang['strviewneedsdef']); + } else { + // Initialise variables + if (!isset($_REQUEST['formView'])) { + $_REQUEST['formView'] = ''; + } + + if (!isset($_REQUEST['formComment'])) { + $_REQUEST['formComment'] = ''; + } + + $this->printTrail('schema'); + $misc->printTitle($lang['strcreateviewwiz'], 'pg.view.create'); + $misc->printMsg($msg); + + $tblCount = sizeof($_POST['formTables']); + //unserialize our schema/table information and store in arrSelTables + for ($i = 0; $i < $tblCount; $i++) { + $arrSelTables[] = unserialize($_POST['formTables'][$i]); + } + + $linkCount = $tblCount; + + //get linking keys + $rsLinkKeys = $data->getLinkingKeys($arrSelTables); + $linkCount = $rsLinkKeys->recordCount() > $tblCount ? $rsLinkKeys->recordCount() : $tblCount; + + $arrFields = []; //array that will hold all our table/field names + + //if we have schemas we need to specify the correct schema for each table we're retrieiving + //with getTableAttributes + $curSchema = $data->_schema; + for ($i = 0; $i < $tblCount; $i++) { + if ($data->_schema != $arrSelTables[$i]['schemaname']) { + $data->setSchema($arrSelTables[$i]['schemaname']); + } + + $attrs = $data->getTableAttributes($arrSelTables[$i]['tablename']); + while (!$attrs->EOF) { + $arrFields["{$arrSelTables[$i]['schemaname']}.{$arrSelTables[$i]['tablename']}.{$attrs->fields['attname']}"] = serialize([ + 'schemaname' => $arrSelTables[$i]['schemaname'], + 'tablename' => $arrSelTables[$i]['tablename'], + 'fieldname' => $attrs->fields['attname']] + ); + $attrs->moveNext(); + } + + $data->setSchema($curSchema); + } + asort($arrFields); + + echo "<form action=\"/src/views/materialized_views.php\" method=\"post\">\n"; + echo "<table>\n"; + echo "<tr><th class=\"data\">{$lang['strviewname']}</th></tr>"; + echo "<tr>\n<td class=\"data1\">\n"; + // View name + echo "<input name=\"formView\" value=\"", htmlspecialchars($_REQUEST['formView']), "\" size=\"32\" maxlength=\"{$data->_maxNameLen}\" />\n"; + echo "</td>\n</tr>\n"; + echo "<tr><th class=\"data\">{$lang['strcomment']}</th></tr>"; + echo "<tr>\n<td class=\"data1\">\n"; + // View comments + echo "<textarea name=\"formComment\" rows=\"3\" cols=\"32\">", + htmlspecialchars($_REQUEST['formComment']), "</textarea>\n"; + echo "</td>\n</tr>\n"; + echo "</table>\n"; + + // Output selector for fields to be retrieved from view + echo "<table>\n"; + echo "<tr><th class=\"data\">{$lang['strcolumns']}</th></tr>"; + echo "<tr>\n<td class=\"data1\">\n"; + echo \PHPPgAdmin\GUI::printCombo($arrFields, 'formFields[]', false, '', true); + echo "</td>\n</tr>"; + echo "<tr><td><input type=\"radio\" name=\"dblFldMeth\" id=\"dblFldMeth1\" value=\"rename\" /><label for=\"dblFldMeth1\">{$lang['strrenamedupfields']}</label>"; + echo "<br /><input type=\"radio\" name=\"dblFldMeth\" id=\"dblFldMeth2\" value=\"drop\" /><label for=\"dblFldMeth2\">{$lang['strdropdupfields']}</label>"; + echo "<br /><input type=\"radio\" name=\"dblFldMeth\" id=\"dblFldMeth3\" value=\"\" checked=\"checked\" /><label for=\"dblFldMeth3\">{$lang['strerrordupfields']}</label></td></tr></table><br />"; + + // Output the Linking keys combo boxes + echo "<table>\n"; + echo "<tr><th class=\"data\">{$lang['strviewlink']}</th></tr>"; + $rowClass = 'data1'; + for ($i = 0; $i < $linkCount; $i++) { + // Initialise variables + if (!isset($formLink[$i]['operator'])) { + $formLink[$i]['operator'] = 'INNER JOIN'; + } + + echo "<tr>\n<td class=\"$rowClass\">\n"; + + if (!$rsLinkKeys->EOF) { + $curLeftLink = htmlspecialchars(serialize(['schemaname' => $rsLinkKeys->fields['p_schema'], 'tablename' => $rsLinkKeys->fields['p_table'], 'fieldname' => $rsLinkKeys->fields['p_field']])); + $curRightLink = htmlspecialchars(serialize(['schemaname' => $rsLinkKeys->fields['f_schema'], 'tablename' => $rsLinkKeys->fields['f_table'], 'fieldname' => $rsLinkKeys->fields['f_field']])); + $rsLinkKeys->moveNext(); + } else { + $curLeftLink = ''; + $curRightLink = ''; + } + + echo \PHPPgAdmin\GUI::printCombo($arrFields, "formLink[$i][leftlink]", true, $curLeftLink, false); + echo \PHPPgAdmin\GUI::printCombo($data->joinOps, "formLink[$i][operator]", true, $formLink[$i]['operator']); + echo \PHPPgAdmin\GUI::printCombo($arrFields, "formLink[$i][rightlink]", true, $curRightLink, false); + echo "</td>\n</tr>\n"; + $rowClass = $rowClass == 'data1' ? 'data2' : 'data1'; + } + echo "</table>\n<br />\n"; + + // Build list of available operators (infix only) + $arrOperators = []; + foreach ($data->selectOps as $k => $v) { + if ($v == 'i') { + $arrOperators[$k] = $k; + } + + } + + // Output additional conditions, note that this portion of the wizard treats the right hand side as literal values + //(not as database objects) so field names will be treated as strings, use the above linking keys section to perform joins + echo "<table>\n"; + echo "<tr><th class=\"data\">{$lang['strviewconditions']}</th></tr>"; + $rowClass = 'data1'; + for ($i = 0; $i < $linkCount; $i++) { + echo "<tr>\n<td class=\"$rowClass\">\n"; + echo \PHPPgAdmin\GUI::printCombo($arrFields, "formCondition[$i][field]"); + echo \PHPPgAdmin\GUI::printCombo($arrOperators, "formCondition[$i][operator]", false, false); + echo "<input type=\"text\" name=\"formCondition[$i][txt]\" />\n"; + echo "</td>\n</tr>\n"; + $rowClass = $rowClass == 'data1' ? 'data2' : 'data1'; + } + echo "</table>\n"; + echo "<p><input type=\"hidden\" name=\"action\" value=\"save_create_wiz\" />\n"; + + foreach ($arrSelTables as $curTable) { + echo "<input type=\"hidden\" name=\"formTables[]\" value=\"" . htmlspecialchars(serialize($curTable)) . "\" />\n"; + } + + echo $misc->form; + echo "<input type=\"submit\" value=\"{$lang['strcreate']}\" />\n"; + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; + echo "</form>\n"; + } + } + + /** + * Display a wizard where they can enter a new view + */ + public function doWizardCreate($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $tables = $data->getTables(true); + + $this->printTrail('schema'); + $misc->printTitle($lang['strcreateviewwiz'], 'pg.view.create'); + $misc->printMsg($msg); + + echo "<form action=\"/src/views/materialized_views.php\" method=\"post\">\n"; + echo "<table>\n"; + echo "<tr><th class=\"data\">{$lang['strtables']}</th></tr>"; + echo "<tr>\n<td class=\"data1\">\n"; + + $arrTables = []; + while (!$tables->EOF) { + $arrTmp = []; + $arrTmp['schemaname'] = $tables->fields['nspname']; + $arrTmp['tablename'] = $tables->fields['relname']; + $arrTables[$tables->fields['nspname'] . '.' . $tables->fields['relname']] = serialize($arrTmp); + $tables->moveNext(); + } + echo \PHPPgAdmin\GUI::printCombo($arrTables, 'formTables[]', false, '', true); + + echo "</td>\n</tr>\n"; + echo "</table>\n"; + echo "<p><input type=\"hidden\" name=\"action\" value=\"set_params_create\" />\n"; + echo $misc->form; + echo "<input type=\"submit\" value=\"{$lang['strnext']}\" />\n"; + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; + echo "</form>\n"; + } + + /** + * Displays a screen where they can enter a new view + */ + public function doCreate($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + if (!isset($_REQUEST['formView'])) { + $_REQUEST['formView'] = ''; + } + + if (!isset($_REQUEST['formDefinition'])) { + if (isset($_SESSION['sqlquery'])) { + $_REQUEST['formDefinition'] = $_SESSION['sqlquery']; + } else { + $_REQUEST['formDefinition'] = 'SELECT '; + } + + } + if (!isset($_REQUEST['formComment'])) { + $_REQUEST['formComment'] = ''; + } + + $this->printTrail('schema'); + $misc->printTitle($lang['strcreateview'], 'pg.view.create'); + $misc->printMsg($msg); + + echo "<form action=\"/src/views/materialized_views.php\" method=\"post\">\n"; + echo "<table style=\"width: 100%\">\n"; + echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strname']}</th>\n"; + echo "\t<td class=\"data1\"><input name=\"formView\" size=\"32\" maxlength=\"{$data->_maxNameLen}\" value=\"", + htmlspecialchars($_REQUEST['formView']), "\" /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strdefinition']}</th>\n"; + echo "\t<td class=\"data1\"><textarea style=\"width:100%;\" rows=\"10\" cols=\"50\" name=\"formDefinition\">", + htmlspecialchars($_REQUEST['formDefinition']), "</textarea></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strcomment']}</th>\n"; + echo "\t\t<td class=\"data1\"><textarea name=\"formComment\" rows=\"3\" cols=\"32\">", + htmlspecialchars($_REQUEST['formComment']), "</textarea></td>\n\t</tr>\n"; + echo "</table>\n"; + echo "<p><input type=\"hidden\" name=\"action\" value=\"save_create\" />\n"; + echo $misc->form; + echo "<input type=\"submit\" value=\"{$lang['strcreate']}\" />\n"; + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; + echo "</form>\n"; + } + + /** + * Actually creates the new view in the database + */ + public function doSaveCreate() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + // Check that they've given a name and a definition + if ($_POST['formView'] == '') { + $this->doCreate($lang['strviewneedsname']); + } elseif ($_POST['formDefinition'] == '') { + $this->doCreate($lang['strviewneedsdef']); + } else { + $status = $data->createView($_POST['formView'], $_POST['formDefinition'], false, $_POST['formComment']); + if ($status == 0) { + $this->misc->setReloadBrowser(true); + $this->doDefault($lang['strviewcreated']); + } else { + $this->doCreate($lang['strviewcreatedbad']); + } + + } + } + + /** + * Actually creates the new wizard view in the database + */ + public function doSaveCreateWiz() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + // Check that they've given a name and fields they want to select + + if (!strlen($_POST['formView'])) { + $this->doSetParamsCreate($lang['strviewneedsname']); + } else if (!isset($_POST['formFields']) || !count($_POST['formFields'])) { + $this->doSetParamsCreate($lang['strviewneedsfields']); + } else { + $selFields = ''; + + if (!empty($_POST['dblFldMeth'])) { + $tmpHsh = []; + } + + foreach ($_POST['formFields'] as $curField) { + $arrTmp = unserialize($curField); + $data->fieldArrayClean($arrTmp); + if (!empty($_POST['dblFldMeth'])) { + // doublon control + if (empty($tmpHsh[$arrTmp['fieldname']])) { + // field does not exist + $selFields .= "\"{$arrTmp['schemaname']}\".\"{$arrTmp['tablename']}\".\"{$arrTmp['fieldname']}\", "; + $tmpHsh[$arrTmp['fieldname']] = 1; + } else if ($_POST['dblFldMeth'] == 'rename') { + // field exist and must be renamed + $tmpHsh[$arrTmp['fieldname']]++; + $selFields .= "\"{$arrTmp['schemaname']}\".\"{$arrTmp['tablename']}\".\"{$arrTmp['fieldname']}\" AS \"{$arrTmp['schemaname']}_{$arrTmp['tablename']}_{$arrTmp['fieldname']}{$tmpHsh[$arrTmp['fieldname']]}\", "; + } + /* field already exist, just ignore this one */ + } else { + // no doublon control + $selFields .= "\"{$arrTmp['schemaname']}\".\"{$arrTmp['tablename']}\".\"{$arrTmp['fieldname']}\", "; + } + } + + $selFields = substr($selFields, 0, -2); + unset($arrTmp, $tmpHsh); + $linkFields = ''; + + // If we have links, out put the JOIN ... ON statements + if (is_array($_POST['formLink'])) { + // Filter out invalid/blank entries for our links + $arrLinks = []; + foreach ($_POST['formLink'] as $curLink) { + if (strlen($curLink['leftlink']) && strlen($curLink['rightlink']) && strlen($curLink['operator'])) { + $arrLinks[] = $curLink; + } + } + // We must perform some magic to make sure that we have a valid join order + $count = sizeof($arrLinks); + $arrJoined = []; + $arrUsedTbls = []; + + // If we have at least one join condition, output it + if ($count > 0) { + $j = 0; + while ($j < $count) { + foreach ($arrLinks as $curLink) { + + $arrLeftLink = unserialize($curLink['leftlink']); + $arrRightLink = unserialize($curLink['rightlink']); + $data->fieldArrayClean($arrLeftLink); + $data->fieldArrayClean($arrRightLink); + + $tbl1 = "\"{$arrLeftLink['schemaname']}\".\"{$arrLeftLink['tablename']}\""; + $tbl2 = "\"{$arrRightLink['schemaname']}\".\"{$arrRightLink['tablename']}\""; + + if ((!in_array($curLink, $arrJoined) && in_array($tbl1, $arrUsedTbls)) || !count($arrJoined)) { + + // Make sure for multi-column foreign keys that we use a table alias tables joined to more than once + // This can (and should be) more optimized for multi-column foreign keys + $adj_tbl2 = in_array($tbl2, $arrUsedTbls) ? "$tbl2 AS alias_ppa_" . mktime() : $tbl2; + + $linkFields .= strlen($linkFields) ? "{$curLink['operator']} $adj_tbl2 ON (\"{$arrLeftLink['schemaname']}\".\"{$arrLeftLink['tablename']}\".\"{$arrLeftLink['fieldname']}\" = \"{$arrRightLink['schemaname']}\".\"{$arrRightLink['tablename']}\".\"{$arrRightLink['fieldname']}\") " + : "$tbl1 {$curLink['operator']} $adj_tbl2 ON (\"{$arrLeftLink['schemaname']}\".\"{$arrLeftLink['tablename']}\".\"{$arrLeftLink['fieldname']}\" = \"{$arrRightLink['schemaname']}\".\"{$arrRightLink['tablename']}\".\"{$arrRightLink['fieldname']}\") "; + + $arrJoined[] = $curLink; + if (!in_array($tbl1, $arrUsedTbls)) { + $arrUsedTbls[] = $tbl1; + } + + if (!in_array($tbl2, $arrUsedTbls)) { + $arrUsedTbls[] = $tbl2; + } + + } + } + $j++; + } + } + } + + //if linkfields has no length then either _POST['formLink'] was not set, or there were no join conditions + //just select from all seleted tables - a cartesian join do a + if (!strlen($linkFields)) { + foreach ($_POST['formTables'] as $curTable) { + $arrTmp = unserialize($curTable); + $data->fieldArrayClean($arrTmp); + $linkFields .= strlen($linkFields) ? ", \"{$arrTmp['schemaname']}\".\"{$arrTmp['tablename']}\"" : "\"{$arrTmp['schemaname']}\".\"{$arrTmp['tablename']}\""; + } + } + + $addConditions = ''; + if (is_array($_POST['formCondition'])) { + foreach ($_POST['formCondition'] as $curCondition) { + if (strlen($curCondition['field']) && strlen($curCondition['txt'])) { + $arrTmp = unserialize($curCondition['field']); + $data->fieldArrayClean($arrTmp); + $addConditions .= strlen($addConditions) ? " AND \"{$arrTmp['schemaname']}\".\"{$arrTmp['tablename']}\".\"{$arrTmp['fieldname']}\" {$curCondition['operator']} '{$curCondition['txt']}' " + : " \"{$arrTmp['schemaname']}\".\"{$arrTmp['tablename']}\".\"{$arrTmp['fieldname']}\" {$curCondition['operator']} '{$curCondition['txt']}' "; + } + } + } + + $viewQuery = "SELECT $selFields FROM $linkFields "; + + //add where from additional conditions + if (strlen($addConditions)) { + $viewQuery .= ' WHERE ' . $addConditions; + } + + $status = $data->createView($_POST['formView'], $viewQuery, false, $_POST['formComment']); + if ($status == 0) { + $this->misc->setReloadBrowser(true); + $this->doDefault($lang['strviewcreated']); + } else { + $this->doSetParamsCreate($lang['strviewcreatedbad']); + } + + } + } + + /** + * Show default list of views in the database + */ + public function doDefault($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $this->printTrail('schema'); + $this->printTabs('schema', 'matviews'); + $misc->printMsg($msg); + + //$matviews = $data->getViews(); + $matviews = $data->getMaterializedViews(); + + $columns = [ + 'matview' => [ + 'title' => 'M ' . $lang['strview'], + 'field' => Decorator::field('relname'), + 'url' => "/redirect/matview?{$misc->href}&", + 'vars' => ['matview' => 'relname'], + ], + 'owner' => [ + 'title' => $lang['strowner'], + 'field' => Decorator::field('relowner'), + ], + 'actions' => [ + 'title' => $lang['stractions'], + ], + 'comment' => [ + 'title' => $lang['strcomment'], + 'field' => Decorator::field('relcomment'), + ], + ]; + + $actions = [ + 'multiactions' => [ + 'keycols' => ['matview' => 'relname'], + 'url' => 'materialized_materialized_views.php', + ], + 'browse' => [ + 'content' => $lang['strbrowse'], + 'attr' => [ + 'href' => [ + 'url' => 'display.php', + 'urlvars' => [ + 'action' => 'confselectrows', + 'subject' => 'matview', + 'return' => 'schema', + 'matview' => Decorator::field('relname'), + ], + ], + ], + ], + 'select' => [ + 'content' => $lang['strselect'], + 'attr' => [ + 'href' => [ + 'url' => 'materialized_views.php', + 'urlvars' => [ + 'action' => 'confselectrows', + 'matview' => Decorator::field('relname'), + ], + ], + ], + ], + + // Insert is possible if the relevant rule for the view has been created. + // 'insert' => array( + // 'title' => $lang['strinsert'], + // 'url' => "materialized_views.php?action=confinsertrow&{$misc->href}&", + // 'vars' => array('view' => 'relname'), + // ), + + 'alter' => [ + 'content' => $lang['stralter'], + 'attr' => [ + 'href' => [ + 'url' => 'viewproperties.php', + 'urlvars' => [ + 'action' => 'confirm_alter', + 'matview' => Decorator::field('relname'), + ], + ], + ], + ], + 'drop' => [ + 'multiaction' => 'confirm_drop', + 'content' => $lang['strdrop'], + 'attr' => [ + 'href' => [ + 'url' => 'materialized_views.php', + 'urlvars' => [ + 'action' => 'confirm_drop', + 'matview' => Decorator::field('relname'), + ], + ], + ], + ], + ]; + + echo $this->printTable($matviews, $columns, $actions, $this->table_place, $lang['strnoviews']); + + $navlinks = [ + 'create' => [ + 'attr' => [ + 'href' => [ + 'url' => 'materialized_views.php', + 'urlvars' => [ + 'action' => 'create', + 'server' => $_REQUEST['server'], + 'database' => $_REQUEST['database'], + 'schema' => $_REQUEST['schema'], + ], + ], + ], + 'content' => $lang['strcreateview'], + ], + 'createwiz' => [ + 'attr' => [ + 'href' => [ + 'url' => 'materialized_views.php', + 'urlvars' => [ + 'action' => 'wiz_create', + 'server' => $_REQUEST['server'], + 'database' => $_REQUEST['database'], + 'schema' => $_REQUEST['schema'], + ], + ], + ], + 'content' => $lang['strcreateviewwiz'], + ], + ]; + $this->printNavLinks($navlinks, $this->table_place, get_defined_vars()); + + } + + public function render() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $misc->printHeader('M ' . $lang['strviews']); + $misc->printBody(); + + switch ($action) { + case 'selectrows': + if (!isset($_REQUEST['cancel'])) { + $this->doSelectRows(false); + } else { + $this->doDefault(); + } + + break; + case 'confselectrows': + $this->doSelectRows(true); + break; + case 'save_create_wiz': + if (isset($_REQUEST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveCreateWiz(); + } + + break; + case 'wiz_create': + doWizardCreate(); + break; + case 'set_params_create': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doSetParamsCreate(); + } + + break; + case 'save_create': + if (isset($_REQUEST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveCreate(); + } + + break; + case 'create': + doCreate(); + break; + case 'drop': + if (isset($_POST['drop'])) { + $this->doDrop(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + default: + $this->doDefault(); + break; + } + + $misc->printFooter(); + + } +} diff --git a/src/controllers/OpClassesController.php b/src/controllers/OpClassesController.php index 18a0a066..550c7a31 100644 --- a/src/controllers/OpClassesController.php +++ b/src/controllers/OpClassesController.php @@ -9,17 +9,64 @@ use \PHPPgAdmin\Decorators\Decorator; class OpClassesController extends BaseController { public $_name = 'OpClassesController'; + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + + $misc->printHeader($lang['stropclasses']); + $misc->printBody(); + + switch ($action) { + default: + $this->doDefault(); + break; + } + + $misc->printFooter(); + + } + /** - * Show default list of opclasss in the database + * Generate XML for the browser tree. */ + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $opclasses = $data->getOpClasses(); + + // OpClass prototype: "op_class/access_method" + $proto = Decorator::concat(Decorator::field('opcname'), '/', Decorator::field('amname')); + + $attrs = [ + 'text' => $proto, + 'icon' => 'OperatorClass', + 'toolTip' => Decorator::field('opccomment'), + ]; + + return $misc->printTree($opclasses, $attrs, 'opclasses'); + } + + /** + * Show default list of opclasss in the database + */ public function doDefault($msg = '') { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('schema'); - $misc->printTabs('schema', 'opclasses'); + $this->printTrail('schema'); + $this->printTabs('schema', 'opclasses'); $misc->printMsg($msg); $opclasses = $data->getOpClasses(); @@ -50,7 +97,7 @@ class OpClassesController extends BaseController { $actions = []; - echo $misc->printTable($opclasses, $columns, $actions, 'opclasses-opclasses', $lang['strnoopclasses']); + echo $this->printTable($opclasses, $columns, $actions, 'opclasses-opclasses', $lang['strnoopclasses']); } } diff --git a/src/controllers/OperatorController.php b/src/controllers/OperatorController.php index 2791d945..ff4b6323 100644 --- a/src/controllers/OperatorController.php +++ b/src/controllers/OperatorController.php @@ -9,16 +9,98 @@ use \PHPPgAdmin\Decorators\Decorator; class OperatorController extends BaseController { public $_name = 'OperatorController'; -/** - * Show read only properties for an operator - */ + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + + $misc->printHeader($lang['stroperators']); + $misc->printBody(); + + switch ($action) { + case 'save_create': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveCreate(); + } + + break; + case 'create': + doCreate(); + break; + case 'drop': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doDrop(false); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + case 'properties': + $this->doProperties(); + break; + default: + $this->doDefault(); + break; + } + + $misc->printFooter(); + + } + + /** + * Generate XML for the browser tree. + */ + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $operators = $data->getOperators(); + + // Operator prototype: "type operator type" + $proto = Decorator::concat(Decorator::field('oprleftname'), ' ', Decorator::field('oprname'), ' ', Decorator::field('oprrightname')); + + $reqvars = $misc->getRequestVars('operator'); + + $attrs = [ + 'text' => $proto, + 'icon' => 'Operator', + 'toolTip' => Decorator::field('oprcomment'), + 'action' => Decorator::actionurl('operators.php', + $reqvars, + [ + 'action' => 'properties', + 'operator' => $proto, + 'operator_oid' => Decorator::field('oid'), + ] + ), + ]; + + return $misc->printTree($operators, $attrs, 'operators'); + } + + /** + * Show read only properties for an operator + */ public function doProperties($msg = '') { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('operator'); + $this->printTrail('operator'); $misc->printTitle($lang['strproperties'], 'pg.operator'); $misc->printMsg($msg); @@ -62,7 +144,7 @@ class OperatorController extends BaseController { } echo "</table>\n"; - $misc->printNavLinks([ + $this->printNavLinks([ 'showall' => [ 'attr' => [ 'href' => [ @@ -93,7 +175,7 @@ class OperatorController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('operator'); + $this->printTrail('operator'); $misc->printTitle($lang['strdrop'], 'pg.operator.drop'); echo "<p>", sprintf($lang['strconfdropoperator'], $misc->printVal($_REQUEST['operator'])), "</p>\n"; @@ -128,8 +210,8 @@ class OperatorController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('schema'); - $misc->printTabs('schema', 'operators'); + $this->printTrail('schema'); + $this->printTabs('schema', 'operators'); $misc->printMsg($msg); $operators = $data->getOperators(); @@ -181,7 +263,7 @@ class OperatorController extends BaseController { ], ]; - echo $misc->printTable($operators, $columns, $actions, 'operators-operators', $lang['strnooperators']); + echo $this->printTable($operators, $columns, $actions, 'operators-operators', $lang['strnooperators']); // TODO operators.php action=create $lang['strcreateoperator'] } diff --git a/src/controllers/PrivilegeController.php b/src/controllers/PrivilegeController.php new file mode 100644 index 00000000..f5ec29b8 --- /dev/null +++ b/src/controllers/PrivilegeController.php @@ -0,0 +1,405 @@ +<?php + +namespace PHPPgAdmin\Controller; + +/** + * PrivilegeController controller class + */ +class PrivilegeController extends BaseController { + public $_name = 'PrivilegeController'; + public $table_place = 'privileges-privileges'; + + function render() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + $data = $misc->getDatabaseAccessor(); + + $misc->printHeader($lang['strprivileges']); + $misc->printBody(); + + switch ($action) { + case 'save': + if (isset($_REQUEST['cancel'])) { + $this->doDefault(); + } else { + $this->doAlter(false, $_REQUEST['mode']); + } + + break; + case 'alter': + $this->doAlter(true, $_REQUEST['mode']); + break; + default: + $this->doDefault(); + break; + } + + $misc->printFooter(); + } + /** + * Grant permissions on an object to a user + * @param $confirm To show entry screen + * @param $mode 'grant' or 'revoke' + * @param $msg (optional) A message to show + */ + function doAlter($confirm, $mode, $msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + $data = $misc->getDatabaseAccessor(); + + if (!isset($_REQUEST['username'])) { + $_REQUEST['username'] = []; + } + + if (!isset($_REQUEST['groupname'])) { + $_REQUEST['groupname'] = []; + } + + if (!isset($_REQUEST['privilege'])) { + $_REQUEST['privilege'] = []; + } + + if ($confirm) { + // Get users from the database + $users = $data->getUsers(); + // Get groups from the database + $groups = $data->getGroups(); + + $misc->printTrail($_REQUEST['subject']); + + switch ($mode) { + case 'grant': + $misc->printTitle($lang['strgrant'], 'pg.privilege.grant'); + break; + case 'revoke': + $misc->printTitle($lang['strrevoke'], 'pg.privilege.revoke'); + break; + } + $misc->printMsg($msg); + + echo "<form action=\"/src/views/privileges.php\" method=\"post\">\n"; + echo "<table>\n"; + echo "<tr><th class=\"data left\">{$lang['strusers']}</th>\n"; + echo "<td class=\"data1\"><select name=\"username[]\" multiple=\"multiple\" size=\"", min(6, $users->recordCount()), "\">\n"; + while (!$users->EOF) { + $uname = htmlspecialchars($users->fields['usename']); + echo "<option value=\"{$uname}\"", + in_array($users->fields['usename'], $_REQUEST['username']) ? ' selected="selected"' : '', ">{$uname}</option>\n"; + $users->moveNext(); + } + echo "</select></td></tr>\n"; + echo "<tr><th class=\"data left\">{$lang['strgroups']}</th>\n"; + echo "<td class=\"data1\">\n"; + echo "<input type=\"checkbox\" id=\"public\" name=\"public\"", (isset($_REQUEST['public']) ? ' checked="checked"' : ''), " /><label for=\"public\">PUBLIC</label>\n"; + // Only show groups if there are groups! + if ($groups->recordCount() > 0) { + echo "<br /><select name=\"groupname[]\" multiple=\"multiple\" size=\"", min(6, $groups->recordCount()), "\">\n"; + while (!$groups->EOF) { + $gname = htmlspecialchars($groups->fields['groname']); + echo "<option value=\"{$gname}\"", + in_array($groups->fields['groname'], $_REQUEST['groupname']) ? ' selected="selected"' : '', ">{$gname}</option>\n"; + $groups->moveNext(); + } + echo "</select>\n"; + } + echo "</td></tr>\n"; + echo "<tr><th class=\"data left required\">{$lang['strprivileges']}</th>\n"; + echo "<td class=\"data1\">\n"; + foreach ($data->privlist[$_REQUEST['subject']] as $v) { + $v = htmlspecialchars($v); + echo "<input type=\"checkbox\" id=\"privilege[$v]\" name=\"privilege[$v]\"", + isset($_REQUEST['privilege'][$v]) ? ' checked="checked"' : '', " /><label for=\"privilege[$v]\">{$v}</label><br />\n"; + } + echo "</td></tr>\n"; + // Grant option + if ($data->hasGrantOption()) { + echo "<tr><th class=\"data left\">{$lang['stroptions']}</th>\n"; + echo "<td class=\"data1\">\n"; + if ($mode == 'grant') { + echo "<input type=\"checkbox\" id=\"grantoption\" name=\"grantoption\"", + isset($_REQUEST['grantoption']) ? ' checked="checked"' : '', " /><label for=\"grantoption\">GRANT OPTION</label>\n"; + } elseif ($mode == 'revoke') { + echo "<input type=\"checkbox\" id=\"grantoption\" name=\"grantoption\"", + isset($_REQUEST['grantoption']) ? ' checked="checked"' : '', " /><label for=\"grantoption\">GRANT OPTION FOR</label><br />\n"; + echo "<input type=\"checkbox\" id=\"cascade\" name=\"cascade\"", + isset($_REQUEST['cascade']) ? ' checked="checked"' : '', " /><label for=\"cascade\">CASCADE</label><br />\n"; + } + echo "</td></tr>\n"; + } + echo "</table>\n"; + + echo "<p><input type=\"hidden\" name=\"action\" value=\"save\" />\n"; + echo "<input type=\"hidden\" name=\"mode\" value=\"", htmlspecialchars($mode), "\" />\n"; + echo "<input type=\"hidden\" name=\"subject\" value=\"", htmlspecialchars($_REQUEST['subject']), "\" />\n"; + if (isset($_REQUEST[$_REQUEST['subject'] . '_oid'])) { + echo "<input type=\"hidden\" name=\"", htmlspecialchars($_REQUEST['subject'] . '_oid'), + "\" value=\"", htmlspecialchars($_REQUEST[$_REQUEST['subject'] . '_oid']), "\" />\n"; + } + + echo "<input type=\"hidden\" name=\"", htmlspecialchars($_REQUEST['subject']), + "\" value=\"", htmlspecialchars($_REQUEST[$_REQUEST['subject']]), "\" />\n"; + if ($_REQUEST['subject'] == 'column') { + echo "<input type=\"hidden\" name=\"table\" value=\"", + htmlspecialchars($_REQUEST['table']), "\" />\n"; + } + + echo $misc->form; + if ($mode == 'grant') { + echo "<input type=\"submit\" name=\"grant\" value=\"{$lang['strgrant']}\" />\n"; + } elseif ($mode == 'revoke') { + echo "<input type=\"submit\" name=\"revoke\" value=\"{$lang['strrevoke']}\" />\n"; + } + + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>"; + echo "</form>\n"; + } else { + // Determine whether object should be ref'd by name or oid. + if (isset($_REQUEST[$_REQUEST['subject'] . '_oid'])) { + $object = $_REQUEST[$_REQUEST['subject'] . '_oid']; + } else { + $object = $_REQUEST[$_REQUEST['subject']]; + } + + if (isset($_REQUEST['table'])) { + $table = $_REQUEST['table']; + } else { + $table = null; + } + + $status = $data->setPrivileges(($mode == 'grant') ? 'GRANT' : 'REVOKE', $_REQUEST['subject'], $object, + isset($_REQUEST['public']), $_REQUEST['username'], $_REQUEST['groupname'], array_keys($_REQUEST['privilege']), + isset($_REQUEST['grantoption']), isset($_REQUEST['cascade']), $table); + + if ($status == 0) { + doDefault($lang['strgranted']); + } elseif ($status == -3 || $status == -4) { + doAlter(true, $_REQUEST['mode'], $lang['strgrantbad']); + } else { + doAlter(true, $_REQUEST['mode'], $lang['strgrantfailed']); + } + + } + } + + /** + * Show permissions on a database, namespace, relation, language or function + */ + function doDefault($msg = '') { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + $data = $misc->getDatabaseAccessor(); + $database = $misc->getDatabase(); + + $misc->printTrail($_REQUEST['subject']); + + # @@@FIXME: This switch is just a temporary solution, + # need a better way, maybe every type of object should + # have a tab bar??? + switch ($_REQUEST['subject']) { + case 'server': + case 'database': + case 'schema': + case 'table': + case 'column': + case 'view': + $misc->printTabs($_REQUEST['subject'], 'privileges'); + break; + default: + $misc->printTitle($lang['strprivileges'], 'pg.privilege'); + } + $misc->printMsg($msg); + + // Determine whether object should be ref'd by name or oid. + if (isset($_REQUEST[$_REQUEST['subject'] . '_oid'])) { + $object = $_REQUEST[$_REQUEST['subject'] . '_oid']; + } else { + $object = $_REQUEST[$_REQUEST['subject']]; + } + + // Get the privileges on the object, given its type + if ($_REQUEST['subject'] == 'column') { + $privileges = $data->getPrivileges($object, 'column', $_REQUEST['table']); + } else { + $privileges = $data->getPrivileges($object, $_REQUEST['subject']); + } + + if (sizeof($privileges) > 0) { + echo "<table>\n"; + if ($data->hasRoles()) { + echo "<tr><th class=\"data\">{$lang['strrole']}</th>"; + } else { + echo "<tr><th class=\"data\">{$lang['strtype']}</th><th class=\"data\">{$lang['struser']}/{$lang['strgroup']}</th>"; + } + + foreach ($data->privlist[$_REQUEST['subject']] as $v2) { + // Skip over ALL PRIVILEGES + if ($v2 == 'ALL PRIVILEGES') { + continue; + } + + echo "<th class=\"data\">{$v2}</th>\n"; + } + if ($data->hasGrantOption()) { + echo "<th class=\"data\">{$lang['strgrantor']}</th>"; + } + echo "</tr>\n"; + + // Loop over privileges, outputting them + $i = 0; + foreach ($privileges as $v) { + $id = (($i % 2) == 0 ? '1' : '2'); + echo "<tr class=\"data{$id}\">\n"; + if (!$data->hasRoles()) { + echo "<td>", $misc->printVal($v[0]), "</td>\n"; + } + + echo "<td>", $misc->printVal($v[1]), "</td>\n"; + foreach ($data->privlist[$_REQUEST['subject']] as $v2) { + // Skip over ALL PRIVILEGES + if ($v2 == 'ALL PRIVILEGES') { + continue; + } + + echo "<td>"; + if (in_array($v2, $v[2])) { + echo $lang['stryes']; + } else { + echo $lang['strno']; + } + + // If we have grant option for this, end mark + if ($data->hasGrantOption() && in_array($v2, $v[4])) { + echo $lang['strasterisk']; + } + + echo "</td>\n"; + } + if ($data->hasGrantOption()) { + echo "<td>", $misc->printVal($v[3]), "</td>\n"; + } + echo "</tr>\n"; + $i++; + } + + echo "</table>"; + } else { + echo "<p>{$lang['strnoprivileges']}</p>\n"; + } + + // Links for granting to a user or group + switch ($_REQUEST['subject']) { + case 'table': + case 'view': + case 'sequence': + case 'function': + case 'tablespace': + $alllabel = "showall{$_REQUEST['subject']}s"; + $allurl = "{$_REQUEST['subject']}s.php"; + $alltxt = $lang["strshowall{$_REQUEST['subject']}s"]; + break; + case 'schema': + $alllabel = "showallschemas"; + $allurl = "schemas.php"; + $alltxt = $lang["strshowallschemas"]; + break; + case 'database': + $alllabel = "showalldatabases"; + $allurl = 'all_db.php'; + $alltxt = $lang['strshowalldatabases']; + break; + } + + $subject = $_REQUEST['subject']; + $object = $_REQUEST[$_REQUEST['subject']]; + + if ($_REQUEST['subject'] == 'function') { + $objectoid = $_REQUEST[$_REQUEST['subject'] . '_oid']; + $urlvars = [ + 'action' => 'alter', + 'server' => $_REQUEST['server'], + 'database' => $_REQUEST['database'], + 'schema' => $_REQUEST['schema'], + $subject => $object, + "{$subject}_oid" => $objectoid, + 'subject' => $subject, + ]; + } else if ($_REQUEST['subject'] == 'column') { + $urlvars = [ + 'action' => 'alter', + 'server' => $_REQUEST['server'], + 'database' => $_REQUEST['database'], + 'schema' => $_REQUEST['schema'], + $subject => $object, + 'subject' => $subject, + ]; + + if (isset($_REQUEST['table'])) { + $urlvars['table'] = $_REQUEST['table']; + } else { + $urlvars['view'] = $_REQUEST['view']; + } + + } else { + $urlvars = [ + 'action' => 'alter', + 'server' => $_REQUEST['server'], + 'database' => $_REQUEST['database'], + $subject => $object, + 'subject' => $subject, + ]; + if (isset($_REQUEST['schema'])) { + $urlvars['schema'] = $_REQUEST['schema']; + } + } + + $navlinks = [ + 'grant' => [ + 'attr' => [ + 'href' => [ + 'url' => 'privileges.php', + 'urlvars' => array_merge($urlvars, ['mode' => 'grant']), + ], + ], + 'content' => $lang['strgrant'], + ], + 'revoke' => [ + 'attr' => [ + 'href' => [ + 'url' => 'privileges.php', + 'urlvars' => array_merge($urlvars, ['mode' => 'revoke']), + ], + ], + 'content' => $lang['strrevoke'], + ], + ]; + + if (isset($allurl)) { + $navlinks[$alllabel] = [ + 'attr' => [ + 'href' => [ + 'url' => $allurl, + 'urlvars' => [ + 'server' => $_REQUEST['server'], + 'database' => $_REQUEST['database'], + ], + ], + ], + 'content' => $alltxt, + ]; + if (isset($_REQUEST['schema'])) { + $navlinks[$alllabel]['attr']['href']['urlvars']['schema'] = $_REQUEST['schema']; + } + } + + $misc->printNavLinks($navlinks, $this->table_place, get_defined_vars()); + } + +} diff --git a/src/controllers/RolesController.php b/src/controllers/RolesController.php new file mode 100644 index 00000000..58596345 --- /dev/null +++ b/src/controllers/RolesController.php @@ -0,0 +1,860 @@ +<?php + +namespace PHPPgAdmin\Controller; +use \PHPPgAdmin\Decorators\Decorator; + +/** + * Base controller class + */ +class RolesController extends BaseController { + public $_name = 'RolesController'; + + /** + * Show default list of roles in the database + */ + function doDefault($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $renderRoleConnLimit = function ($val) use ($lang) { + return $val == '-1' ? $lang['strnolimit'] : htmlspecialchars($val); + }; + + $renderRoleExpires = function ($val) use ($lang) { + return $val == 'infinity' ? $lang['strnever'] : htmlspecialchars($val); + }; + + $this->printTrail('server'); + $this->printTabs('server', 'roles'); + $misc->printMsg($msg); + + $roles = $data->getRoles(); + + $columns = [ + 'role' => [ + 'title' => $lang['strrole'], + 'field' => Decorator::field('rolname'), + 'url' => "/redirect/role?action=properties&{$misc->href}&", + 'vars' => ['rolename' => 'rolname'], + ], + 'superuser' => [ + 'title' => $lang['strsuper'], + 'field' => Decorator::field('rolsuper'), + 'type' => 'yesno', + ], + 'createdb' => [ + 'title' => $lang['strcreatedb'], + 'field' => Decorator::field('rolcreatedb'), + 'type' => 'yesno', + ], + 'createrole' => [ + 'title' => $lang['strcancreaterole'], + 'field' => Decorator::field('rolcreaterole'), + 'type' => 'yesno', + ], + 'inherits' => [ + 'title' => $lang['strinheritsprivs'], + 'field' => Decorator::field('rolinherit'), + 'type' => 'yesno', + ], + 'canloging' => [ + 'title' => $lang['strcanlogin'], + 'field' => Decorator::field('rolcanlogin'), + 'type' => 'yesno', + ], + 'connlimit' => [ + 'title' => $lang['strconnlimit'], + 'field' => Decorator::field('rolconnlimit'), + 'type' => 'callback', + 'params' => ['function' => $renderRoleConnLimit], + ], + 'expires' => [ + 'title' => $lang['strexpires'], + 'field' => Decorator::field('rolvaliduntil'), + 'type' => 'callback', + 'params' => ['function' => $renderRoleExpires, 'null' => $lang['strnever']], + ], + 'actions' => [ + 'title' => $lang['stractions'], + ], + ]; + + $actions = [ + 'alter' => [ + 'content' => $lang['stralter'], + 'attr' => [ + 'href' => [ + 'url' => 'roles.php', + 'urlvars' => [ + 'action' => 'alter', + 'rolename' => Decorator::field('rolname'), + ], + ], + ], + ], + 'drop' => [ + 'content' => $lang['strdrop'], + 'attr' => [ + 'href' => [ + 'url' => 'roles.php', + 'urlvars' => [ + 'action' => 'confirm_drop', + 'rolename' => Decorator::field('rolname'), + ], + ], + ], + ], + ]; + + echo $this->printTable($roles, $columns, $actions, 'roles-roles', $lang['strnoroles']); + + $navlinks = [ + 'create' => [ + 'attr' => [ + 'href' => [ + 'url' => 'roles.php', + 'urlvars' => [ + 'action' => 'create', + 'server' => $_REQUEST['server'], + ], + ], + ], + 'content' => $lang['strcreaterole'], + ], + ]; + $this->printNavLinks($navlinks, 'roles-roles', get_defined_vars()); + } + + function render() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + $action = $this->action; + + $misc->printHeader($lang['strroles']); + $misc->printBody(); + + switch ($action) { + case 'create': + $this->doCreate(); + break; + case 'save_create': + if (isset($_POST['create'])) { + $this->doSaveCreate(); + } else { + $this->doDefault(); + } + + break; + case 'alter': + $this->doAlter(); + break; + case 'save_alter': + if (isset($_POST['alter'])) { + $this->doSaveAlter(); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + case 'drop': + if (isset($_POST['drop'])) { + $this->doDrop(false); + } else { + $this->doDefault(); + } + + break; + case 'properties': + $this->doProperties(); + break; + case 'confchangepassword': + $this->doChangePassword(true); + break; + case 'changepassword': + if (isset($_REQUEST['ok'])) { + $this->doChangePassword(false); + } else { + $this->doAccount(); + } + + break; + case 'account': + $this->doAccount(); + break; + default: + $this->doDefault(); + } + + $misc->printFooter(); + } + + /** + * Displays a screen for create a new role + */ + function doCreate($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + if (!isset($_POST['formRolename'])) { + $_POST['formRolename'] = ''; + } + + if (!isset($_POST['formPassword'])) { + $_POST['formPassword'] = ''; + } + + if (!isset($_POST['formConfirm'])) { + $_POST['formConfirm'] = ''; + } + + if (!isset($_POST['formConnLimit'])) { + $_POST['formConnLimit'] = ''; + } + + if (!isset($_POST['formExpires'])) { + $_POST['formExpires'] = ''; + } + + if (!isset($_POST['memberof'])) { + $_POST['memberof'] = []; + } + + if (!isset($_POST['members'])) { + $_POST['members'] = []; + } + + if (!isset($_POST['adminmembers'])) { + $_POST['adminmembers'] = []; + } + + $this->printTrail('role'); + $misc->printTitle($lang['strcreaterole'], 'pg.role.create'); + $misc->printMsg($msg); + + echo "<form action=\"/src/views/roles.php\" method=\"post\">\n"; + echo "<table>\n"; + echo "\t<tr>\n\t\t<th class=\"data left required\" style=\"width: 130px\">{$lang['strname']}</th>\n"; + echo "\t\t<td class=\"data1\"><input size=\"15\" maxlength=\"{$data->_maxNameLen}\" name=\"formRolename\" value=\"", htmlspecialchars($_POST['formRolename']), "\" /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strpassword']}</th>\n"; + echo "\t\t<td class=\"data1\"><input size=\"15\" type=\"password\" name=\"formPassword\" value=\"", htmlspecialchars($_POST['formPassword']), "\" /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strconfirm']}</th>\n"; + echo "\t\t<td class=\"data1\"><input size=\"15\" type=\"password\" name=\"formConfirm\" value=\"", htmlspecialchars($_POST['formConfirm']), "\" /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formSuper\">{$lang['strsuper']}</label></th>\n"; + echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formSuper\" name=\"formSuper\"", + (isset($_POST['formSuper'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formCreateDB\">{$lang['strcreatedb']}</label></th>\n"; + echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formCreateDB\" name=\"formCreateDB\"", + (isset($_POST['formCreateDB'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formCreateRole\">{$lang['strcancreaterole']}</label></th>\n"; + echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formCreateRole\" name=\"formCreateRole\"", + (isset($_POST['formCreateRole'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formInherits\">{$lang['strinheritsprivs']}</label></th>\n"; + echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formInherits\" name=\"formInherits\"", + (isset($_POST['formInherits'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formCanLogin\">{$lang['strcanlogin']}</label></th>\n"; + echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formCanLogin\" name=\"formCanLogin\"", + (isset($_POST['formCanLogin'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strconnlimit']}</th>\n"; + echo "\t\t<td class=\"data1\"><input size=\"4\" name=\"formConnLimit\" value=\"", htmlspecialchars($_POST['formConnLimit']), "\" /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strexpires']}</th>\n"; + echo "\t\t<td class=\"data1\"><input size=\"23\" name=\"formExpires\" value=\"", htmlspecialchars($_POST['formExpires']), "\" /></td>\n\t</tr>\n"; + + $roles = $data->getRoles(); + if ($roles->recordCount() > 0) { + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strmemberof']}</th>\n"; + echo "\t\t<td class=\"data\">\n"; + echo "\t\t\t<select name=\"memberof[]\" multiple=\"multiple\" size=\"", min(20, $roles->recordCount()), "\">\n"; + while (!$roles->EOF) { + $rolename = $roles->fields['rolname']; + echo "\t\t\t\t<option value=\"{$rolename}\"", + (in_array($rolename, $_POST['memberof']) ? ' selected="selected"' : ''), ">", $misc->printVal($rolename), "</option>\n"; + $roles->moveNext(); + } + echo "\t\t\t</select>\n"; + echo "\t\t</td>\n\t</tr>\n"; + + $roles->moveFirst(); + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strmembers']}</th>\n"; + echo "\t\t<td class=\"data\">\n"; + echo "\t\t\t<select name=\"members[]\" multiple=\"multiple\" size=\"", min(20, $roles->recordCount()), "\">\n"; + while (!$roles->EOF) { + $rolename = $roles->fields['rolname']; + echo "\t\t\t\t<option value=\"{$rolename}\"", + (in_array($rolename, $_POST['members']) ? ' selected="selected"' : ''), ">", $misc->printVal($rolename), "</option>\n"; + $roles->moveNext(); + } + echo "\t\t\t</select>\n"; + echo "\t\t</td>\n\t</tr>\n"; + + $roles->moveFirst(); + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['stradminmembers']}</th>\n"; + echo "\t\t<td class=\"data\">\n"; + echo "\t\t\t<select name=\"adminmembers[]\" multiple=\"multiple\" size=\"", min(20, $roles->recordCount()), "\">\n"; + while (!$roles->EOF) { + $rolename = $roles->fields['rolname']; + echo "\t\t\t\t<option value=\"{$rolename}\"", + (in_array($rolename, $_POST['adminmembers']) ? ' selected="selected"' : ''), ">", $misc->printVal($rolename), "</option>\n"; + $roles->moveNext(); + } + echo "\t\t\t</select>\n"; + echo "\t\t</td>\n\t</tr>\n"; + } + + echo "</table>\n"; + echo "<p><input type=\"hidden\" name=\"action\" value=\"save_create\" />\n"; + echo $misc->form; + echo "<input type=\"submit\" name=\"create\" value=\"{$lang['strcreate']}\" />\n"; + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; + echo "</form>\n"; + } + + /** + * Actually creates the new role in the database + */ + function doSaveCreate() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + if (!isset($_POST['memberof'])) { + $_POST['memberof'] = []; + } + + if (!isset($_POST['members'])) { + $_POST['members'] = []; + } + + if (!isset($_POST['adminmembers'])) { + $_POST['adminmembers'] = []; + } + + // Check data + if ($_POST['formRolename'] == '') { + $this->doCreate($lang['strroleneedsname']); + } else if ($_POST['formPassword'] != $_POST['formConfirm']) { + $this->doCreate($lang['strpasswordconfirm']); + } else { + $status = $data->createRole($_POST['formRolename'], $_POST['formPassword'], isset($_POST['formSuper']), + isset($_POST['formCreateDB']), isset($_POST['formCreateRole']), isset($_POST['formInherits']), + isset($_POST['formCanLogin']), $_POST['formConnLimit'], $_POST['formExpires'], $_POST['memberof'], $_POST['members'], + $_POST['adminmembers']); + if ($status == 0) { + $this->doDefault($lang['strrolecreated']); + } else { + $this->doCreate($lang['strrolecreatedbad']); + } + + } + } + + /** + * Function to allow alter a role + */ + function doAlter($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $this->printTrail('role'); + $misc->printTitle($lang['stralter'], 'pg.role.alter'); + $misc->printMsg($msg); + + $roledata = $data->getRole($_REQUEST['rolename']); + + if ($roledata->recordCount() > 0) { + $server_info = $misc->getServerInfo(); + $canRename = $data->hasUserRename() && ($_REQUEST['rolename'] != $server_info['username']); + $roledata->fields['rolsuper'] = $data->phpBool($roledata->fields['rolsuper']); + $roledata->fields['rolcreatedb'] = $data->phpBool($roledata->fields['rolcreatedb']); + $roledata->fields['rolcreaterole'] = $data->phpBool($roledata->fields['rolcreaterole']); + $roledata->fields['rolinherit'] = $data->phpBool($roledata->fields['rolinherit']); + $roledata->fields['rolcanlogin'] = $data->phpBool($roledata->fields['rolcanlogin']); + + if (!isset($_POST['formExpires'])) { + if ($canRename) { + $_POST['formNewRoleName'] = $roledata->fields['rolname']; + } + + if ($roledata->fields['rolsuper']) { + $_POST['formSuper'] = ''; + } + + if ($roledata->fields['rolcreatedb']) { + $_POST['formCreateDB'] = ''; + } + + if ($roledata->fields['rolcreaterole']) { + $_POST['formCreateRole'] = ''; + } + + if ($roledata->fields['rolinherit']) { + $_POST['formInherits'] = ''; + } + + if ($roledata->fields['rolcanlogin']) { + $_POST['formCanLogin'] = ''; + } + + $_POST['formConnLimit'] = $roledata->fields['rolconnlimit'] == '-1' ? '' : $roledata->fields['rolconnlimit']; + $_POST['formExpires'] = $roledata->fields['rolvaliduntil'] == 'infinity' ? '' : $roledata->fields['rolvaliduntil']; + $_POST['formPassword'] = ''; + } + + echo "<form action=\"/src/views/roles.php\" method=\"post\">\n"; + echo "<table>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\" style=\"width: 130px\">{$lang['strname']}</th>\n"; + echo "\t\t<td class=\"data1\">", ($canRename ? "<input name=\"formNewRoleName\" size=\"15\" maxlength=\"{$data->_maxNameLen}\" value=\"" . htmlspecialchars($_POST['formNewRoleName']) . "\" />" : $misc->printVal($roledata->fields['rolname'])), "</td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strpassword']}</th>\n"; + echo "\t\t<td class=\"data1\"><input type=\"password\" size=\"15\" name=\"formPassword\" value=\"", htmlspecialchars($_POST['formPassword']), "\" /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strconfirm']}</th>\n"; + echo "\t\t<td class=\"data1\"><input type=\"password\" size=\"15\" name=\"formConfirm\" value=\"\" /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formSuper\">{$lang['strsuper']}</label></th>\n"; + echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formSuper\" name=\"formSuper\"", + (isset($_POST['formSuper'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formCreateDB\">{$lang['strcreatedb']}</label></th>\n"; + echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formCreateDB\" name=\"formCreateDB\"", + (isset($_POST['formCreateDB'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formCreateRole\">{$lang['strcancreaterole']}</label></th>\n"; + echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formCreateRole\" name=\"formCreateRole\"", + (isset($_POST['formCreateRole'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formInherits\">{$lang['strinheritsprivs']}</label></th>\n"; + echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formInherits\" name=\"formInherits\"", + (isset($_POST['formInherits'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formCanLogin\">{$lang['strcanlogin']}</label></th>\n"; + echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formCanLogin\" name=\"formCanLogin\"", + (isset($_POST['formCanLogin'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strconnlimit']}</th>\n"; + echo "\t\t<td class=\"data1\"><input size=\"4\" name=\"formConnLimit\" value=\"", htmlspecialchars($_POST['formConnLimit']), "\" /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strexpires']}</th>\n"; + echo "\t\t<td class=\"data1\"><input size=\"23\" name=\"formExpires\" value=\"", htmlspecialchars($_POST['formExpires']), "\" /></td>\n\t</tr>\n"; + + if (!isset($_POST['memberof'])) { + $memberof = $data->getMemberOf($_REQUEST['rolename']); + if ($memberof->recordCount() > 0) { + $i = 0; + while (!$memberof->EOF) { + $_POST['memberof'][$i++] = $memberof->fields['rolname']; + $memberof->moveNext(); + } + } else { + $_POST['memberof'] = []; + } + + $memberofold = implode(',', $_POST['memberof']); + } + if (!isset($_POST['members'])) { + $members = $data->getMembers($_REQUEST['rolename']); + if ($members->recordCount() > 0) { + $i = 0; + while (!$members->EOF) { + $_POST['members'][$i++] = $members->fields['rolname']; + $members->moveNext(); + } + } else { + $_POST['members'] = []; + } + + $membersold = implode(',', $_POST['members']); + } + if (!isset($_POST['adminmembers'])) { + $adminmembers = $data->getMembers($_REQUEST['rolename'], 't'); + if ($adminmembers->recordCount() > 0) { + $i = 0; + while (!$adminmembers->EOF) { + $_POST['adminmembers'][$i++] = $adminmembers->fields['rolname']; + $adminmembers->moveNext(); + } + } else { + $_POST['adminmembers'] = []; + } + + $adminmembersold = implode(',', $_POST['adminmembers']); + } + + $roles = $data->getRoles($_REQUEST['rolename']); + if ($roles->recordCount() > 0) { + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strmemberof']}</th>\n"; + echo "\t\t<td class=\"data\">\n"; + echo "\t\t\t<select name=\"memberof[]\" multiple=\"multiple\" size=\"", min(20, $roles->recordCount()), "\">\n"; + while (!$roles->EOF) { + $rolename = $roles->fields['rolname']; + echo "\t\t\t\t<option value=\"{$rolename}\"", + (in_array($rolename, $_POST['memberof']) ? ' selected="selected"' : ''), ">", $misc->printVal($rolename), "</option>\n"; + $roles->moveNext(); + } + echo "\t\t\t</select>\n"; + echo "\t\t</td>\n\t</tr>\n"; + + $roles->moveFirst(); + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strmembers']}</th>\n"; + echo "\t\t<td class=\"data\">\n"; + echo "\t\t\t<select name=\"members[]\" multiple=\"multiple\" size=\"", min(20, $roles->recordCount()), "\">\n"; + while (!$roles->EOF) { + $rolename = $roles->fields['rolname']; + echo "\t\t\t\t<option value=\"{$rolename}\"", + (in_array($rolename, $_POST['members']) ? ' selected="selected"' : ''), ">", $misc->printVal($rolename), "</option>\n"; + $roles->moveNext(); + } + echo "\t\t\t</select>\n"; + echo "\t\t</td>\n\t</tr>\n"; + + $roles->moveFirst(); + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['stradminmembers']}</th>\n"; + echo "\t\t<td class=\"data\">\n"; + echo "\t\t\t<select name=\"adminmembers[]\" multiple=\"multiple\" size=\"", min(20, $roles->recordCount()), "\">\n"; + while (!$roles->EOF) { + $rolename = $roles->fields['rolname']; + echo "\t\t\t\t<option value=\"{$rolename}\"", + (in_array($rolename, $_POST['adminmembers']) ? ' selected="selected"' : ''), ">", $misc->printVal($rolename), "</option>\n"; + $roles->moveNext(); + } + echo "\t\t\t</select>\n"; + echo "\t\t</td>\n\t</tr>\n"; + } + echo "</table>\n"; + + echo "<p><input type=\"hidden\" name=\"action\" value=\"save_alter\" />\n"; + echo "<input type=\"hidden\" name=\"rolename\" value=\"", htmlspecialchars($_REQUEST['rolename']), "\" />\n"; + echo "<input type=\"hidden\" name=\"memberofold\" value=\"", isset($_POST['memberofold']) ? $_POST['memberofold'] : htmlspecialchars($memberofold), "\" />\n"; + echo "<input type=\"hidden\" name=\"membersold\" value=\"", isset($_POST['membersold']) ? $_POST['membersold'] : htmlspecialchars($membersold), "\" />\n"; + echo "<input type=\"hidden\" name=\"adminmembersold\" value=\"", isset($_POST['adminmembersold']) ? $_POST['adminmembersold'] : htmlspecialchars($adminmembersold), "\" />\n"; + echo $misc->form; + echo "<input type=\"submit\" name=\"alter\" value=\"{$lang['stralter']}\" />\n"; + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; + echo "</form>\n"; + } else { + echo "<p>{$lang['strnodata']}</p>\n"; + } + + } + + /** + * Function to save after editing a role + */ + function doSaveAlter() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + if (!isset($_POST['memberof'])) { + $_POST['memberof'] = []; + } + + if (!isset($_POST['members'])) { + $_POST['members'] = []; + } + + if (!isset($_POST['adminmembers'])) { + $_POST['adminmembers'] = []; + } + + // Check name and password + if (isset($_POST['formNewRoleName']) && $_POST['formNewRoleName'] == '') { + $this->doAlter($lang['strroleneedsname']); + } else if ($_POST['formPassword'] != $_POST['formConfirm']) { + $this->doAlter($lang['strpasswordconfirm']); + } else { + if (isset($_POST['formNewRoleName'])) { + $status = $data->setRenameRole($_POST['rolename'], $_POST['formPassword'], isset($_POST['formSuper']), isset($_POST['formCreateDB']), isset($_POST['formCreateRole']), isset($_POST['formInherits']), isset($_POST['formCanLogin']), $_POST['formConnLimit'], $_POST['formExpires'], $_POST['memberof'], $_POST['members'], $_POST['adminmembers'], $_POST['memberofold'], $_POST['membersold'], $_POST['adminmembersold'], $_POST['formNewRoleName']); + } else { + $status = $data->setRole($_POST['rolename'], $_POST['formPassword'], isset($_POST['formSuper']), isset($_POST['formCreateDB']), isset($_POST['formCreateRole']), isset($_POST['formInherits']), isset($_POST['formCanLogin']), $_POST['formConnLimit'], $_POST['formExpires'], $_POST['memberof'], $_POST['members'], $_POST['adminmembers'], $_POST['memberofold'], $_POST['membersold'], $_POST['adminmembersold']); + } + + if ($status == 0) { + $this->doDefault($lang['strrolealtered']); + } else { + $this->doAlter($lang['strrolealteredbad']); + } + + } + } + + /** + * Show confirmation of drop a role and perform actual drop + */ + function doDrop($confirm) { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + if ($confirm) { + $this->printTrail('role'); + $misc->printTitle($lang['strdroprole'], 'pg.role.drop'); + + echo "<p>", sprintf($lang['strconfdroprole'], $misc->printVal($_REQUEST['rolename'])), "</p>\n"; + + echo "<form action=\"/src/views/roles.php\" method=\"post\">\n"; + echo "<p><input type=\"hidden\" name=\"action\" value=\"drop\" />\n"; + echo "<input type=\"hidden\" name=\"rolename\" value=\"", htmlspecialchars($_REQUEST['rolename']), "\" />\n"; + echo $misc->form; + echo "<input type=\"submit\" name=\"drop\" value=\"{$lang['strdrop']}\" />\n"; + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; + echo "</form>\n"; + } else { + $status = $data->dropRole($_REQUEST['rolename']); + if ($status == 0) { + $this->doDefault($lang['strroledropped']); + } else { + $this->doDefault($lang['strroledroppedbad']); + } + + } + } + + /** + * Show the properties of a role + */ + function doProperties($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $this->printTrail('role'); + $misc->printTitle($lang['strproperties'], 'pg.role'); + $misc->printMsg($msg); + + $roledata = $data->getRole($_REQUEST['rolename']); + if ($roledata->recordCount() > 0) { + $roledata->fields['rolsuper'] = $data->phpBool($roledata->fields['rolsuper']); + $roledata->fields['rolcreatedb'] = $data->phpBool($roledata->fields['rolcreatedb']); + $roledata->fields['rolcreaterole'] = $data->phpBool($roledata->fields['rolcreaterole']); + $roledata->fields['rolinherit'] = $data->phpBool($roledata->fields['rolinherit']); + $roledata->fields['rolcanlogin'] = $data->phpBool($roledata->fields['rolcanlogin']); + + echo "<table>\n"; + echo "\t<tr>\n\t\t<th class=\"data\" style=\"width: 130px\">Description</th>\n"; + echo "\t\t<th class=\"data\" style=\"width: 120\">Value</th>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<td class=\"data1\">{$lang['strname']}</td>\n"; + echo "\t\t<td class=\"data1\">", htmlspecialchars($_REQUEST['rolename']), "</td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<td class=\"data2\">{$lang['strsuper']}</td>\n"; + echo "\t\t<td class=\"data2\">", (($roledata->fields['rolsuper']) ? $lang['stryes'] : $lang['strno']), "</td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<td class=\"data1\">{$lang['strcreatedb']}</td>\n"; + echo "\t\t<td class=\"data1\">", (($roledata->fields['rolcreatedb']) ? $lang['stryes'] : $lang['strno']), "</td>\n"; + echo "\t<tr>\n\t\t<td class=\"data2\">{$lang['strcancreaterole']}</td>\n"; + echo "\t\t<td class=\"data2\">", (($roledata->fields['rolcreaterole']) ? $lang['stryes'] : $lang['strno']), "</td>\n"; + echo "\t<tr>\n\t\t<td class=\"data1\">{$lang['strinheritsprivs']}</td>\n"; + echo "\t\t<td class=\"data1\">", (($roledata->fields['rolinherit']) ? $lang['stryes'] : $lang['strno']), "</td>\n"; + echo "\t<tr>\n\t\t<td class=\"data2\">{$lang['strcanlogin']}</td>\n"; + echo "\t\t<td class=\"data2\">", (($roledata->fields['rolcanlogin']) ? $lang['stryes'] : $lang['strno']), "</td>\n"; + echo "\t<tr>\n\t\t<td class=\"data1\">{$lang['strconnlimit']}</td>\n"; + echo "\t\t<td class=\"data1\">", ($roledata->fields['rolconnlimit'] == '-1' ? $lang['strnolimit'] : $misc->printVal($roledata->fields['rolconnlimit'])), "</td>\n"; + echo "\t<tr>\n\t\t<td class=\"data2\">{$lang['strexpires']}</td>\n"; + echo "\t\t<td class=\"data2\">", ($roledata->fields['rolvaliduntil'] == 'infinity' || is_null($roledata->fields['rolvaliduntil']) ? $lang['strnever'] : $misc->printVal($roledata->fields['rolvaliduntil'])), "</td>\n"; + echo "\t<tr>\n\t\t<td class=\"data1\">{$lang['strsessiondefaults']}</td>\n"; + echo "\t\t<td class=\"data1\">", $misc->printVal($roledata->fields['rolconfig']), "</td>\n"; + echo "\t<tr>\n\t\t<td class=\"data2\">{$lang['strmemberof']}</td>\n"; + echo "\t\t<td class=\"data2\">"; + $memberof = $data->getMemberOf($_REQUEST['rolename']); + if ($memberof->recordCount() > 0) { + while (!$memberof->EOF) { + echo $misc->printVal($memberof->fields['rolname']), "<br />\n"; + $memberof->moveNext(); + } + } + echo "</td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<td class=\"data1\">{$lang['strmembers']}</td>\n"; + echo "\t\t<td class=\"data1\">"; + $members = $data->getMembers($_REQUEST['rolename']); + if ($members->recordCount() > 0) { + while (!$members->EOF) { + echo $misc->printVal($members->fields['rolname']), "<br />\n"; + $members->moveNext(); + } + } + echo "</td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<td class=\"data2\">{$lang['stradminmembers']}</td>\n"; + echo "\t\t<td class=\"data2\">"; + $adminmembers = $data->getMembers($_REQUEST['rolename'], 't'); + if ($adminmembers->recordCount() > 0) { + while (!$adminmembers->EOF) { + echo $misc->printVal($adminmembers->fields['rolname']), "<br />\n"; + $adminmembers->moveNext(); + } + } + echo "</td>\n\t</tr>\n"; + echo "</table>\n"; + } else { + echo "<p>{$lang['strnodata']}</p>\n"; + } + + $navlinks = [ + 'showall' => [ + 'attr' => [ + 'href' => [ + 'url' => 'roles.php', + 'urlvars' => [ + 'server' => $_REQUEST['server'], + ], + ], + ], + 'content' => $lang['strshowallroles'], + ], + 'alter' => [ + 'attr' => [ + 'href' => [ + 'url' => 'roles.php', + 'urlvars' => [ + 'action' => 'alter', + 'server' => $_REQUEST['server'], + 'rolename' => $_REQUEST['rolename'], + ], + ], + ], + 'content' => $lang['stralter'], + ], + 'drop' => [ + 'attr' => [ + 'href' => [ + 'url' => 'roles.php', + 'urlvars' => [ + 'action' => 'confirm_drop', + 'server' => $_REQUEST['server'], + 'rolename' => $_REQUEST['rolename'], + ], + ], + ], + 'content' => $lang['strdrop'], + ], + ]; + + $this->printNavLinks($navlinks, 'roles-properties', get_defined_vars()); + } + + /** + * If a role is not a superuser role, then we have an 'account management' + * page for change his password, etc. We don't prevent them from + * messing with the URL to gain access to other role admin stuff, because + * the PostgreSQL permissions will prevent them changing anything anyway. + */ + function doAccount($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $server_info = $misc->getServerInfo(); + + $roledata = $data->getRole($server_info['username']); + $_REQUEST['rolename'] = $server_info['username']; + + $this->printTrail('role'); + $this->printTabs('server', 'account'); + $misc->printMsg($msg); + + if ($roledata->recordCount() > 0) { + $roledata->fields['rolsuper'] = $data->phpBool($roledata->fields['rolsuper']); + $roledata->fields['rolcreatedb'] = $data->phpBool($roledata->fields['rolcreatedb']); + $roledata->fields['rolcreaterole'] = $data->phpBool($roledata->fields['rolcreaterole']); + $roledata->fields['rolinherit'] = $data->phpBool($roledata->fields['rolinherit']); + echo "<table>\n"; + echo "\t<tr>\n\t\t<th class=\"data\">{$lang['strname']}</th>\n"; + echo "\t\t<th class=\"data\">{$lang['strsuper']}</th>\n"; + echo "\t\t<th class=\"data\">{$lang['strcreatedb']}</th>\n"; + echo "\t\t<th class=\"data\">{$lang['strcancreaterole']}</th>\n"; + echo "\t\t<th class=\"data\">{$lang['strinheritsprivs']}</th>\n"; + echo "\t\t<th class=\"data\">{$lang['strconnlimit']}</th>\n"; + echo "\t\t<th class=\"data\">{$lang['strexpires']}</th>\n"; + echo "\t\t<th class=\"data\">{$lang['strsessiondefaults']}</th>\n"; + echo "\t</tr>\n"; + echo "\t<tr>\n\t\t<td class=\"data1\">", $misc->printVal($roledata->fields['rolname']), "</td>\n"; + echo "\t\t<td class=\"data1\">", $misc->printVal($roledata->fields['rolsuper'], 'yesno'), "</td>\n"; + echo "\t\t<td class=\"data1\">", $misc->printVal($roledata->fields['rolcreatedb'], 'yesno'), "</td>\n"; + echo "\t\t<td class=\"data1\">", $misc->printVal($roledata->fields['rolcreaterole'], 'yesno'), "</td>\n"; + echo "\t\t<td class=\"data1\">", $misc->printVal($roledata->fields['rolinherit'], 'yesno'), "</td>\n"; + echo "\t\t<td class=\"data1\">", ($roledata->fields['rolconnlimit'] == '-1' ? $lang['strnolimit'] : $misc->printVal($roledata->fields['rolconnlimit'])), "</td>\n"; + echo "\t\t<td class=\"data1\">", ($roledata->fields['rolvaliduntil'] == 'infinity' || is_null($roledata->fields['rolvaliduntil']) ? $lang['strnever'] : $misc->printVal($roledata->fields['rolvaliduntil'])), "</td>\n"; + echo "\t\t<td class=\"data1\">", $misc->printVal($roledata->fields['rolconfig']), "</td>\n"; + echo "\t</tr>\n</table>\n"; + } else { + echo "<p>{$lang['strnodata']}</p>\n"; + } + + $this->printNavLinks(['changepassword' => [ + 'attr' => [ + 'href' => [ + 'url' => 'roles.php', + 'urlvars' => [ + 'action' => 'confchangepassword', + 'server' => $_REQUEST['server'], + ], + ], + ], + 'content' => $lang['strchangepassword'], + ]], 'roles-account', get_defined_vars()); + } + + /** + * Show confirmation of change password and actually change password + */ + function doChangePassword($confirm, $msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $server_info = $misc->getServerInfo(); + + if ($confirm) { + $_REQUEST['rolename'] = $server_info['username']; + $this->printTrail('role'); + $misc->printTitle($lang['strchangepassword'], 'pg.role.alter'); + $misc->printMsg($msg); + + if (!isset($_POST['password'])) { + $_POST['password'] = ''; + } + + if (!isset($_POST['confirm'])) { + $_POST['confirm'] = ''; + } + + echo "<form action=\"/src/views/roles.php\" method=\"post\">\n"; + echo "<table>\n"; + echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strpassword']}</th>\n"; + echo "\t\t<td><input type=\"password\" name=\"password\" size=\"32\" value=\"", + htmlspecialchars($_POST['password']), "\" /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strconfirm']}</th>\n"; + echo "\t\t<td><input type=\"password\" name=\"confirm\" size=\"32\" value=\"\" /></td>\n\t</tr>\n"; + echo "</table>\n"; + echo "<p><input type=\"hidden\" name=\"action\" value=\"changepassword\" />\n"; + echo $misc->form; + echo "<input type=\"submit\" name=\"ok\" value=\"{$lang['strok']}\" />\n"; + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n"; + echo "</p></form>\n"; + } else { + // Check that password is minimum length + if (strlen($_POST['password']) < $conf['min_password_length']) { + $this->doChangePassword(true, $lang['strpasswordshort']); + } + + // Check that password matches confirmation password + elseif ($_POST['password'] != $_POST['confirm']) { + $this->doChangePassword(true, $lang['strpasswordconfirm']); + } else { + $status = $data->changePassword($server_info['username'], $_POST['password']); + if ($status == 0) { + $this->doAccount($lang['strpasswordchanged']); + } else { + $this->doAccount($lang['strpasswordchangedbad']); + } + + } + } + } + +} diff --git a/src/controllers/RuleController.php b/src/controllers/RuleController.php index a19be99e..84aadf82 100644 --- a/src/controllers/RuleController.php +++ b/src/controllers/RuleController.php @@ -9,9 +9,74 @@ use \PHPPgAdmin\Decorators\Decorator; class RuleController extends BaseController { public $_name = 'RuleController'; -/** - * Confirm and then actually create a rule - */ + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + + // Different header if we're view rules or table rules + $misc->printHeader($_REQUEST[$_REQUEST['subject']] . ' - ' . $lang['strrules']); + $misc->printBody(); + + switch ($action) { + case 'create_rule': + $this->createRule(true); + break; + case 'save_create_rule': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->createRule(false); + } + + break; + case 'drop': + if (isset($_POST['yes'])) { + $this->doDrop(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + default: + $this->doDefault(); + break; + } + + return $misc->printFooter(); + + } + + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $rules = $data->getRules($_REQUEST[$_REQUEST['subject']]); + + $reqvars = $misc->getRequestVars($_REQUEST['subject']); + + $attrs = [ + 'text' => Decorator::field('rulename'), + 'icon' => 'Rule', + ]; + + return $misc->printTree($rules, $attrs, 'rules'); + } + + /** + * Confirm and then actually create a rule + */ function createRule($confirm, $msg = '') { $conf = $this->conf; $misc = $this->misc; @@ -39,7 +104,7 @@ class RuleController extends BaseController { } if ($confirm) { - $misc->printTrail($_REQUEST['subject']); + $this->printTrail($_REQUEST['subject']); $misc->printTitle($lang['strcreaterule'], 'pg.rule.create'); $misc->printMsg($msg); @@ -106,7 +171,7 @@ class RuleController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail($_REQUEST['subject']); + $this->printTrail($_REQUEST['subject']); $misc->printTitle($lang['strdrop'], 'pg.rule.drop'); echo "<p>", sprintf($lang['strconfdroprule'], $misc->printVal($_REQUEST['rule']), @@ -144,8 +209,8 @@ class RuleController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail($_REQUEST['subject']); - $misc->printTabs($_REQUEST['subject'], 'rules'); + $this->printTrail($_REQUEST['subject']); + $this->printTabs($_REQUEST['subject'], 'rules'); $misc->printMsg($msg); $rules = $data->getRules($_REQUEST[$_REQUEST['subject']]); @@ -185,9 +250,9 @@ class RuleController extends BaseController { ], ]; - echo $misc->printTable($rules, $columns, $actions, 'rules-rules', $lang['strnorules']); + echo $this->printTable($rules, $columns, $actions, 'rules-rules', $lang['strnorules']); - $misc->printNavLinks(['create' => [ + $this->printNavLinks(['create' => [ 'attr' => [ 'href' => [ 'url' => 'rules.php', diff --git a/src/controllers/SQLEditController.php b/src/controllers/SQLEditController.php new file mode 100644 index 00000000..ecc54880 --- /dev/null +++ b/src/controllers/SQLEditController.php @@ -0,0 +1,198 @@ +<?php + +namespace PHPPgAdmin\Controller; + +/** + * Base controller class + */ +class SQLEditController extends BaseController { + public $_name = 'SQLEditController'; + public $query = ''; + public $subject = ''; + public $start_time = null; + public $duration = null; + +/** + * Private function to display server and list of databases + */ + function _printConnection($action) { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + // The javascript action on the select box reloads the + // popup whenever the server or database is changed. + // This ensures that the correct page encoding is used. + $onchange = "onchange=\"location.href='/sqledit/" . + urlencode($action) . "?server=' + encodeURI(server.options[server.selectedIndex].value) + '&database=' + encodeURI(database.options[database.selectedIndex].value) + "; + + // The exact URL to reload to is different between SQL and Find mode, however. + if ($action == 'find') { + $onchange .= "'&term=' + encodeURI(term.value) + '&filter=' + encodeURI(filter.value) + '&'\""; + } else { + $onchange .= "'&query=' + encodeURI(query.value) + '&search_path=' + encodeURI(search_path.value) + (paginate.checked ? '&paginate=on' : '') + '&'\""; + } + + return $misc->printConnection($onchange, false); + } + + /** + * Searches for a named database object + */ + function doFind() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + if (!isset($_REQUEST['term'])) { + $_REQUEST['term'] = ''; + } + + if (!isset($_REQUEST['filter'])) { + $_REQUEST['filter'] = ''; + } + + $default_html = $this->printTabs($misc->getNavTabs('popup'), 'find', false); + + $default_html .= "<form action=\"database.php\" method=\"post\" target=\"detail\">\n"; + $default_html .= $this->_printConnection('find'); + $default_html .= "<p><input class=\"focusme\" name=\"term\" value=\"" . htmlspecialchars($_REQUEST['term']) . "\" size=\"32\" maxlength=\"{$data->_maxNameLen}\" />\n"; + + // Output list of filters. This is complex due to all the 'has' and 'conf' feature possibilities + $default_html .= "<select name=\"filter\">\n"; + $default_html .= "\t<option value=\"\"" . ($_REQUEST['filter'] == '' ? ' selected="selected" ' : '') . ">{$lang['strallobjects']}</option>\n"; + $default_html .= "\t<option value=\"SCHEMA\"" . ($_REQUEST['filter'] == 'SCHEMA' ? ' selected="selected" ' : '') . ">{$lang['strschemas']}</option>\n"; + $default_html .= "\t<option value=\"TABLE\"" . ($_REQUEST['filter'] == 'TABLE' ? ' selected="selected" ' : '') . ">{$lang['strtables']}</option>\n"; + $default_html .= "\t<option value=\"VIEW\"" . ($_REQUEST['filter'] == 'VIEW' ? ' selected="selected" ' : '') . ">{$lang['strviews']}</option>\n"; + $default_html .= "\t<option value=\"SEQUENCE\"" . ($_REQUEST['filter'] == 'SEQUENCE' ? ' selected="selected" ' : '') . ">{$lang['strsequences']}</option>\n"; + $default_html .= "\t<option value=\"COLUMN\"" . ($_REQUEST['filter'] == 'COLUMN' ? ' selected="selected" ' : '') . ">{$lang['strcolumns']}</option>\n"; + $default_html .= "\t<option value=\"RULE\"" . ($_REQUEST['filter'] == 'RULE' ? ' selected="selected" ' : '') . ">{$lang['strrules']}</option>\n"; + $default_html .= "\t<option value=\"INDEX\"" . ($_REQUEST['filter'] == 'INDEX' ? ' selected="selected" ' : '') . ">{$lang['strindexes']}</option>\n"; + $default_html .= "\t<option value=\"TRIGGER\"" . ($_REQUEST['filter'] == 'TRIGGER' ? ' selected="selected" ' : '') . ">{$lang['strtriggers']}</option>\n"; + $default_html .= "\t<option value=\"CONSTRAINT\"" . ($_REQUEST['filter'] == 'CONSTRAINT' ? ' selected="selected" ' : '') . ">{$lang['strconstraints']}</option>\n"; + $default_html .= "\t<option value=\"FUNCTION\"" . ($_REQUEST['filter'] == 'FUNCTION' ? ' selected="selected" ' : '') . ">{$lang['strfunctions']}</option>\n"; + $default_html .= "\t<option value=\"DOMAIN\"" . ($_REQUEST['filter'] == 'DOMAIN' ? ' selected="selected" ' : '') . ">{$lang['strdomains']}</option>\n"; + if ($conf['show_advanced']) { + $default_html .= "\t<option value=\"AGGREGATE\"" . ($_REQUEST['filter'] == 'AGGREGATE' ? ' selected="selected" ' : '') . ">{$lang['straggregates']}</option>\n"; + $default_html .= "\t<option value=\"TYPE\"" . ($_REQUEST['filter'] == 'TYPE' ? ' selected="selected" ' : '') . ">{$lang['strtypes']}</option>\n"; + $default_html .= "\t<option value=\"OPERATOR\"" . ($_REQUEST['filter'] == 'OPERATOR' ? ' selected="selected" ' : '') . ">{$lang['stroperators']}</option>\n"; + $default_html .= "\t<option value=\"OPCLASS\"" . ($_REQUEST['filter'] == 'OPCLASS' ? ' selected="selected" ' : '') . ">{$lang['stropclasses']}</option>\n"; + $default_html .= "\t<option value=\"CONVERSION\"" . ($_REQUEST['filter'] == 'CONVERSION' ? ' selected="selected" ' : '') . ">{$lang['strconversions']}</option>\n"; + $default_html .= "\t<option value=\"LANGUAGE\"" . ($_REQUEST['filter'] == 'LANGUAGE' ? ' selected="selected" ' : '') . ">{$lang['strlanguages']}</option>\n"; + } + $default_html .= "</select>\n"; + + $default_html .= "<input type=\"submit\" value=\"{$lang['strfind']}\" />\n"; + $default_html .= "<input type=\"hidden\" name=\"action\" value=\"find\" /></p>\n"; + $default_html .= "</form>\n"; + + // Default focus + //$misc->setFocus('forms[0].term'); + return $default_html; + } + + /** + * Allow execution of arbitrary SQL statements on a database + */ + function doDefault() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + if (!isset($_SESSION['sqlquery'])) { + $_SESSION['sqlquery'] = ''; + } + + $default_html = $this->printTabs($misc->getNavTabs('popup'), 'sql', false); + + $default_html .= '<form action="/src/views/sql.php" method="post" enctype="multipart/form-data" class="sqlform" id="sqlform" target="detail">'; + $default_html .= "\n"; + $default_html .= $this->_printConnection('sql'); + + $default_html .= "\n"; + + if (!isset($_REQUEST['search_path'])) { + $_REQUEST['search_path'] = implode(',', $data->getSearchPath()); + } + $search_path = htmlspecialchars($_REQUEST['search_path']); + $sqlquery = htmlspecialchars($_SESSION['sqlquery']); + + $default_html .= ' <div class="searchpath">'; + $default_html .= "<label>"; + $default_html .= $misc->printHelp($lang['strsearchpath'], 'pg.schema.search_path', false); + + $default_html .= ': <input type="text" name="search_path" size="50" value="' . $search_path . '" />'; + $default_html .= "</label>\n"; + $default_html .= "</div>\n"; + + $default_html .= '<div id="queryedition" style="padding:1%;width:98%;float:left;">'; + $default_html .= "\n"; + $default_html .= '<textarea style="width:98%;" rows="10" cols="50" name="query" id="query" resizable="true">' . $sqlquery . "</textarea>"; + $default_html .= "\n"; + $default_html .= "</div>\n"; + + // Check that file uploads are enabled + if (ini_get('file_uploads')) { + // Don't show upload option if max size of uploads is zero + $max_size = $misc->inisizeToBytes(ini_get('upload_max_filesize')); + if (is_double($max_size) && $max_size > 0) { + $default_html .= "<p>"; + $default_html .= '<input type="hidden" name="MAX_FILE_SIZE" value="' . $max_size . '" />'; + $default_html .= "\n"; + $default_html .= '<label for="script">' . $lang['struploadscript'] . '</label>'; + $default_html .= ' <input id="script" name="script" type="file" /></p>'; + $default_html .= "</p>\n"; + } + } + $checked = (isset($_REQUEST['paginate']) ? ' checked="checked"' : ''); + $default_html .= '<p>'; + $default_html .= '<label for="paginate"><input type="checkbox" id="paginate" name="paginate"' . $checked . ' /> ' . $lang['strpaginate'] . '</label>'; + $default_html .= "</p>\n"; + $default_html .= '<p><input type="submit" name="execute" accesskey="r" value="' . $lang['strexecute'] . '" />'; + $default_html .= "\n"; + $default_html .= '<input type="reset" accesskey="q" value="' . $lang['strreset'] . '" /></p>'; + $default_html .= "\n"; + $default_html .= "</form>"; + $default_html .= "\n"; + + // Default focus + //$misc->setFocus('forms[0].query'); + return $default_html; + + } + + public function render() { + $conf = $this->conf; + $lang = $this->lang; + $misc = $this->misc; + $action = $this->action; + $data = $misc->getDatabaseAccessor(); + + switch ($action) { + case 'find': + $view_param = ['title' => $this->lang['strfind']]; + $body_text = $this->doFind(); + break; + case 'sql': + default: + $view_param = ['title' => $this->lang['strsql']]; + $body_text = $this->doDefault(); + + break; + } + + echo $this->view->fetch('sqledit_header.twig', $view_param); + echo $body_text; + echo $this->view->fetch('sqledit.twig'); + + $misc->setWindowName('sqledit'); + + } + +} diff --git a/src/controllers/SQLQueryController.php b/src/controllers/SQLQueryController.php new file mode 100644 index 00000000..68980db9 --- /dev/null +++ b/src/controllers/SQLQueryController.php @@ -0,0 +1,324 @@ +<?php + +namespace PHPPgAdmin\Controller; + +/** + * Base controller class + */ +class SQLQueryController extends BaseController { + public $_name = 'SQLQueryController'; + public $query = ''; + public $subject = ''; + public $start_time = null; + public $duration = null; + + /* Constructor */ + function __construct(\Slim\Container $container) { + parent::__construct($container); + + // Prevent timeouts on large exports (non-safe mode only) + if (!ini_get('safe_mode')) { + set_time_limit(0); + } + + // We need to store the query in a session for editing purposes + // We avoid GPC vars to avoid truncating long queries + if (isset($_REQUEST['subject']) && $_REQUEST['subject'] == 'history') { + // Or maybe we came from the history popup + $_SESSION['sqlquery'] = $_SESSION['history'][$_REQUEST['server']][$_REQUEST['database']][$_GET['queryid']]['query']; + $this->query = $_SESSION['sqlquery']; + } elseif (isset($_POST['query'])) { + // Or maybe we came from an sql form + $_SESSION['sqlquery'] = $_POST['query']; + $this->query = $_SESSION['sqlquery']; + } else { + echo "could not find the query!!"; + } + + // Pagination maybe set by a get link that has it as FALSE, + // if that's the case, unset the variable. + if (isset($_REQUEST['paginate']) && $_REQUEST['paginate'] == 'f') { + unset($_REQUEST['paginate']); + unset($_POST['paginate']); + unset($_GET['paginate']); + } + + if (isset($_REQUEST['subject'])) { + $this->subject = $_REQUEST['subject']; + } + + } + + public function render() { + $conf = $this->conf; + $lang = $this->lang; + $misc = $this->misc; + $data = $misc->getDatabaseAccessor(); + + // Check to see if pagination has been specified. In that case, send to display + // script for pagination + /* if a file is given or the request is an explain, do not paginate */ + if (isset($_REQUEST['paginate']) && + !(isset($_FILES['script']) && + $_FILES['script']['size'] > 0) && + (preg_match('/^\s*explain/i', $this->query) == 0)) { + + $display_controller = new DisplayController($this->getContainer()); + + return $display_controller->render(); + } + + $misc->printHeader($lang['strqueryresults']); + $misc->printBody(); + $this->printTrail('database'); + $misc->printTitle($lang['strqueryresults']); + + // Set the schema search path + if (isset($_REQUEST['search_path'])) { + if ($data->setSearchPath(array_map('trim', explode(',', $_REQUEST['search_path']))) != 0) { + return $misc->printFooter(); + } + } + + // May as well try to time the query + if (function_exists('microtime')) { + list($usec, $sec) = explode(' ', microtime()); + $this->start_time = ((float) $usec + (float) $sec); + } + + $this->doDefault(); + + $this->printFooter(); + } + + private function execute_script() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + $_connection = $this->getConnection(); + + /** + * This is a callback function to display the result of each separate query + * @param ADORecordSet $rs The recordset returned by the script execetor + */ + $sqlCallback = function ($query, $rs, $lineno) use ($data, $misc, $lang, $_connection) { + + // Check if $rs is false, if so then there was a fatal error + if ($rs === false) { + echo htmlspecialchars($_FILES['script']['name']), ':', $lineno, ': ', nl2br(htmlspecialchars($_connection->getLastError())), "<br/>\n"; + } else { + // Print query results + switch (pg_result_status($rs)) { + case PGSQL_TUPLES_OK: + // If rows returned, then display the results + $num_fields = pg_numfields($rs); + echo "<p><table>\n<tr>"; + for ($k = 0; $k < $num_fields; $k++) { + echo "<th class=\"data\">", $misc->printVal(pg_fieldname($rs, $k)), "</th>"; + } + + $i = 0; + $row = pg_fetch_row($rs); + while ($row !== false) { + $id = (($i % 2) == 0 ? '1' : '2'); + echo "<tr class=\"data{$id}\">\n"; + foreach ($row as $k => $v) { + echo "<td style=\"white-space:nowrap;\">", $misc->printVal($v, pg_fieldtype($rs, $k), ['null' => true]), "</td>"; + } + echo "</tr>\n"; + $row = pg_fetch_row($rs); + $i++; + } + ; + echo "</table><br/>\n"; + echo $i, " {$lang['strrows']}</p>\n"; + break; + case PGSQL_COMMAND_OK: + // If we have the command completion tag + if (version_compare(phpversion(), '4.3', '>=')) { + echo htmlspecialchars(pg_result_status($rs, PGSQL_STATUS_STRING)), "<br/>\n"; + } + // Otherwise if any rows have been affected + elseif ($data->conn->Affected_Rows() > 0) { + echo $data->conn->Affected_Rows(), " {$lang['strrowsaff']}<br/>\n"; + } + // Otherwise output nothing... + break; + case PGSQL_EMPTY_QUERY: + break; + default: + break; + } + } + }; + + return $data->executeScript('script', $sqlCallback); + } + + private function execute_query() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + // Set fetch mode to NUM so that duplicate field names are properly returned + $data->conn->setFetchMode(ADODB_FETCH_NUM); + $rs = $data->conn->Execute($this->query); + + // $rs will only be an object if there is no error + if (is_object($rs)) { + // Request was run, saving it in history + if (!isset($_REQUEST['nohistory'])) { + $misc->saveScriptHistory($this->query); + } + + // Now, depending on what happened do various things + + // First, if rows returned, then display the results + if ($rs->recordCount() > 0) { + echo "<table>\n<tr>"; + foreach ($rs->fields as $k => $v) { + $finfo = $rs->fetchField($k); + echo "<th class=\"data\">", $misc->printVal($finfo->name), "</th>"; + } + echo "</tr>\n"; + $i = 0; + while (!$rs->EOF) { + $id = (($i % 2) == 0 ? '1' : '2'); + echo "<tr class=\"data{$id}\">\n"; + foreach ($rs->fields as $k => $v) { + $finfo = $rs->fetchField($k); + echo "<td style=\"white-space:nowrap;\">", $misc->printVal($v, $finfo->type, ['null' => true]), "</td>"; + } + echo "</tr>\n"; + $rs->moveNext(); + $i++; + } + echo "</table>\n"; + echo "<p>", $rs->recordCount(), " {$lang['strrows']}</p>\n"; + } else if ($data->conn->Affected_Rows() > 0) { + // Otherwise if any rows have been affected + echo "<p>", $data->conn->Affected_Rows(), " {$lang['strrowsaff']}</p>\n"; + } else { + // Otherwise nodata to print + echo '<p>', $lang['strnodata'], "</p>\n"; + } + + } + } + + public function doDefault() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + // Execute the query. If it's a script upload, special handling is necessary + if (isset($_FILES['script']) && $_FILES['script']['size'] > 0) { + return $this->execute_script(); + } else { + return $this->execute_query(); + + } + + } + + public function printFooter() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + // May as well try to time the query + if ($this->start_time !== null) { + list($usec, $sec) = explode(' ', microtime()); + $end_time = ((float) $usec + (float) $sec); + // Get duration in milliseconds, round to 3dp's + $this->duration = number_format(($end_time - $this->start_time) * 1000, 3); + } + + // Reload the browser as we may have made schema changes + $misc->setReloadBrowser(true); + + // Display duration if we know it + if ($this->duration !== null) { + echo "<p>", sprintf($lang['strruntime'], $this->duration), "</p>\n"; + } + + echo "<p>{$lang['strsqlexecuted']}</p>\n"; + + $navlinks = []; + $fields = [ + 'server' => $_REQUEST['server'], + 'database' => $_REQUEST['database'], + ]; + + if (isset($_REQUEST['schema'])) { + $fields['schema'] = $_REQUEST['schema']; + } + + // Return + if (isset($_REQUEST['return'])) { + $urlvars = $misc->getSubjectParams($_REQUEST['return']); + $navlinks['back'] = [ + 'attr' => [ + 'href' => [ + 'url' => $urlvars['url'], + 'urlvars' => $urlvars['params'], + ], + ], + 'content' => $lang['strback'], + ]; + } + + // Edit + $navlinks['alter'] = [ + 'attr' => [ + 'href' => [ + 'url' => 'database.php', + 'urlvars' => array_merge($fields, [ + 'action' => 'sql', + ]), + ], + ], + 'content' => $lang['streditsql'], + ]; + + // Create view and download + if ($this->query !== '' && isset($rs) && is_object($rs) && $rs->recordCount() > 0) { + // Report views don't set a schema, so we need to disable create view in that case + if (isset($_REQUEST['schema'])) { + $navlinks['createview'] = [ + 'attr' => [ + 'href' => [ + 'url' => 'views.php', + 'urlvars' => array_merge($fields, [ + 'action' => 'create', + ]), + ], + ], + 'content' => $lang['strcreateview'], + ]; + } + + if (isset($_REQUEST['search_path'])) { + $fields['search_path'] = $_REQUEST['search_path']; + } + + $navlinks['download'] = [ + 'attr' => [ + 'href' => [ + 'url' => 'dataexport.php', + 'urlvars' => $fields, + ], + ], + 'content' => $lang['strdownload'], + ]; + } + + $this->printNavLinks($navlinks, 'sql-form', get_defined_vars()); + + return $misc->printFooter(); + } +} diff --git a/src/controllers/SchemaController.php b/src/controllers/SchemaController.php index e1d81cfa..e879d5b3 100644 --- a/src/controllers/SchemaController.php +++ b/src/controllers/SchemaController.php @@ -9,17 +9,142 @@ use \PHPPgAdmin\Decorators\Decorator; class SchemaController extends BaseController { public $_name = 'SchemaController'; + public function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + + if ($action == 'tree') { + return $this->doTree(); + } else if ($action == 'subtree') { + return $this->doSubTree(); + } + + $misc->printHeader($lang['strschemas']); + $misc->printBody(); + + if (isset($_POST['cancel'])) { + $action = ''; + } + + switch ($action) { + case 'create': + if (isset($_POST['create'])) { + $this->doSaveCreate(); + } else { + $this->doCreate(); + } + + break; + case 'alter': + if (isset($_POST['alter'])) { + $this->doSaveAlter(); + } else { + $this->doAlter(); + } + + break; + case 'drop': + if (isset($_POST['drop'])) { + $this->doDrop(false); + } else { + $this->doDrop(true); + } + + break; + case 'export': + $this->doExport(); + break; + default: + $this->doDefault(); + break; + } + + $misc->printFooter(); + + } + /** - * Show default list of schemas in the database + * Generate XML for the browser tree. */ + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $schemas = $data->getSchemas(); + + $reqvars = $misc->getRequestVars('schema'); + + $attrs = [ + 'text' => Decorator::field('nspname'), + 'icon' => 'Schema', + 'toolTip' => Decorator::field('nspcomment'), + 'action' => Decorator::redirecturl('redirect.php', + $reqvars, + [ + 'subject' => 'schema', + 'schema' => Decorator::field('nspname'), + ] + ), + 'branch' => Decorator::url('schemas.php', + $reqvars, + [ + 'action' => 'subtree', + 'schema' => Decorator::field('nspname'), + ] + ), + ]; + + $misc->printTree($schemas, $attrs, 'schemas'); + + } + + function doSubTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $tabs = $misc->getNavTabs('schema'); + + $items = $misc->adjustTabsForTree($tabs); + + $reqvars = $misc->getRequestVars('schema'); + + $attrs = [ + 'text' => Decorator::field('title'), + 'icon' => Decorator::field('icon'), + 'action' => Decorator::actionurl(Decorator::field('url'), + $reqvars, + Decorator::field('urlvars', []) + ), + 'branch' => Decorator::url(Decorator::field('url'), + $reqvars, + Decorator::field('urlvars'), + ['action' => 'tree'] + ), + ]; + + $misc->printTree($items, $attrs, 'schema'); + + } + + /** + * Show default list of schemas in the database + */ public function doDefault($msg = '') { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('database'); - $misc->printTabs('database', 'schemas'); + $this->printTrail('database'); + $this->printTabs('database', 'schemas'); $misc->printMsg($msg); // Check that the DB actually supports schemas @@ -93,9 +218,9 @@ class SchemaController extends BaseController { unset($actions['alter']); } - echo $misc->printTable($schemas, $columns, $actions, 'schemas-schemas', $lang['strnoschemas']); + echo $this->printTable($schemas, $columns, $actions, 'schemas-schemas', $lang['strnoschemas']); - $misc->printNavLinks(['create' => [ + $this->printNavLinks(['create' => [ 'attr' => [ 'href' => [ 'url' => 'schemas.php', @@ -110,12 +235,13 @@ class SchemaController extends BaseController { ]], 'schemas-schemas', get_defined_vars()); } -/** - * Displays a screen where they can enter a new schema - */ + /** + * Displays a screen where they can enter a new schema + */ public function doCreate($msg = '') { - global $data, $misc; - global $lang; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); $server_info = $misc->getServerInfo(); @@ -138,7 +264,7 @@ class SchemaController extends BaseController { // Fetch all users from the database $users = $data->getUsers(); - $misc->printTrail('database'); + $this->printTrail('database'); $misc->printTitle($lang['strcreateschema'], 'pg.schema.create'); $misc->printMsg($msg); @@ -172,11 +298,13 @@ class SchemaController extends BaseController { echo "</form>\n"; } -/** - * Actually creates the new schema in the database - */ + /** + * Actually creates the new schema in the database + */ public function doSaveCreate() { - global $data, $lang, $_reload_browser; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); // Check that they've given a name if ($_POST['formName'] == '') { @@ -193,14 +321,17 @@ class SchemaController extends BaseController { } } -/** - * Display a form to permit editing schema properies. - * TODO: permit changing owner - */ + /** + * Display a form to permit editing schema properies. + * TODO: permit changing owner + */ public function doAlter($msg = '') { - global $data, $misc, $lang; - $misc->printTrail('schema'); + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $this->printTrail('schema'); $misc->printTitle($lang['stralter'], 'pg.schema.alter'); $misc->printMsg($msg); @@ -264,11 +395,13 @@ class SchemaController extends BaseController { } } -/** - * Save the form submission containing changes to a schema - */ + /** + * Save the form submission containing changes to a schema + */ public function doSaveAlter($msg = '') { - global $data, $misc, $lang, $_reload_browser; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); $status = $data->updateSchema($_POST['schema'], $_POST['comment'], $_POST['name'], $_POST['owner']); if ($status == 0) { @@ -280,12 +413,13 @@ class SchemaController extends BaseController { } -/** - * Show confirmation of drop and perform actual drop - */ + /** + * Show confirmation of drop and perform actual drop + */ public function doDrop($confirm) { - global $data, $misc; - global $lang, $_reload_browser; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); if (empty($_REQUEST['nsp']) && empty($_REQUEST['ma'])) { $this->doDefault($lang['strspecifyschematodrop']); @@ -293,7 +427,7 @@ class SchemaController extends BaseController { } if ($confirm) { - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strdrop'], 'pg.schema.drop'); echo '<form action="/src/views/schemas.php" method="post">' . "\n"; @@ -353,15 +487,16 @@ class SchemaController extends BaseController { } } -/** - * Displays options for database download - */ + /** + * Displays options for database download + */ public function doExport($msg = '') { - global $data, $misc; - global $lang; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); - $misc->printTrail('schema'); - $misc->printTabs('schema', 'export'); + $this->printTrail('schema'); + $this->printTabs('schema', 'export'); $misc->printMsg($msg); echo '<form action="/src/views/dbexport.php" method="post">' . "\n"; diff --git a/src/controllers/SequenceController.php b/src/controllers/SequenceController.php index db988c60..94e78340 100644 --- a/src/controllers/SequenceController.php +++ b/src/controllers/SequenceController.php @@ -9,17 +9,128 @@ use \PHPPgAdmin\Decorators\Decorator; class SequenceController extends BaseController { public $_name = 'SequenceController'; -/** - * Display list of all sequences in the database/schema - */ + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + + // Print header + $misc->printHeader($lang['strsequences']); + $misc->printBody(); + + switch ($action) { + case 'create': + $this->doCreateSequence(); + break; + case 'save_create_sequence': + if (isset($_POST['create'])) { + $this->doSaveCreateSequence(); + } else { + $this->doDefault(); + } + + break; + case 'properties': + $this->doProperties(); + break; + case 'drop': + if (isset($_POST['drop'])) { + $this->doDrop(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + case 'restart': + $this->doRestart(); + break; + case 'reset': + $this->doReset(); + break; + case 'nextval': + $this->doNextval(); + break; + case 'setval': + if (isset($_POST['setval'])) { + $this->doSaveSetval(); + } else { + $this->doDefault(); + } + + break; + case 'confirm_setval': + $this->doSetval(); + break; + case 'alter': + if (isset($_POST['alter'])) { + $this->doSaveAlter(); + } else { + $this->doDefault(); + } + + break; + case 'confirm_alter': + $this->doAlter(); + break; + default: + $this->doDefault(); + break; + } + + // Print footer + return $misc->printFooter(); + + } + + /** + * Generate XML for the browser tree. + */ + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $sequences = $data->getSequences(); + + $reqvars = $misc->getRequestVars('sequence'); + + $attrs = [ + 'text' => Decorator::field('seqname'), + 'icon' => 'Sequence', + 'toolTip' => Decorator::field('seqcomment'), + 'action' => Decorator::actionurl('sequences.php', + $reqvars, + [ + 'action' => 'properties', + 'sequence' => Decorator::field('seqname'), + ] + ), + ]; + + return $misc->printTree($sequences, $attrs, 'sequences'); + } + + /** + * Display list of all sequences in the database/schema + */ public function doDefault($msg = '') { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('schema'); - $misc->printTabs('schema', 'sequences'); + $this->printTrail('schema'); + $this->printTabs('schema', 'sequences'); $misc->printMsg($msg); // Get all sequences @@ -90,9 +201,9 @@ class SequenceController extends BaseController { ], ]; - echo $misc->printTable($sequences, $columns, $actions, 'sequences-sequences', $lang['strnosequences']); + echo $this->printTable($sequences, $columns, $actions, 'sequences-sequences', $lang['strnosequences']); - $misc->printNavLinks(['create' => [ + $this->printNavLinks(['create' => [ 'attr' => [ 'href' => [ 'url' => 'sequences.php', @@ -116,7 +227,7 @@ class SequenceController extends BaseController { $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('sequence'); + $this->printTrail('sequence'); $misc->printTitle($lang['strproperties'], 'pg.sequence'); $misc->printMsg($msg); @@ -256,7 +367,7 @@ class SequenceController extends BaseController { unset($navlinks['restart']); } - $misc->printNavLinks($navlinks, 'sequences-properties', get_defined_vars()); + $this->printNavLinks($navlinks, 'sequences-properties', get_defined_vars()); } else { echo "<p>{$lang['strnodata']}</p>\n"; } @@ -278,7 +389,7 @@ class SequenceController extends BaseController { } if ($confirm) { - $misc->printTrail('sequence'); + $this->printTrail('sequence'); $misc->printTitle($lang['strdrop'], 'pg.sequence.drop'); $misc->printMsg($msg); @@ -372,7 +483,7 @@ class SequenceController extends BaseController { $_POST['formCacheValue'] = ''; } - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strcreatesequence'], 'pg.sequence.create'); $misc->printMsg($msg); @@ -521,7 +632,7 @@ class SequenceController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('sequence'); + $this->printTrail('sequence'); $misc->printTitle($lang['strsetval'], 'pg.sequence'); $misc->printMsg($msg); @@ -625,7 +736,7 @@ class SequenceController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('sequence'); + $this->printTrail('sequence'); $misc->printTitle($lang['stralter'], 'pg.sequence.alter'); $misc->printMsg($msg); diff --git a/src/controllers/ServerController.php b/src/controllers/ServerController.php new file mode 100644 index 00000000..994fe602 --- /dev/null +++ b/src/controllers/ServerController.php @@ -0,0 +1,205 @@ +<?php + +namespace PHPPgAdmin\Controller; +use \PHPPgAdmin\Decorators\Decorator; + +/** + * Base controller class + */ +class ServerController extends BaseController { + public $_name = 'ServerController'; + public $table_place = 'servers-servers'; + public $query = ''; + public $subject = ''; + public $start_time = null; + public $duration = null; + + /* Constructor */ + function __construct(\Slim\Container $container) { + parent::__construct($container); + + // Prevent timeouts on large exports (non-safe mode only) + if (!ini_get('safe_mode')) { + set_time_limit(0); + } + } + + function doLogout() { + + $plugin_manager = $this->plugin_manager; + $lang = $this->lang; + $misc = $this->misc; + $conf = $this->conf; + $data = $misc->getDatabaseAccessor(); + + $plugin_manager->do_hook('logout', $_REQUEST['logoutServer']); + + $server_info = $misc->getServerInfo($_REQUEST['logoutServer']); + $misc->setServerInfo(null, null, $_REQUEST['logoutServer']); + + unset($_SESSION['sharedUsername'], $_SESSION['sharedPassword']); + + $misc->setReloadBrowser(true); + + echo sprintf($lang['strlogoutmsg'], $server_info['desc']); + + } + + function doDefault($msg = '') { + + $lang = $this->lang; + $conf = $this->conf; + $misc = $this->misc; + $data = $misc->getDatabaseAccessor(); + + $this->printTabs('root', 'servers'); + $misc->printMsg($msg); + $group = isset($_GET['group']) ? $_GET['group'] : false; + + $groups = $misc->getServersGroups(true, $group); + $columns = [ + 'group' => [ + 'title' => $lang['strgroup'], + 'field' => Decorator::field('desc'), + 'url' => 'servers.php?', + 'vars' => ['group' => 'id'], + ], + ]; + $actions = []; + if (($group !== false) and (isset($conf['srv_groups'][$group])) and ($groups->recordCount() > 0)) { + $misc->printTitle(sprintf($lang['strgroupgroups'], htmlentities($conf['srv_groups'][$group]['desc'], ENT_QUOTES, 'UTF-8'))); + } + $this->printTable($groups, $columns, $actions, $this->table_place); + $servers = $misc->getServers(true, $group); + + $svPre = function (&$rowdata) use ($actions) { + $actions['logout']['disable'] = empty($rowdata->fields['username']); + return $actions; + }; + + $columns = [ + 'server' => [ + 'title' => $lang['strserver'], + 'field' => Decorator::field('desc'), + 'url' => "/redirect/server?", + 'vars' => ['server' => 'id'], + ], + 'host' => [ + 'title' => $lang['strhost'], + 'field' => Decorator::field('host'), + ], + 'port' => [ + 'title' => $lang['strport'], + 'field' => Decorator::field('port'), + ], + 'username' => [ + 'title' => $lang['strusername'], + 'field' => Decorator::field('username'), + ], + 'actions' => [ + 'title' => $lang['stractions'], + ], + ]; + + $actions = [ + 'logout' => [ + 'content' => $lang['strlogout'], + 'attr' => [ + 'href' => [ + 'url' => 'servers.php', + 'urlvars' => [ + 'action' => 'logout', + 'logoutServer' => Decorator::field('id'), + ], + ], + ], + ], + ]; + + if (($group !== false) and isset($conf['srv_groups'][$group])) { + $misc->printTitle(sprintf($lang['strgroupservers'], htmlentities($conf['srv_groups'][$group]['desc'], ENT_QUOTES, 'UTF-8')), null); + $actions['logout']['attr']['href']['urlvars']['group'] = $group; + } + echo $this->printTable($servers, $columns, $actions, $this->table_place, $lang['strnoobjects'], $svPre); + + } + + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + + $nodes = []; + $group_id = isset($_GET['group']) ? $_GET['group'] : false; + + /* root with srv_groups */ + if (isset($conf['srv_groups']) and count($conf['srv_groups']) > 0 + and $group_id === false) { + $nodes = $misc->getServersGroups(true); + } else if (isset($conf['srv_groups']) and $group_id !== false) { + /* group subtree */ + if ($group_id !== 'all') { + $nodes = $misc->getServersGroups(false, $group_id); + } + + $nodes = array_merge($nodes, $misc->getServers(false, $group_id)); + $nodes = new \PHPPgAdmin\ArrayRecordSet($nodes); + } else { + /* no srv_group */ + $nodes = $misc->getServers(true, false); + } + + $reqvars = $misc->getRequestVars('server'); + + $attrs = [ + 'text' => Decorator::field('desc'), + + // Show different icons for logged in/out + 'icon' => Decorator::field('icon'), + + 'toolTip' => Decorator::field('id'), + + 'action' => Decorator::field('action'), + + // Only create a branch url if the user has + // logged into the server. + 'branch' => Decorator::field('branch'), + ]; + \PC::debug(['attrs' => $attrs, 'nodes' => $nodes], __CLASS__ . '::' . __METHOD__); + return $misc->printTree($nodes, $attrs, 'servers'); + + } + + public function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + + if ($action == 'tree') { + return $this->doTree(); + } + + $msg = $this->msg; + $data = $misc->getDatabaseAccessor(); + + $misc->printHeader($this->lang['strservers'], null); + $misc->printBody(); + $this->printTrail('root'); + + switch ($action) { + case 'logout': + $this->doLogout(); + + break; + default: + $this->doDefault($msg); + break; + } + + return $misc->printFooter(); + + } + +} diff --git a/src/controllers/TableController.php b/src/controllers/TableController.php index 7ed7498e..9103d582 100644 --- a/src/controllers/TableController.php +++ b/src/controllers/TableController.php @@ -8,12 +8,392 @@ use \PHPPgAdmin\Decorators\Decorator; */ class TableController extends BaseController { use AdminTrait; - public $script = 'table.php'; - public $_name = 'TableController'; + public $script = 'tables.php'; + public $_name = 'TableController'; + public $table_place = 'tables-tables'; + + public function render() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + + if ($action == 'tree') { + return $this->doTree(); + } else if ($action == 'subtree') { + return $this->doSubTree(); + } + + $data = $misc->getDatabaseAccessor(); + + $misc->printHeader($lang['strtables'], null, true, 'datatables_header.twig'); + $misc->printBody(); + + switch ($action) { + case 'create': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doCreate(); + } + + break; + case 'createlike': + $this->doCreateLike(false); + break; + case 'confcreatelike': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doCreateLike(true); + } + + break; + case 'selectrows': + if (!isset($_POST['cancel'])) { + $this->doSelectRows(false); + } else { + $this->doDefault(); + } + + break; + case 'confselectrows': + $this->doSelectRows(true); + break; + case 'insertrow': + if (!isset($_POST['cancel'])) { + $this->doInsertRow(false); + } else { + $this->doDefault(); + } + + break; + case 'confinsertrow': + $this->doInsertRow(true); + break; + case 'empty': + if (isset($_POST['empty'])) { + $this->doEmpty(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_empty': + $this->doEmpty(true); + break; + case 'drop': + if (isset($_POST['drop'])) { + $this->doDrop(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + default: + if ($this->adminActions($action, 'table') === false) { + $this->doDefault(); + } + + break; + } + + return $misc->printFooter(); + + } /** - * Displays a screen where they can enter a new table + * Generate XML for the browser tree. */ + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + //\PC::debug($misc->getDatabase(), 'getDatabase'); + + $tables = $data->getTables(); + + $reqvars = $misc->getRequestVars('table'); + + $attrs = [ + 'text' => Decorator::field('relname'), + 'icon' => 'Table', + 'iconAction' => Decorator::url('display.php', + $reqvars, + ['table' => Decorator::field('relname')] + ), + 'toolTip' => Decorator::field('relcomment'), + 'action' => Decorator::redirecturl('redirect.php', + $reqvars, + ['table' => Decorator::field('relname')] + ), + 'branch' => Decorator::url('tables.php', + $reqvars, + [ + 'action' => 'subtree', + 'table' => Decorator::field('relname'), + ] + ), + ]; + + return $misc->printTree($tables, $attrs, 'tables'); + } + + function doSubTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $tabs = $misc->getNavTabs('table'); + $items = $misc->adjustTabsForTree($tabs); + $reqvars = $misc->getRequestVars('table'); + + $attrs = [ + 'text' => Decorator::field('title'), + 'icon' => Decorator::field('icon'), + 'action' => Decorator::actionurl( + Decorator::field('url'), + $reqvars, + Decorator::field('urlvars'), + ['table' => $_REQUEST['table']] + ), + 'branch' => Decorator::ifempty( + Decorator::field('branch'), '', Decorator::url(Decorator::field('url'), $reqvars, [ + 'action' => 'tree', + 'table' => $_REQUEST['table'], + ] + ) + ), + ]; + + return $misc->printTree($items, $attrs, 'table'); + } + + /** + * Show default list of tables in the database + */ + public function doDefault($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $this->printTrail('schema'); + $this->printTabs('schema', 'tables'); + $misc->printMsg($msg); + + $tables = $data->getTables(); + + $columns = [ + 'table' => [ + 'title' => $lang['strtable'], + 'field' => Decorator::field('relname'), + 'url' => "/redirect/table?{$misc->href}&", + 'vars' => ['table' => 'relname'], + ], + 'owner' => [ + 'title' => $lang['strowner'], + 'field' => Decorator::field('relowner'), + ], + 'tablespace' => [ + 'title' => $lang['strtablespace'], + 'field' => Decorator::field('tablespace'), + ], + 'tuples' => [ + 'title' => $lang['strestimatedrowcount'], + 'field' => Decorator::field('reltuples'), + 'type' => 'numeric', + ], + 'actions' => [ + 'title' => $lang['stractions'], + ], + 'comment' => [ + 'title' => $lang['strcomment'], + 'field' => Decorator::field('relcomment'), + ], + ]; + + $actions = [ + 'multiactions' => [ + 'keycols' => ['table' => 'relname'], + 'url' => 'tables.php', + 'default' => 'analyze', + ], + 'browse' => [ + 'content' => $lang['strbrowse'], + 'attr' => [ + 'href' => [ + 'url' => 'display.php', + 'urlvars' => [ + 'subject' => 'table', + 'return' => 'table', + 'table' => Decorator::field('relname'), + ], + ], + ], + ], + 'select' => [ + 'content' => $lang['strselect'], + 'attr' => [ + 'href' => [ + 'url' => 'tables.php', + 'urlvars' => [ + 'action' => 'confselectrows', + 'table' => Decorator::field('relname'), + ], + ], + ], + ], + 'insert' => [ + 'content' => $lang['strinsert'], + 'attr' => [ + 'href' => [ + 'url' => 'tables.php', + 'urlvars' => [ + 'action' => 'confinsertrow', + 'table' => Decorator::field('relname'), + ], + ], + ], + ], + 'empty' => [ + 'multiaction' => 'confirm_empty', + 'content' => $lang['strempty'], + 'attr' => [ + 'href' => [ + 'url' => 'tables.php', + 'urlvars' => [ + 'action' => 'confirm_empty', + 'table' => Decorator::field('relname'), + ], + ], + ], + ], + 'alter' => [ + 'content' => $lang['stralter'], + 'attr' => [ + 'href' => [ + 'url' => 'tblproperties.php', + 'urlvars' => [ + 'action' => 'confirm_alter', + 'table' => Decorator::field('relname'), + ], + ], + ], + ], + 'drop' => [ + 'multiaction' => 'confirm_drop', + 'content' => $lang['strdrop'], + 'attr' => [ + 'href' => [ + 'url' => 'tables.php', + 'urlvars' => [ + 'action' => 'confirm_drop', + 'table' => Decorator::field('relname'), + ], + ], + ], + ], + 'vacuum' => [ + 'multiaction' => 'confirm_vacuum', + 'content' => $lang['strvacuum'], + 'attr' => [ + 'href' => [ + 'url' => 'tables.php', + 'urlvars' => [ + 'action' => 'confirm_vacuum', + 'table' => Decorator::field('relname'), + ], + ], + ], + ], + 'analyze' => [ + 'multiaction' => 'confirm_analyze', + 'content' => $lang['stranalyze'], + 'attr' => [ + 'href' => [ + 'url' => 'tables.php', + 'urlvars' => [ + 'action' => 'confirm_analyze', + 'table' => Decorator::field('relname'), + ], + ], + ], + ], + 'reindex' => [ + 'multiaction' => 'confirm_reindex', + 'content' => $lang['strreindex'], + 'attr' => [ + 'href' => [ + 'url' => 'tables.php', + 'urlvars' => [ + 'action' => 'confirm_reindex', + 'table' => Decorator::field('relname'), + ], + ], + ], + ], + //'cluster' TODO ? + ]; + + if (!$data->hasTablespaces()) { + unset($columns['tablespace']); + } + + //\Kint::dump($tables); + + echo $this->printTable($tables, $columns, $actions, $this->table_place, $lang['strnotables']); + + $navlinks = [ + 'create' => [ + 'attr' => [ + 'href' => [ + 'url' => 'tables.php', + 'urlvars' => [ + 'action' => 'create', + 'server' => $_REQUEST['server'], + 'database' => $_REQUEST['database'], + 'schema' => $_REQUEST['schema'], + ], + ], + ], + 'content' => $lang['strcreatetable'], + ], + ]; + + if (($tables->recordCount() > 0) && $data->hasCreateTableLike()) { + $navlinks['createlike'] = [ + 'attr' => [ + 'href' => [ + 'url' => 'tables.php', + 'urlvars' => [ + 'action' => 'createlike', + 'server' => $_REQUEST['server'], + 'database' => $_REQUEST['database'], + 'schema' => $_REQUEST['schema'], + ], + ], + ], + 'content' => $lang['strcreatetablelike'], + ]; + } + $this->printNavLinks($navlinks, 'tables-tables', get_defined_vars()); + + echo $this->view->fetch('table_list_footer.twig', ['table_class' => $this->table_place]); + + } + /** + * Displays a screen where they can enter a new table + */ public function doCreate($msg = '') { $conf = $this->conf; $misc = $this->misc; @@ -52,11 +432,12 @@ class TableController extends BaseController { $tablespaces = $data->getTablespaces(); } - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strcreatetable'], 'pg.table.create'); $misc->printMsg($msg); - echo "<form action=\"/src/views/tables.php\" method=\"post\">\n"; + echo '<form action="/src/views/' . $this->script . '" method="post">'; + echo "\n"; echo "<table>\n"; echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strname']}</th>\n"; echo "\t\t<td class=\"data\"><input name=\"name\" size=\"32\" maxlength=\"{$data->_maxNameLen}\" value=\"", @@ -113,7 +494,7 @@ class TableController extends BaseController { $types = $data->getTypes(true, false, true); $types_for_js = []; - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strcreatetable'], 'pg.table.create'); $misc->printMsg($msg); @@ -272,11 +653,11 @@ class TableController extends BaseController { } } -/** - * Dsiplay a screen where user can create a table from an existing one. - * We don't have to check if pg supports schema cause create table like - * is available under pg 7.4+ which has schema. - */ + /** + * Dsiplay a screen where user can create a table from an existing one. + * We don't have to check if pg supports schema cause create table like + * is available under pg 7.4+ which has schema. + */ public function doCreateLike($confirm, $msg = '') { $conf = $this->conf; $misc = $this->misc; @@ -299,7 +680,7 @@ class TableController extends BaseController { $_REQUEST['tablespace'] = ''; } - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strcreatetable'], 'pg.table.create'); $misc->printMsg($msg); @@ -392,9 +773,9 @@ class TableController extends BaseController { } } -/** - * Ask for select parameters and perform select - */ + /** + * Ask for select parameters and perform select + */ public function doSelectRows($confirm, $msg = '') { $conf = $this->conf; $misc = $this->misc; @@ -402,8 +783,8 @@ class TableController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('table'); - $misc->printTabs('table', 'select'); + $this->printTrail('table'); + $this->printTabs('table', 'select'); $misc->printMsg($msg); $attrs = $data->getTableAttributes($_REQUEST['table']); @@ -476,6 +857,8 @@ class TableController extends BaseController { echo "<input type=\"submit\" name=\"select\" accesskey=\"r\" value=\"{$lang['strselect']}\" />\n"; echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; echo "</form>\n"; + + return; } else { if (!isset($_POST['show'])) { $_POST['show'] = []; @@ -507,15 +890,18 @@ class TableController extends BaseController { $_REQUEST['return'] = 'selectrows'; $misc->setNoOutput(true); - include './display.php'; - exit; + + $display_controller = new DisplayController($this->getContainer()); + + return $display_controller->render(); + } } } -/** - * Ask for insert parameters and then actually insert row - */ + /** + * Ask for insert parameters and then actually insert row + */ public function doInsertRow($confirm, $msg = '') { $conf = $this->conf; $misc = $this->misc; @@ -523,8 +909,8 @@ class TableController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('table'); - $misc->printTabs('table', 'insert'); + $this->printTrail('table'); + $this->printTabs('table', 'insert'); $misc->printMsg($msg); @@ -662,9 +1048,9 @@ class TableController extends BaseController { } -/** - * Show confirmation of empty and perform actual empty - */ + /** + * Show confirmation of empty and perform actual empty + */ public function doEmpty($confirm) { $conf = $this->conf; $misc = $this->misc; @@ -678,7 +1064,7 @@ class TableController extends BaseController { if ($confirm) { if (isset($_REQUEST['ma'])) { - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strempty'], 'pg.table.empty'); echo "<form action=\"/src/views/tables.php\" method=\"post\">\n"; @@ -689,7 +1075,7 @@ class TableController extends BaseController { } } // END mutli empty else { - $misc->printTrail('table'); + $this->printTrail('table'); $misc->printTitle($lang['strempty'], 'pg.table.empty'); echo "<p>", sprintf($lang['strconfemptytable'], $misc->printVal($_REQUEST['table'])), "</p>\n"; @@ -730,9 +1116,9 @@ class TableController extends BaseController { } // END do Empty } -/** - * Show confirmation of drop and perform actual drop - */ + /** + * Show confirmation of drop and perform actual drop + */ public function doDrop($confirm) { $conf = $this->conf; $misc = $this->misc; @@ -748,7 +1134,7 @@ class TableController extends BaseController { //If multi drop if (isset($_REQUEST['ma'])) { - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strdrop'], 'pg.table.drop'); echo "<form action=\"/src/views/tables.php\" method=\"post\">\n"; @@ -759,7 +1145,7 @@ class TableController extends BaseController { } } else { - $misc->printTrail('table'); + $this->printTrail('table'); $misc->printTitle($lang['strdrop'], 'pg.table.drop'); echo "<p>", sprintf($lang['strconfdroptable'], $misc->printVal($_REQUEST['table'])), "</p>\n"; @@ -813,213 +1199,4 @@ class TableController extends BaseController { } // END DROP } // END Function -/** - * Show default list of tables in the database - */ - public function doDefault($msg = '') { - $conf = $this->conf; - $misc = $this->misc; - $lang = $this->lang; - $data = $misc->getDatabaseAccessor(); - - $misc->printTrail('schema'); - $misc->printTabs('schema', 'tables'); - $misc->printMsg($msg); - - $tables = $data->getTables(); - - $columns = [ - 'table' => [ - 'title' => $lang['strtable'], - 'field' => Decorator::field('relname'), - 'url' => "/redirect/table?{$misc->href}&", - 'vars' => ['table' => 'relname'], - ], - 'owner' => [ - 'title' => $lang['strowner'], - 'field' => Decorator::field('relowner'), - ], - 'tablespace' => [ - 'title' => $lang['strtablespace'], - 'field' => Decorator::field('tablespace'), - ], - 'tuples' => [ - 'title' => $lang['strestimatedrowcount'], - 'field' => Decorator::field('reltuples'), - 'type' => 'numeric', - ], - 'actions' => [ - 'title' => $lang['stractions'], - ], - 'comment' => [ - 'title' => $lang['strcomment'], - 'field' => Decorator::field('relcomment'), - ], - ]; - - $actions = [ - 'multiactions' => [ - 'keycols' => ['table' => 'relname'], - 'url' => 'tables.php', - 'default' => 'analyze', - ], - 'browse' => [ - 'content' => $lang['strbrowse'], - 'attr' => [ - 'href' => [ - 'url' => 'display.php', - 'urlvars' => [ - 'subject' => 'table', - 'return' => 'table', - 'table' => Decorator::field('relname'), - ], - ], - ], - ], - 'select' => [ - 'content' => $lang['strselect'], - 'attr' => [ - 'href' => [ - 'url' => 'tables.php', - 'urlvars' => [ - 'action' => 'confselectrows', - 'table' => Decorator::field('relname'), - ], - ], - ], - ], - 'insert' => [ - 'content' => $lang['strinsert'], - 'attr' => [ - 'href' => [ - 'url' => 'tables.php', - 'urlvars' => [ - 'action' => 'confinsertrow', - 'table' => Decorator::field('relname'), - ], - ], - ], - ], - 'empty' => [ - 'multiaction' => 'confirm_empty', - 'content' => $lang['strempty'], - 'attr' => [ - 'href' => [ - 'url' => 'tables.php', - 'urlvars' => [ - 'action' => 'confirm_empty', - 'table' => Decorator::field('relname'), - ], - ], - ], - ], - 'alter' => [ - 'content' => $lang['stralter'], - 'attr' => [ - 'href' => [ - 'url' => 'tblproperties.php', - 'urlvars' => [ - 'action' => 'confirm_alter', - 'table' => Decorator::field('relname'), - ], - ], - ], - ], - 'drop' => [ - 'multiaction' => 'confirm_drop', - 'content' => $lang['strdrop'], - 'attr' => [ - 'href' => [ - 'url' => 'tables.php', - 'urlvars' => [ - 'action' => 'confirm_drop', - 'table' => Decorator::field('relname'), - ], - ], - ], - ], - 'vacuum' => [ - 'multiaction' => 'confirm_vacuum', - 'content' => $lang['strvacuum'], - 'attr' => [ - 'href' => [ - 'url' => 'tables.php', - 'urlvars' => [ - 'action' => 'confirm_vacuum', - 'table' => Decorator::field('relname'), - ], - ], - ], - ], - 'analyze' => [ - 'multiaction' => 'confirm_analyze', - 'content' => $lang['stranalyze'], - 'attr' => [ - 'href' => [ - 'url' => 'tables.php', - 'urlvars' => [ - 'action' => 'confirm_analyze', - 'table' => Decorator::field('relname'), - ], - ], - ], - ], - 'reindex' => [ - 'multiaction' => 'confirm_reindex', - 'content' => $lang['strreindex'], - 'attr' => [ - 'href' => [ - 'url' => 'tables.php', - 'urlvars' => [ - 'action' => 'confirm_reindex', - 'table' => Decorator::field('relname'), - ], - ], - ], - ], - //'cluster' TODO ? - ]; - - if (!$data->hasTablespaces()) { - unset($columns['tablespace']); - } - - echo $misc->printTable($tables, $columns, $actions, 'tables-tables', $lang['strnotables']); - - $navlinks = [ - 'create' => [ - 'attr' => [ - 'href' => [ - 'url' => 'tables.php', - 'urlvars' => [ - 'action' => 'create', - 'server' => $_REQUEST['server'], - 'database' => $_REQUEST['database'], - 'schema' => $_REQUEST['schema'], - ], - ], - ], - 'content' => $lang['strcreatetable'], - ], - ]; - - if (($tables->recordCount() > 0) && $data->hasCreateTableLike()) { - $navlinks['createlike'] = [ - 'attr' => [ - 'href' => [ - 'url' => 'tables.php', - 'urlvars' => [ - 'action' => 'createlike', - 'server' => $_REQUEST['server'], - 'database' => $_REQUEST['database'], - 'schema' => $_REQUEST['schema'], - ], - ], - ], - 'content' => $lang['strcreatetablelike'], - ]; - } - $misc->printNavLinks($navlinks, 'tables-tables', get_defined_vars()); - } - } diff --git a/src/controllers/TablePropertyController.php b/src/controllers/TablePropertyController.php index 96deb1bd..2301da45 100644 --- a/src/controllers/TablePropertyController.php +++ b/src/controllers/TablePropertyController.php @@ -9,6 +9,112 @@ use \PHPPgAdmin\Decorators\Decorator; class TablePropertyController extends BaseController { public $_name = 'TablePropertyController'; + public function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + $data = $misc->getDatabaseAccessor(); + $misc->printHeader($lang['strtables'] . ' - ' . $_REQUEST['table']); + $misc->printBody(); + + switch ($action) { + case 'alter': + if (isset($_POST['alter'])) { + $this->doSaveAlter(); + } else { + $this->doDefault(); + } + + break; + case 'confirm_alter': + $this->doAlter(); + break; + case 'import': + $this->doImport(); + break; + case 'export': + $this->doExport(); + break; + case 'add_column': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doAddColumn(); + } + + break; + case 'properties': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doProperties(); + } + + break; + case 'drop': + if (isset($_POST['drop'])) { + $this->doDrop(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + default: + $this->doDefault(); + break; + } + + return $misc->printFooter(); + + } + + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $columns = $data->getTableAttributes($_REQUEST['table']); + $reqvars = $misc->getRequestVars('column'); + + $attrs = [ + 'text' => Decorator::field('attname'), + 'action' => Decorator::actionurl('colproperties.php', + $reqvars, + [ + 'table' => $_REQUEST['table'], + 'column' => Decorator::field('attname'), + ] + ), + 'icon' => 'Column', + 'iconAction' => Decorator::url('display.php', + $reqvars, + [ + 'table' => $_REQUEST['table'], + 'column' => Decorator::field('attname'), + 'query' => Decorator::replace( + 'SELECT "%column%", count(*) AS "count" FROM "%table%" GROUP BY "%column%" ORDER BY "%column%"', + [ + '%column%' => Decorator::field('attname'), + '%table%' => $_REQUEST['table'], + ] + ), + ] + ), + 'toolTip' => Decorator::field('comment'), + ]; + + return $misc->printTree($columns, $attrs, 'tblcolumns'); + } public function doSaveAlter() { $conf = $this->conf; $misc = $this->misc; @@ -37,13 +143,13 @@ class TablePropertyController extends BaseController { // Jump them to the new table name $_REQUEST['table'] = $_POST['name']; // Force a browser reload - $_reload_browser = true; + $misc->setReloadBrowser(true); } // If schema has changed, need to change to the new schema and reload the browser if (!empty($_POST['newschema']) && ($_POST['newschema'] != $data->_schema)) { // Jump them to the new sequence schema $misc->setCurrentSchema($_POST['newschema']); - $_reload_browser = true; + $misc->setReloadBrowser(true); } $this->doDefault($lang['strtablealtered']); } else { @@ -61,7 +167,7 @@ class TablePropertyController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('table'); + $this->printTrail('table'); $misc->printTitle($lang['stralter'], 'pg.table.alter'); $misc->printMsg($msg); @@ -171,8 +277,8 @@ class TablePropertyController extends BaseController { // Determine whether or not the table has an object ID $hasID = $data->hasObjectID($_REQUEST['table']); - $misc->printTrail('table'); - $misc->printTabs('table', 'export'); + $this->printTrail('table'); + $this->printTabs('table', 'export'); $misc->printMsg($msg); echo "<form action=\"/src/views/dataexport.php\" method=\"post\">\n"; @@ -228,8 +334,8 @@ class TablePropertyController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('table'); - $misc->printTabs('table', 'import'); + $this->printTrail('table'); + $this->printTabs('table', 'import'); $misc->printMsg($msg); // Check that file uploads are enabled @@ -312,7 +418,7 @@ class TablePropertyController extends BaseController { $types = $data->getTypes(true, false, true); $types_for_js = []; - $misc->printTrail('table'); + $this->printTrail('table'); $misc->printTitle($lang['straddcolumn'], 'pg.column.add'); $misc->printMsg($msg); @@ -400,7 +506,7 @@ class TablePropertyController extends BaseController { $_POST['type'], $_POST['array'] != '', $_POST['length'], isset($_POST['notnull']), $_POST['default'], $_POST['comment']); if ($status == 0) { - $_reload_browser = true; + $misc->setReloadBrowser(true); $this->doDefault($lang['strcolumnadded']); } else { $_REQUEST['stage'] = 1; @@ -423,7 +529,7 @@ class TablePropertyController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('column'); + $this->printTrail('column'); $misc->printTitle($lang['strdrop'], 'pg.column.drop'); echo "<p>", sprintf($lang['strconfdropcolumn'], $misc->printVal($_REQUEST['column']), @@ -441,7 +547,7 @@ class TablePropertyController extends BaseController { } else { $status = $data->dropColumn($_POST['table'], $_POST['column'], isset($_POST['cascade'])); if ($status == 0) { - $_reload_browser = true; + $misc->setReloadBrowser(true); $this->doDefault($lang['strcolumndropped']); } else { $this->doDefault($lang['strcolumndroppedbad']); @@ -451,9 +557,9 @@ class TablePropertyController extends BaseController { } -/** - * Show default list of columns in the table - */ + /** + * Show default list of columns in the table + */ public function doDefault($msg = '') { $conf = $this->conf; $misc = $this->misc; @@ -509,8 +615,8 @@ class TablePropertyController extends BaseController { return $str; }; - $misc->printTrail('table'); - $misc->printTabs('table', 'columns'); + $this->printTrail('table'); + $this->printTabs('table', 'columns'); $misc->printMsg($msg); // Get table @@ -646,7 +752,7 @@ class TablePropertyController extends BaseController { ], ]; - echo $misc->printTable($attrs, $columns, $actions, 'tblproperties-tblproperties', null, $attPre); + echo $this->printTable($attrs, $columns, $actions, 'tblproperties-tblproperties', null, $attPre); $navlinks = [ 'browse' => [ @@ -756,7 +862,7 @@ class TablePropertyController extends BaseController { 'content' => $lang['stralter'], ], ]; - $misc->printNavLinks($navlinks, 'tblproperties-tblproperties', get_defined_vars()); + $this->printNavLinks($navlinks, 'tblproperties-tblproperties', get_defined_vars()); } diff --git a/src/controllers/TableSpacesController.php b/src/controllers/TableSpacesController.php new file mode 100644 index 00000000..42c8ce57 --- /dev/null +++ b/src/controllers/TableSpacesController.php @@ -0,0 +1,379 @@ +<?php + +namespace PHPPgAdmin\Controller; +use \PHPPgAdmin\Decorators\Decorator; + +/** + * Base controller class + */ +class TableSpacesController extends BaseController { + public $_name = 'TableSpacesController'; + + /** + * Function to allow altering of a tablespace + */ + function doAlter($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $this->printTrail('tablespace'); + $misc->printTitle($lang['stralter'], 'pg.tablespace.alter'); + $misc->printMsg($msg); + + // Fetch tablespace info + $tablespace = $data->getTablespace($_REQUEST['tablespace']); + // Fetch all users + $users = $data->getUsers(); + + if ($tablespace->recordCount() > 0) { + + if (!isset($_POST['name'])) { + $_POST['name'] = $tablespace->fields['spcname']; + } + + if (!isset($_POST['owner'])) { + $_POST['owner'] = $tablespace->fields['spcowner']; + } + + if (!isset($_POST['comment'])) { + $_POST['comment'] = ($data->hasSharedComments()) ? $tablespace->fields['spccomment'] : ''; + } + + echo "<form action=\"/src/views/tablespaces.php\" method=\"post\">\n"; + echo $misc->form; + echo "<table>\n"; + echo "<tr><th class=\"data left required\">{$lang['strname']}</th>\n"; + echo "<td class=\"data1\">"; + echo "<input name=\"name\" size=\"32\" maxlength=\"{$data->_maxNameLen}\" value=\"", + htmlspecialchars($_POST['name']), "\" /></td></tr>\n"; + echo "<tr><th class=\"data left required\">{$lang['strowner']}</th>\n"; + echo "<td class=\"data1\"><select name=\"owner\">"; + while (!$users->EOF) { + $uname = $users->fields['usename']; + echo "<option value=\"", htmlspecialchars($uname), "\"", + ($uname == $_POST['owner']) ? ' selected="selected"' : '', ">", htmlspecialchars($uname), "</option>\n"; + $users->moveNext(); + } + echo "</select></td></tr>\n"; + if ($data->hasSharedComments()) { + echo "<tr><th class=\"data left\">{$lang['strcomment']}</th>\n"; + echo "<td class=\"data1\">"; + echo "<textarea rows=\"3\" cols=\"32\" name=\"comment\">", + htmlspecialchars($_POST['comment']), "</textarea></td></tr>\n"; + } + echo "</table>\n"; + echo "<p><input type=\"hidden\" name=\"action\" value=\"save_edit\" />\n"; + echo "<input type=\"hidden\" name=\"tablespace\" value=\"", htmlspecialchars($_REQUEST['tablespace']), "\" />\n"; + echo "<input type=\"submit\" name=\"alter\" value=\"{$lang['stralter']}\" />\n"; + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; + echo "</form>\n"; + } else { + echo "<p>{$lang['strnodata']}</p>\n"; + } + + } + + /** + * Function to save after altering a tablespace + */ + function doSaveAlter() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + // Check data + if (trim($_POST['name']) == '') { + $this->doAlter($lang['strtablespaceneedsname']); + } else { + $status = $data->alterTablespace($_POST['tablespace'], $_POST['name'], $_POST['owner'], $_POST['comment']); + if ($status == 0) { + // If tablespace has been renamed, need to change to the new name + if ($_POST['tablespace'] != $_POST['name']) { + // Jump them to the new table name + $_REQUEST['tablespace'] = $_POST['name']; + } + $this->doDefault($lang['strtablespacealtered']); + } else { + $this->doAlter($lang['strtablespacealteredbad']); + } + + } + } + + /** + * Show confirmation of drop and perform actual drop + */ + function doDrop($confirm) { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + if ($confirm) { + $this->printTrail('tablespace'); + $misc->printTitle($lang['strdrop'], 'pg.tablespace.drop'); + + echo "<p>", sprintf($lang['strconfdroptablespace'], $misc->printVal($_REQUEST['tablespace'])), "</p>\n"; + + echo "<form action=\"/src/views/tablespaces.php\" method=\"post\">\n"; + echo $misc->form; + echo "<input type=\"hidden\" name=\"action\" value=\"drop\" />\n"; + echo "<input type=\"hidden\" name=\"tablespace\" value=\"", htmlspecialchars($_REQUEST['tablespace']), "\" />\n"; + echo "<input type=\"submit\" name=\"drop\" value=\"{$lang['strdrop']}\" />\n"; + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n"; + echo "</form>\n"; + } else { + $status = $data->droptablespace($_REQUEST['tablespace']); + if ($status == 0) { + $this->doDefault($lang['strtablespacedropped']); + } else { + $this->doDefault($lang['strtablespacedroppedbad']); + } + + } + } + + /** + * Displays a screen where they can enter a new tablespace + */ + function doCreate($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $server_info = $misc->getServerInfo(); + + if (!isset($_POST['formSpcname'])) { + $_POST['formSpcname'] = ''; + } + + if (!isset($_POST['formOwner'])) { + $_POST['formOwner'] = $server_info['username']; + } + + if (!isset($_POST['formLoc'])) { + $_POST['formLoc'] = ''; + } + + if (!isset($_POST['formComment'])) { + $_POST['formComment'] = ''; + } + + // Fetch all users + $users = $data->getUsers(); + + $this->printTrail('server'); + $misc->printTitle($lang['strcreatetablespace'], 'pg.tablespace.create'); + $misc->printMsg($msg); + + echo "<form action=\"/src/views/tablespaces.php\" method=\"post\">\n"; + echo $misc->form; + echo "<table>\n"; + echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strname']}</th>\n"; + echo "\t\t<td class=\"data1\"><input size=\"32\" name=\"formSpcname\" maxlength=\"{$data->_maxNameLen}\" value=\"", htmlspecialchars($_POST['formSpcname']), "\" /></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strowner']}</th>\n"; + echo "\t\t<td class=\"data1\"><select name=\"formOwner\">\n"; + while (!$users->EOF) { + $uname = $users->fields['usename']; + echo "\t\t\t<option value=\"", htmlspecialchars($uname), "\"", + ($uname == $_POST['formOwner']) ? ' selected="selected"' : '', ">", htmlspecialchars($uname), "</option>\n"; + $users->moveNext(); + } + echo "\t\t</select></td>\n\t</tr>\n"; + echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strlocation']}</th>\n"; + echo "\t\t<td class=\"data1\"><input size=\"32\" name=\"formLoc\" value=\"", htmlspecialchars($_POST['formLoc']), "\" /></td>\n\t</tr>\n"; + // Comments (if available) + if ($data->hasSharedComments()) { + echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strcomment']}</th>\n"; + echo "\t\t<td><textarea name=\"formComment\" rows=\"3\" cols=\"32\">", + htmlspecialchars($_POST['formComment']), "</textarea></td>\n\t</tr>\n"; + } + echo "</table>\n"; + echo "<p><input type=\"hidden\" name=\"action\" value=\"save_create\" />\n"; + echo "<input type=\"submit\" value=\"{$lang['strcreate']}\" />\n"; + echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; + echo "</form>\n"; + } + + /** + * Actually creates the new tablespace in the cluster + */ + function doSaveCreate() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + // Check data + if (trim($_POST['formSpcname']) == '') { + $this->doCreate($lang['strtablespaceneedsname']); + } elseif (trim($_POST['formLoc']) == '') { + $this->doCreate($lang['strtablespaceneedsloc']); + } else { + // Default comment to blank if it isn't set + if (!isset($_POST['formComment'])) { + $_POST['formComment'] = null; + } + + $status = $data->createTablespace($_POST['formSpcname'], $_POST['formOwner'], $_POST['formLoc'], $_POST['formComment']); + if ($status == 0) { + $this->doDefault($lang['strtablespacecreated']); + } else { + $this->doCreate($lang['strtablespacecreatedbad']); + } + + } + } + + /** + * Show default list of tablespaces in the cluster + */ + function doDefault($msg = '') { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $this->printTrail('server'); + $this->printTabs('server', 'tablespaces'); + $misc->printMsg($msg); + + $tablespaces = $data->getTablespaces(); + + $columns = [ + 'database' => [ + 'title' => $lang['strname'], + 'field' => \PHPPgAdmin\Decorators\Decorator::field('spcname'), + ], + 'owner' => [ + 'title' => $lang['strowner'], + 'field' => \PHPPgAdmin\Decorators\Decorator::field('spcowner'), + ], + 'location' => [ + 'title' => $lang['strlocation'], + 'field' => \PHPPgAdmin\Decorators\Decorator::field('spclocation'), + ], + 'actions' => [ + 'title' => $lang['stractions'], + ], + ]; + + if ($data->hasSharedComments()) { + $columns['comment'] = [ + 'title' => $lang['strcomment'], + 'field' => \PHPPgAdmin\Decorators\Decorator::field('spccomment'), + ]; + } + + $actions = [ + 'alter' => [ + 'content' => $lang['stralter'], + 'attr' => [ + 'href' => [ + 'url' => 'tablespaces.php', + 'urlvars' => [ + 'action' => 'edit', + 'tablespace' => \PHPPgAdmin\Decorators\Decorator::field('spcname'), + ], + ], + ], + ], + 'drop' => [ + 'content' => $lang['strdrop'], + 'attr' => [ + 'href' => [ + 'url' => 'tablespaces.php', + 'urlvars' => [ + 'action' => 'confirm_drop', + 'tablespace' => \PHPPgAdmin\Decorators\Decorator::field('spcname'), + ], + ], + ], + ], + 'privileges' => [ + 'content' => $lang['strprivileges'], + 'attr' => [ + 'href' => [ + 'url' => 'privileges.php', + 'urlvars' => [ + 'subject' => 'tablespace', + 'tablespace' => \PHPPgAdmin\Decorators\Decorator::field('spcname'), + ], + ], + ], + ], + ]; + + echo $this->printTable($tablespaces, $columns, $actions, 'tablespaces-tablespaces', $lang['strnotablespaces']); + + $this->printNavLinks(['create' => [ + 'attr' => [ + 'href' => [ + 'url' => 'tablespaces.php', + 'urlvars' => [ + 'action' => 'create', + 'server' => $_REQUEST['server'], + ], + ], + ], + 'content' => $lang['strcreatetablespace'], + ]], 'tablespaces-tablespaces', get_defined_vars()); + } + + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + $action->$this->action; + + $misc->printHeader($lang['strtablespaces']); + $misc->printBody(); + + switch ($action) { + case 'save_create': + if (isset($_REQUEST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveCreate(); + } + + break; + case 'create': + $this->doCreate(); + break; + case 'drop': + if (isset($_REQUEST['cancel'])) { + $this->doDefault(); + } else { + $this->doDrop(false); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + case 'save_edit': + if (isset($_REQUEST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveAlter(); + } + + break; + case 'edit': + $this->doAlter(); + break; + default: + $this->doDefault(); + break; + } + + $misc->printFooter(); + } + +} diff --git a/src/controllers/TriggerController.php b/src/controllers/TriggerController.php index 16e22338..a2927362 100644 --- a/src/controllers/TriggerController.php +++ b/src/controllers/TriggerController.php @@ -9,9 +9,107 @@ use \PHPPgAdmin\Decorators\Decorator; class TriggerController extends BaseController { public $_name = 'TriggerController'; -/** - * Function to save after altering a trigger - */ + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + + $misc->printHeader($lang['strtables'] . ' - ' . $_REQUEST['table'] . ' - ' . $lang['strtriggers']); + $misc->printBody(); + + switch ($action) { + case 'alter': + if (isset($_POST['alter'])) { + $this->doSaveAlter(); + } else { + $this->doDefault(); + } + + break; + case 'confirm_alter': + $this->doAlter(); + break; + case 'confirm_enable': + $this->doEnable(true); + break; + case 'confirm_disable': + $this->doDisable(true); + break; + case 'save_create': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveCreate(); + } + + break; + case 'create': + $this->doCreate(); + break; + case 'drop': + if (isset($_POST['yes'])) { + $this->doDrop(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + case 'enable': + if (isset($_POST['yes'])) { + $this->doEnable(false); + } else { + $this->doDefault(); + } + + break; + case 'disable': + if (isset($_POST['yes'])) { + $this->doDisable(false); + } else { + $this->doDefault(); + } + + break; + default: + $this->doDefault(); + break; + } + + return $misc->printFooter(); + + } + + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $triggers = $data->getTriggers($_REQUEST['table']); + + $reqvars = $misc->getRequestVars('table'); + + $attrs = [ + 'text' => Decorator::field('tgname'), + 'icon' => 'Trigger', + ]; + + return $misc->printTree($triggers, $attrs, 'triggers'); + + } + + /** + * Function to save after altering a trigger + */ public function doSaveAlter() { $conf = $this->conf; $misc = $this->misc; @@ -36,7 +134,7 @@ class TriggerController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('trigger'); + $this->printTrail('trigger'); $misc->printTitle($lang['stralter'], 'pg.trigger.alter'); $misc->printMsg($msg); @@ -78,7 +176,7 @@ class TriggerController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('trigger'); + $this->printTrail('trigger'); $misc->printTitle($lang['strdrop'], 'pg.trigger.drop'); echo "<p>", sprintf($lang['strconfdroptrigger'], $misc->printVal($_REQUEST['trigger']), @@ -115,7 +213,7 @@ class TriggerController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('trigger'); + $this->printTrail('trigger'); $misc->printTitle($lang['strenable'], 'pg.table.alter'); echo "<p>", sprintf($lang['strconfenabletrigger'], $misc->printVal($_REQUEST['trigger']), @@ -151,7 +249,7 @@ class TriggerController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('trigger'); + $this->printTrail('trigger'); $misc->printTitle($lang['strdisable'], 'pg.table.alter'); echo "<p>", sprintf($lang['strconfdisabletrigger'], $misc->printVal($_REQUEST['trigger']), @@ -186,7 +284,7 @@ class TriggerController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('table'); + $this->printTrail('table'); $misc->printTitle($lang['strcreatetrigger'], 'pg.trigger.create'); $misc->printMsg($msg); @@ -296,8 +394,8 @@ class TriggerController extends BaseController { return $actions; }; - $misc->printTrail('table'); - $misc->printTabs('table', 'triggers'); + $this->printTrail('table'); + $this->printTabs('table', 'triggers'); $misc->printMsg($msg); $triggers = $data->getTriggers($_REQUEST['table']); @@ -383,9 +481,9 @@ class TriggerController extends BaseController { ]; } - echo $misc->printTable($triggers, $columns, $actions, 'triggers-triggers', $lang['strnotriggers'], $tgPre); + echo $this->printTable($triggers, $columns, $actions, 'triggers-triggers', $lang['strnotriggers'], $tgPre); - $misc->printNavLinks(['create' => [ + $this->printNavLinks(['create' => [ 'attr' => [ 'href' => [ 'url' => 'triggers.php', diff --git a/src/controllers/TypeController.php b/src/controllers/TypeController.php index 7ffe5a1b..23bc2fb6 100644 --- a/src/controllers/TypeController.php +++ b/src/controllers/TypeController.php @@ -9,9 +9,103 @@ use \PHPPgAdmin\Decorators\Decorator; class TypeController extends BaseController { public $_name = 'TypeController'; + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + + $misc->printHeader($lang['strtypes']); + $misc->printBody(); + + switch ($action) { + case 'create_comp': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doCreateComposite(); + } + + break; + case 'create_enum': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doCreateEnum(); + } + + break; + case 'save_create': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveCreate(); + } + + break; + case 'create': + $this->doCreate(); + break; + case 'drop': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doDrop(false); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + case 'properties': + $this->doProperties(); + break; + default: + $this->doDefault(); + break; + } + + return $misc->printFooter(); + + } /** - * Show read only properties for a type + * Generate XML for the browser tree. */ + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $types = $data->getTypes(); + + $reqvars = $misc->getRequestVars('type'); + + $attrs = [ + 'text' => Decorator::field('typname'), + 'icon' => 'Type', + 'toolTip' => Decorator::field('typcomment'), + 'action' => Decorator::actionurl('types.php', + $reqvars, + [ + 'action' => 'properties', + 'type' => Decorator::field('basename'), + ] + ), + ]; + + return $misc->printTree($types, $attrs, 'types'); + + } + + /** + * Show read only properties for a type + */ public function doProperties($msg = '') { $conf = $this->conf; $misc = $this->misc; @@ -20,7 +114,7 @@ class TypeController extends BaseController { // Get type (using base name) $typedata = $data->getType($_REQUEST['type']); - $misc->printTrail('type'); + $this->printTrail('type'); $misc->printTitle($lang['strproperties'], 'pg.type'); $misc->printMsg($msg); @@ -51,7 +145,7 @@ class TypeController extends BaseController { $actions = []; - echo $misc->printTable($attrs, $columns, $actions, 'types-properties', null, $attPre); + echo $this->printTable($attrs, $columns, $actions, 'types-properties', null, $attPre); break; case 'e': @@ -84,7 +178,7 @@ class TypeController extends BaseController { echo "</table>\n"; } - $misc->printNavLinks(['showall' => [ + $this->printNavLinks(['showall' => [ 'attr' => [ 'href' => [ 'url' => 'types.php', @@ -107,11 +201,13 @@ class TypeController extends BaseController { * Show confirmation of drop and perform actual drop */ public function doDrop($confirm) { - global $data, $misc; - global $lang; + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('type'); + $this->printTrail('type'); $misc->printTitle($lang['strdrop'], 'pg.type.drop'); echo "<p>", sprintf($lang['strconfdroptype'], $misc->printVal($_REQUEST['type'])), "</p>\n"; @@ -140,8 +236,10 @@ class TypeController extends BaseController { * Displays a screen where they can enter a new composite type */ public function doCreateComposite($msg = '') { - global $data, $misc; - global $lang; + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); if (!isset($_REQUEST['stage'])) { $_REQUEST['stage'] = 1; @@ -161,7 +259,7 @@ class TypeController extends BaseController { switch ($_REQUEST['stage']) { case 1: - $misc->printTrail('type'); + $this->printTrail('type'); $misc->printTitle($lang['strcreatecomptype'], 'pg.type.create'); $misc->printMsg($msg); @@ -187,7 +285,6 @@ class TypeController extends BaseController { echo "</form>\n"; break; case 2: - global $lang; // Check inputs $fields = trim($_REQUEST['fields']); @@ -203,7 +300,7 @@ class TypeController extends BaseController { $types = $data->getTypes(true, false, true); - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strcreatecomptype'], 'pg.type.create'); $misc->printMsg($msg); @@ -265,7 +362,6 @@ class TypeController extends BaseController { break; case 3: - global $data, $lang; // Check inputs $fields = trim($_REQUEST['fields']); @@ -304,8 +400,10 @@ class TypeController extends BaseController { * Displays a screen where they can enter a new enum type */ public function doCreateEnum($msg = '') { - global $data, $misc; - global $lang; + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); if (!isset($_REQUEST['stage'])) { $_REQUEST['stage'] = 1; @@ -325,7 +423,7 @@ class TypeController extends BaseController { switch ($_REQUEST['stage']) { case 1: - $misc->printTrail('type'); + $this->printTrail('type'); $misc->printTitle($lang['strcreateenumtype'], 'pg.type.create'); $misc->printMsg($msg); @@ -351,7 +449,6 @@ class TypeController extends BaseController { echo "</form>\n"; break; case 2: - global $lang; // Check inputs $values = trim($_REQUEST['values']); @@ -365,7 +462,7 @@ class TypeController extends BaseController { return; } - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strcreateenumtype'], 'pg.type.create'); $misc->printMsg($msg); @@ -397,7 +494,6 @@ class TypeController extends BaseController { break; case 3: - global $data, $lang; // Check inputs $values = trim($_REQUEST['values']); @@ -434,8 +530,10 @@ class TypeController extends BaseController { * Displays a screen where they can enter a new type */ public function doCreate($msg = '') { - global $data, $misc; - global $lang; + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); if (!isset($_POST['typname'])) { $_POST['typname'] = ''; @@ -477,7 +575,7 @@ class TypeController extends BaseController { $funcs = $data->getFunctions(true); $types = $data->getTypes(true); - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strcreatetype'], 'pg.type.create'); $misc->printMsg($msg); @@ -553,8 +651,10 @@ class TypeController extends BaseController { * Actually creates the new type in the database */ public function doSaveCreate() { - global $data; - global $lang; + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); // Check that they've given a name and a length. // Note: We're assuming they've given in and out functions here @@ -589,11 +689,13 @@ class TypeController extends BaseController { * Show default list of types in the database */ public function doDefault($msg = '') { - global $data, $conf, $misc; - global $lang; + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); - $misc->printTrail('schema'); - $misc->printTabs('schema', 'types'); + $this->printTrail('schema'); + $this->printTabs('schema', 'types'); $misc->printMsg($msg); $types = $data->getTypes(); @@ -652,7 +754,7 @@ class TypeController extends BaseController { ], ]; - echo $misc->printTable($types, $columns, $actions, 'types-types', $lang['strnotypes']); + echo $this->printTable($types, $columns, $actions, 'types-types', $lang['strnotypes']); $navlinks = [ 'create' => [ @@ -703,7 +805,7 @@ class TypeController extends BaseController { unset($navlinks['enum']); } - $misc->printNavLinks($navlinks, 'types-types', get_defined_vars()); + $this->printNavLinks($navlinks, 'types-types', get_defined_vars()); } } diff --git a/src/controllers/UserController.php b/src/controllers/UserController.php index 59a9d742..01644ccd 100644 --- a/src/controllers/UserController.php +++ b/src/controllers/UserController.php @@ -26,8 +26,8 @@ class UserController extends BaseController { $userdata = $data->getUser($server_info['username']); $_REQUEST['user'] = $server_info['username']; - $misc->printTrail('user'); - $misc->printTabs('server', 'account'); + $this->printTrail('user'); + $this->printTabs('server', 'account'); $misc->printMsg($msg); if ($userdata->recordCount() > 0) { @@ -47,7 +47,7 @@ class UserController extends BaseController { echo "<p>{$lang['strnodata']}</p>\n"; } - $misc->printNavLinks(['changepassword' => [ + $this->printNavLinks(['changepassword' => [ 'attr' => [ 'href' => [ 'url' => 'users.php', @@ -74,7 +74,7 @@ class UserController extends BaseController { if ($confirm) { $_REQUEST['user'] = $server_info['username']; - $misc->printTrail('user'); + $this->printTrail('user'); $misc->printTitle($lang['strchangepassword'], 'pg.user.alter'); $misc->printMsg($msg); @@ -130,7 +130,7 @@ class UserController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('user'); + $this->printTrail('user'); $misc->printTitle($lang['stralter'], 'pg.user.alter'); $misc->printMsg($msg); @@ -228,7 +228,7 @@ class UserController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('user'); + $this->printTrail('user'); $misc->printTitle($lang['strdrop'], 'pg.user.drop'); echo "<p>", sprintf($lang['strconfdropuser'], $misc->printVal($_REQUEST['username'])), "</p>\n"; @@ -276,7 +276,7 @@ class UserController extends BaseController { $_POST['formExpires'] = ''; } - $misc->printTrail('server'); + $this->printTrail('server'); $misc->printTitle($lang['strcreateuser'], 'pg.user.create'); $misc->printMsg($msg); @@ -339,13 +339,12 @@ class UserController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - function renderUseExpires($val) { - global $lang; + $renderUseExpires = function ($val) use ($lang) { return $val == 'infinity' ? $lang['strnever'] : htmlspecialchars($val); - } + }; - $misc->printTrail('server'); - $misc->printTabs('server', 'users'); + $this->printTrail('server'); + $this->printTabs('server', 'users'); $misc->printMsg($msg); $users = $data->getUsers(); @@ -369,7 +368,7 @@ class UserController extends BaseController { 'title' => $lang['strexpires'], 'field' => Decorator::field('useexpires'), 'type' => 'callback', - 'params' => ['function' => 'renderUseExpires', 'null' => $lang['strnever']], + 'params' => ['function' => $renderUseExpires, 'null' => $lang['strnever']], ], 'defaults' => [ 'title' => $lang['strsessiondefaults'], @@ -407,9 +406,9 @@ class UserController extends BaseController { ], ]; - echo $misc->printTable($users, $columns, $actions, 'users-users', $lang['strnousers']); + echo $this->printTable($users, $columns, $actions, 'users-users', $lang['strnousers']); - $misc->printNavLinks(['create' => [ + $this->printNavLinks(['create' => [ 'attr' => [ 'href' => [ 'url' => 'users.php', diff --git a/src/controllers/ViewController.php b/src/controllers/ViewController.php index 8273fd6f..3f84997f 100644 --- a/src/controllers/ViewController.php +++ b/src/controllers/ViewController.php @@ -7,12 +7,152 @@ use \PHPPgAdmin\Decorators\Decorator; * Base controller class */ class ViewController extends BaseController { - public $script = 'view.php'; - public $_name = 'ViewController'; + public $script = 'views.php'; + public $_name = 'ViewController'; + public $table_place = 'views-views'; + + public function render() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $action = $this->action; + + if ($action == 'tree') { + return $this->doTree(); + } else if ($action == 'subtree') { + return $this->doSubTree(); + } + + $data = $misc->getDatabaseAccessor(); + + $misc->printHeader($lang['strviews']); + $misc->printBody(); + + switch ($action) { + case 'selectrows': + if (!isset($_REQUEST['cancel'])) { + $this->doSelectRows(false); + } else { + $this->doDefault(); + } + + break; + case 'confselectrows': + $this->doSelectRows(true); + break; + case 'save_create_wiz': + if (isset($_REQUEST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveCreateWiz(); + } + + break; + case 'wiz_create': + doWizardCreate(); + break; + case 'set_params_create': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doSetParamsCreate(); + } + + break; + case 'save_create': + if (isset($_REQUEST['cancel'])) { + $this->doDefault(); + } else { + $this->doSaveCreate(); + } + + break; + case 'create': + doCreate(); + break; + case 'drop': + if (isset($_POST['drop'])) { + $this->doDrop(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + default: + $this->doDefault(); + break; + } + + return $misc->printFooter(); + + } /** - * Ask for select parameters and perform select + * Generate XML for the browser tree. */ + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $views = $data->getViews(); + + $reqvars = $misc->getRequestVars('view'); + + $attrs = [ + 'text' => Decorator::field('relname'), + 'icon' => 'View', + 'iconAction' => Decorator::url('display.php', $reqvars, ['view' => Decorator::field('relname')]), + 'toolTip' => Decorator::field('relcomment'), + 'action' => Decorator::redirecturl('redirect.php', $reqvars, ['view' => Decorator::field('relname')]), + 'branch' => Decorator::url('views.php', $reqvars, + [ + 'action' => 'subtree', + 'view' => Decorator::field('relname'), + ] + ), + ]; + + return $misc->printTree($views, $attrs, 'views'); + } + + function doSubTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $tabs = $misc->getNavTabs('view'); + $items = $misc->adjustTabsForTree($tabs); + $reqvars = $misc->getRequestVars('view'); + + $attrs = [ + 'text' => Decorator::field('title'), + 'icon' => Decorator::field('icon'), + 'action' => Decorator::actionurl(Decorator::field('url'), $reqvars, Decorator::field('urlvars'), ['view' => $_REQUEST['view']]), + 'branch' => Decorator::ifempty( + Decorator::field('branch'), '', Decorator::url(Decorator::field('url'), Decorator::field('urlvars'), $reqvars, + [ + 'action' => 'tree', + 'view' => $_REQUEST['view'], + ] + ) + ), + ]; + + return $misc->printTree($items, $attrs, 'view'); + } + + /** + * Ask for select parameters and perform select + */ public function doSelectRows($confirm, $msg = '') { $conf = $this->conf; $misc = $this->misc; @@ -20,13 +160,15 @@ class ViewController extends BaseController { $data = $misc->getDatabaseAccessor(); if ($confirm) { - $misc->printTrail('view'); - $misc->printTabs('view', 'select'); + $this->printTrail('view'); + $this->printTabs('view', 'select'); $misc->printMsg($msg); $attrs = $data->getTableAttributes($_REQUEST['view']); - echo "<form action=\"/src/views/views.php\" method=\"post\" id=\"selectform\">\n"; + echo '<form action="/src/views/' . $this->script . '" method="post" id="selectform">'; + echo "\n"; + if ($attrs->recordCount() > 0) { // JavaScript for select all feature echo "<script type=\"text/javascript\">\n"; @@ -96,6 +238,7 @@ class ViewController extends BaseController { echo "<input type=\"submit\" name=\"select\" accesskey=\"r\" value=\"{$lang['strselect']}\" />\n"; echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; echo "</form>\n"; + return; } else { if (!isset($_POST['show'])) { $_POST['show'] = []; @@ -118,24 +261,27 @@ class ViewController extends BaseController { } if (sizeof($_POST['show']) == 0) { - $this->doSelectRows(true, $lang['strselectneedscol']); + return $this->doSelectRows(true, $lang['strselectneedscol']); } else { // Generate query SQL - $query = $data->getSelectSQL($_REQUEST['view'], array_keys($_POST['show']), - $_POST['values'], $_POST['ops']); + $query = $data->getSelectSQL($_REQUEST['view'], array_keys($_POST['show']), $_POST['values'], $_POST['ops']); + $_REQUEST['query'] = $query; $_REQUEST['return'] = "schema"; + $misc->setNoOutput(true); - include './display.php'; - exit; + + $display_controller = new DisplayController($this->getContainer()); + + return $display_controller->render(); } } } -/** - * Show confirmation of drop and perform actual drop - */ + /** + * Show confirmation of drop and perform actual drop + */ public function doDrop($confirm) { $conf = $this->conf; $misc = $this->misc; @@ -148,7 +294,7 @@ class ViewController extends BaseController { } if ($confirm) { - $misc->printTrail('view'); + $this->printTrail('view'); $misc->printTitle($lang['strdrop'], 'pg.view.drop'); echo "<form action=\"/src/views/views.php\" method=\"post\">\n"; @@ -210,9 +356,9 @@ class ViewController extends BaseController { } -/** - * Sets up choices for table linkage, and which fields to select for the view we're creating - */ + /** + * Sets up choices for table linkage, and which fields to select for the view we're creating + */ public function doSetParamsCreate($msg = '') { $conf = $this->conf; $misc = $this->misc; @@ -232,7 +378,7 @@ class ViewController extends BaseController { $_REQUEST['formComment'] = ''; } - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strcreateviewwiz'], 'pg.view.create'); $misc->printMsg($msg); @@ -362,9 +508,9 @@ class ViewController extends BaseController { } } -/** - * Display a wizard where they can enter a new view - */ + /** + * Display a wizard where they can enter a new view + */ public function doWizardCreate($msg = '') { $conf = $this->conf; $misc = $this->misc; @@ -373,7 +519,7 @@ class ViewController extends BaseController { $tables = $data->getTables(true); - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strcreateviewwiz'], 'pg.view.create'); $misc->printMsg($msg); @@ -401,9 +547,9 @@ class ViewController extends BaseController { echo "</form>\n"; } -/** - * Displays a screen where they can enter a new view - */ + /** + * Displays a screen where they can enter a new view + */ public function doCreate($msg = '') { $conf = $this->conf; $misc = $this->misc; @@ -426,7 +572,7 @@ class ViewController extends BaseController { $_REQUEST['formComment'] = ''; } - $misc->printTrail('schema'); + $this->printTrail('schema'); $misc->printTitle($lang['strcreateview'], 'pg.view.create'); $misc->printMsg($msg); @@ -449,9 +595,9 @@ class ViewController extends BaseController { echo "</form>\n"; } -/** - * Actually creates the new view in the database - */ + /** + * Actually creates the new view in the database + */ public function doSaveCreate() { $conf = $this->conf; $misc = $this->misc; @@ -475,9 +621,9 @@ class ViewController extends BaseController { } } -/** - * Actually creates the new wizard view in the database - */ + /** + * Actually creates the new wizard view in the database + */ public function doSaveCreateWiz() { $conf = $this->conf; $misc = $this->misc; @@ -615,17 +761,17 @@ class ViewController extends BaseController { } } -/** - * Show default list of views in the database - */ + /** + * Show default list of views in the database + */ public function doDefault($msg = '') { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('schema'); - $misc->printTabs('schema', 'views'); + $this->printTrail('schema'); + $this->printTabs('schema', 'views'); $misc->printMsg($msg); $views = $data->getViews(); @@ -716,7 +862,7 @@ class ViewController extends BaseController { ], ]; - echo $misc->printTable($views, $columns, $actions, 'views-views', $lang['strnoviews']); + echo $this->printTable($views, $columns, $actions, $this->table_place, $lang['strnoviews']); $navlinks = [ 'create' => [ @@ -748,7 +894,7 @@ class ViewController extends BaseController { 'content' => $lang['strcreateviewwiz'], ], ]; - $misc->printNavLinks($navlinks, 'views-views', get_defined_vars()); + $this->printNavLinks($navlinks, $this->table_place, get_defined_vars()); } diff --git a/src/controllers/ViewPropertyController.php b/src/controllers/ViewPropertyController.php index 2c67d166..48f6a85e 100644 --- a/src/controllers/ViewPropertyController.php +++ b/src/controllers/ViewPropertyController.php @@ -9,9 +9,120 @@ use \PHPPgAdmin\Decorators\Decorator; class ViewPropertyController extends BaseController { public $_name = 'ViewPropertyController'; -/** - * Function to save after editing a view - */ + function render() { + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + + $action = $this->action; + if ($action == 'tree') { + return $this->doTree(); + } + + $misc->printHeader($lang['strviews'] . ' - ' . $_REQUEST['view']); + $misc->printBody(); + + switch ($action) { + case 'save_edit': + if (isset($_POST['cancel'])) { + $this->doDefinition(); + } else { + $this->doSaveEdit(); + } + + break; + case 'edit': + $this->doEdit(); + break; + case 'export': + $this->doExport(); + break; + case 'definition': + $this->doDefinition(); + break; + case 'properties': + if (isset($_POST['cancel'])) { + $this->doDefault(); + } else { + $this->doProperties(); + } + + break; + case 'alter': + if (isset($_POST['alter'])) { + $this->doAlter(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_alter': + doAlter(true); + break; + case 'drop': + if (isset($_POST['drop'])) { + $this->doDrop(false); + } else { + $this->doDefault(); + } + + break; + case 'confirm_drop': + $this->doDrop(true); + break; + default: + $this->doDefault(); + break; + } + + $misc->printFooter(); + + } + + function doTree() { + + $conf = $this->conf; + $misc = $this->misc; + $lang = $this->lang; + $data = $misc->getDatabaseAccessor(); + + $reqvars = $misc->getRequestVars('column'); + $columns = $data->getTableAttributes($_REQUEST['view']); + + $attrs = [ + 'text' => Decorator::field('attname'), + 'action' => Decorator::actionurl('colproperties.php', + $reqvars, + [ + 'view' => $_REQUEST['view'], + 'column' => Decorator::field('attname'), + ] + ), + 'icon' => 'Column', + 'iconAction' => Decorator::url('display.php', + $reqvars, + [ + 'view' => $_REQUEST['view'], + 'column' => Decorator::field('attname'), + 'query' => Decorator::replace( + 'SELECT "%column%", count(*) AS "count" FROM %view% GROUP BY "%column%" ORDER BY "%column%"', + [ + '%column%' => Decorator::field('attname'), + '%view%' => $_REQUEST['view'], + ] + ), + ] + ), + 'toolTip' => Decorator::field('comment'), + ]; + + return $misc->printTree($columns, $attrs, 'viewcolumns'); + + } + + /** + * Function to save after editing a view + */ public function doSaveEdit() { $conf = $this->conf; $misc = $this->misc; @@ -27,16 +138,16 @@ class ViewPropertyController extends BaseController { } -/** - * Function to allow editing of a view - */ + /** + * Function to allow editing of a view + */ public function doEdit($msg = '') { $conf = $this->conf; $misc = $this->misc; $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('view'); + $this->printTrail('view'); $misc->printTitle($lang['stredit'], 'pg.view.alter'); $misc->printMsg($msg); @@ -83,8 +194,8 @@ class ViewPropertyController extends BaseController { $lang = $this->lang; $data = $misc->getDatabaseAccessor(); - $misc->printTrail('view'); - $misc->printTabs('view', 'export'); + $this->printTrail('view'); + $this->printTabs('view', 'export'); $misc->printMsg($msg); echo "<form action=\"/src/views/dataexport.php\" method=\"post\">\n"; @@ -145,8 +256,8 @@ class ViewPropertyController extends BaseController { // Get view $vdata = $data->getView($_REQUEST['view']); - $misc->printTrail('view'); - $misc->printTabs('view', 'definition'); + $this->printTrail('view'); + $this->printTabs('view', 'definition'); $misc->printMsg($msg); if ($vdata->recordCount() > 0) { @@ -163,7 +274,7 @@ class ViewPropertyController extends BaseController { echo "<p>{$lang['strnodata']}</p>\n"; } - $misc->printNavLinks(['alter' => [ + $this->printNavLinks(['alter' => [ 'attr' => [ 'href' => [ 'url' => 'viewproperties.php', @@ -195,9 +306,8 @@ class ViewPropertyController extends BaseController { switch ($_REQUEST['stage']) { case 1: - global $lang; - $misc->printTrail('column'); + $this->printTrail('column'); $misc->printTitle($lang['stralter'], 'pg.column.alter'); $misc->printMsg($msg); @@ -238,7 +348,6 @@ class ViewPropertyController extends BaseController { break; case 2: - global $data, $lang; // Check inputs if (trim($_REQUEST['field']) == '') { @@ -272,7 +381,7 @@ class ViewPropertyController extends BaseController { if ($confirm) { - $misc->printTrail('view'); + $this->printTrail('view'); $misc->printTitle($lang['stralter'], 'pg.view.alter'); $misc->printMsg($msg); @@ -348,7 +457,6 @@ class ViewPropertyController extends BaseController { } } else { - global $data, $lang, $_reload_browser, $misc; // For databases that don't allow owner change if (!isset($_POST['owner'])) { @@ -396,8 +504,8 @@ class ViewPropertyController extends BaseController { $rowdata->fields['+type'] = $data->formatType($rowdata->fields['type'], $rowdata->fields['atttypmod']); }; - $misc->printTrail('view'); - $misc->printTabs('view', 'columns'); + $this->printTrail('view'); + $this->printTabs('view', 'columns'); $misc->printMsg($msg); // Get view @@ -450,7 +558,7 @@ class ViewPropertyController extends BaseController { ], ]; - echo $misc->printTable($attrs, $columns, $actions, 'viewproperties-viewproperties', null, $attPre); + echo $this->printTable($attrs, $columns, $actions, 'viewproperties-viewproperties', null, $attPre); echo "<br />\n"; @@ -518,7 +626,7 @@ class ViewPropertyController extends BaseController { ], ]; - $misc->printNavLinks($navlinks, 'viewproperties-viewproperties', get_defined_vars()); + $this->printNavLinks($navlinks, 'viewproperties-viewproperties', get_defined_vars()); } } diff --git a/src/classes/database/ADODB_base.php b/src/database/ADODB_base.php index fe052a75..fe052a75 100644 --- a/src/classes/database/ADODB_base.php +++ b/src/database/ADODB_base.php diff --git a/src/classes/database/Connection.php b/src/database/Connection.php index 93a61df0..93a61df0 100755 --- a/src/classes/database/Connection.php +++ b/src/database/Connection.php diff --git a/src/classes/database/Postgres.php b/src/database/Postgres.php index 2bf3ba59..c6df059a 100755 --- a/src/classes/database/Postgres.php +++ b/src/database/Postgres.php @@ -9,7 +9,7 @@ namespace PHPPgAdmin\Database; class Postgres extends ADODB_base { - var $major_version = 9.4; + var $major_version = 9.5; // Max object name length var $_maxNameLen = 63; // Store the current schema @@ -434,7 +434,7 @@ class Postgres extends ADODB_base { } function getHelpPages() { - include_once BASE_PATH . '/help/PostgresDoc94.php'; + include_once BASE_PATH . '/help/PostgresDoc95.php'; return $this->help_page; } @@ -3269,6 +3269,23 @@ class Postgres extends ADODB_base { } /** + * Returns a list of all views in the database + * @return All views + */ + function getMaterializedViews() { + $c_schema = $this->_schema; + $this->clean($c_schema); + $sql = " + SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner, + pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) + WHERE (n.nspname='{$c_schema}') AND (c.relkind = 'm'::\"char\") + ORDER BY relname"; + + return $this->selectSet($sql); + } + /** * Updates a view. * @param $viewname The name fo the view to update * @param $definition The new definition for the view diff --git a/src/classes/database/Postgres74.php b/src/database/Postgres74.php index 0af4d449..0af4d449 100644 --- a/src/classes/database/Postgres74.php +++ b/src/database/Postgres74.php diff --git a/src/classes/database/Postgres80.php b/src/database/Postgres80.php index 37451b11..37451b11 100644 --- a/src/classes/database/Postgres80.php +++ b/src/database/Postgres80.php diff --git a/src/classes/database/Postgres81.php b/src/database/Postgres81.php index ddc81730..ddc81730 100644 --- a/src/classes/database/Postgres81.php +++ b/src/database/Postgres81.php diff --git a/src/classes/database/Postgres82.php b/src/database/Postgres82.php index a0a93c53..a0a93c53 100644 --- a/src/classes/database/Postgres82.php +++ b/src/database/Postgres82.php diff --git a/src/classes/database/Postgres83.php b/src/database/Postgres83.php index 63b22fb0..63b22fb0 100644 --- a/src/classes/database/Postgres83.php +++ b/src/database/Postgres83.php diff --git a/src/classes/database/Postgres84.php b/src/database/Postgres84.php index bbdb3a09..bbdb3a09 100755 --- a/src/classes/database/Postgres84.php +++ b/src/database/Postgres84.php diff --git a/src/classes/database/Postgres90.php b/src/database/Postgres90.php index 58e66828..58e66828 100755 --- a/src/classes/database/Postgres90.php +++ b/src/database/Postgres90.php diff --git a/src/classes/database/Postgres91.php b/src/database/Postgres91.php index e4b48114..e4b48114 100755 --- a/src/classes/database/Postgres91.php +++ b/src/database/Postgres91.php diff --git a/src/classes/database/Postgres92.php b/src/database/Postgres92.php index 8110d4a7..8110d4a7 100644 --- a/src/classes/database/Postgres92.php +++ b/src/database/Postgres92.php diff --git a/src/classes/database/Postgres93.php b/src/database/Postgres93.php index 63c506c2..63c506c2 100644 --- a/src/classes/database/Postgres93.php +++ b/src/database/Postgres93.php diff --git a/src/decorator.inc.php b/src/decorator.inc.php deleted file mode 100644 index d3309e6d..00000000 --- a/src/decorator.inc.php +++ /dev/null @@ -1,86 +0,0 @@ -<?php -// $Id: decorator.inc.php,v 1.8 2007/04/05 11:09:38 mr-russ Exp $ - -// This group of functions and classes provides support for -// resolving values in a lazy manner (ie, as and when required) -// using the Decorator pattern. - -###TODO: Better documentation!!! - -// Construction functions: - -function merge( /* ... */) { - return new \PHPPgAdmin\Decorators\ArrayMergeDecorator(func_get_args()); -} - -function concat( /* ... */) { - return new \PHPPgAdmin\Decorators\ConcatDecorator(func_get_args()); -} - -function callback($callback, $params = null) { - return new \PHPPgAdmin\Decorators\CallbackDecorator($callback, $params); -} - -function ifempty($value, $empty, $full = null) { - return new \PHPPgAdmin\Decorators\IfEmptyDecorator($value, $empty, $full); -} - -function url($base, $vars = null/* ... */) { - // If more than one array of vars is given, - // use an ArrayMergeDecorator to have them merged - // at value evaluation time. - if (func_num_args() > 2) { - $v = func_get_args(); - array_shift($v); - return new \PHPPgAdmin\Decorators\UrlDecorator($base, new \PHPPgAdmin\Decorators\ArrayMergeDecorator($v)); - } - return new \PHPPgAdmin\Decorators\UrlDecorator($base, $vars); -} - -function replace($str, $params) { - return new \PHPPgAdmin\Decorators\replaceDecorator($str, $params); -} - -// Resolving functions: - -function value(&$var, &$fields, $esc = null) { - if (is_a($var, 'PHPPgAdmin\Decorators\Decorator')) { - $val = $var->value($fields); - } else { - $val = &$var; - } - - if (is_string($val)) { - switch ($esc) { - case 'xml': - return strtr($val, [ - '&' => '&', - "'" => ''', '"' => '"', - '<' => '<', '>' => '>', - ]); - case 'html': - return htmlentities($val, ENT_COMPAT, 'UTF-8'); - case 'url': - return urlencode($val); - } - } - return $val; -} - -function value_xml(&$var, &$fields) { - return value($var, $fields, 'xml'); -} - -function value_xml_attr($attr, &$var, &$fields) { - $val = value($var, $fields, 'xml'); - if (!empty($val)) { - return " {$attr}=\"{$val}\""; - } else { - return ''; - } - -} - -function value_url(&$var, &$fields) { - return value($var, $fields, 'url'); -} diff --git a/src/classes/decorators/ActionUrlDecorator.php b/src/decorators/ActionUrlDecorator.php index 5176e02a..01378bad 100644 --- a/src/classes/decorators/ActionUrlDecorator.php +++ b/src/decorators/ActionUrlDecorator.php @@ -12,18 +12,18 @@ class ActionUrlDecorator extends Decorator { } function value($fields) { - $url = value($this->b, $fields); + $url = Decorator::get_sanitized_value($this->b, $fields); if ($url === false) { return ''; } if (!empty($this->q)) { - $queryVars = value($this->q, $fields); + $queryVars = Decorator::get_sanitized_value($this->q, $fields); $sep = '?'; foreach ($queryVars as $var => $value) { - $url .= $sep . value_url($var, $fields) . '=' . value_url($value, $fields); + $url .= $sep . Decorator::value_url($var, $fields) . '=' . Decorator::value_url($value, $fields); $sep = '&'; } } diff --git a/src/classes/decorators/ArrayMergeDecorator.php b/src/decorators/ArrayMergeDecorator.php index 1a5e5a2f..7c1f0489 100644 --- a/src/classes/decorators/ArrayMergeDecorator.php +++ b/src/decorators/ArrayMergeDecorator.php @@ -7,9 +7,9 @@ class ArrayMergeDecorator extends Decorator { } function value($fields) { - $accum = array(); + $accum = []; foreach ($this->m as $var) { - $accum = array_merge($accum, value($var, $fields)); + $accum = array_merge($accum, Decorator::get_sanitized_value($var, $fields)); } return $accum; } diff --git a/src/classes/decorators/BranchUrlDecorator.php b/src/decorators/BranchUrlDecorator.php index 592f3a0b..93a86530 100644 --- a/src/classes/decorators/BranchUrlDecorator.php +++ b/src/decorators/BranchUrlDecorator.php @@ -4,7 +4,7 @@ namespace PHPPgAdmin\Decorators; class BranchUrlDecorator extends Decorator { function __construct($base, $queryVars = null) { - \PC::debug($base, 'BranchUrlDecorator'); + //\PC::debug($base, 'BranchUrlDecorator'); $this->b = $base; if ($queryVars !== null) { @@ -14,19 +14,19 @@ class BranchUrlDecorator extends Decorator { } function value($fields) { - $url = value($this->b, $fields); + $url = Decorator::get_sanitized_value($this->b, $fields); if ($url === false) { return ''; } if (!empty($this->q)) { - $queryVars = value($this->q, $fields); + $queryVars = Decorator::get_sanitized_value($this->q, $fields); $sep = '?'; foreach ($queryVars as $var => $value) { - $varname = value_url($var, $fields); - $varvalue = value_url($value, $fields); + $varname = Decorator::value_url($var, $fields); + $varvalue = Decorator::value_url($value, $fields); if ($varname == 'action') { if ($varvalue == 'subtree') { $url = '/tree/' . str_replace('.php', '/subtree', $url); diff --git a/src/classes/decorators/CallbackDecorator.php b/src/decorators/CallbackDecorator.php index 4207fc0f..a83a063e 100644 --- a/src/classes/decorators/CallbackDecorator.php +++ b/src/decorators/CallbackDecorator.php @@ -5,7 +5,7 @@ class CallbackDecorator extends Decorator { function __construct($callback, $param = null) { $this->fn = $callback; - $this->p = $param; + $this->p = $param; } function value($fields) { diff --git a/src/classes/decorators/ConcatDecorator.php b/src/decorators/ConcatDecorator.php index 6c438e6c..7d242749 100644 --- a/src/classes/decorators/ConcatDecorator.php +++ b/src/decorators/ConcatDecorator.php @@ -9,7 +9,7 @@ class ConcatDecorator extends Decorator { function value($fields) { $accum = ''; foreach ($this->c as $var) { - $accum .= value($var, $fields); + $accum .= Decorator::get_sanitized_value($var, $fields); } return trim($accum); } diff --git a/src/classes/decorators/Decorator.php b/src/decorators/Decorator.php index d1c0bab8..9c6ae694 100644 --- a/src/classes/decorators/Decorator.php +++ b/src/decorators/Decorator.php @@ -10,6 +10,51 @@ class Decorator { return $this->v; } + public static function get_sanitized_value(&$var, &$fields, $esc = null) { + if (is_a($var, 'PHPPgAdmin\Decorators\Decorator')) { + $val = $var->value($fields); + } else { + $val = &$var; + } + + if (is_string($val)) { + switch ($esc) { + case 'xml': + return strtr($val, [ + '&' => '&', + "'" => ''', '"' => '"', + '<' => '<', '>' => '>', + ]); + case 'html': + return htmlentities($val, ENT_COMPAT, 'UTF-8'); + case 'url': + return urlencode($val); + } + } + return $val; + } + + public static function value_xml_attr($attr, &$var, &$fields) { + $val = self::get_sanitized_value($var, $fields, 'xml'); + if (!empty($val)) { + return " {$attr}=\"{$val}\""; + } else { + return ''; + } + + } + + public static function value_url(&$var, &$fields) { + return self::get_sanitized_value($var, $fields, 'url'); + } + + public static function concat( /* ... */) { + return new \PHPPgAdmin\Decorators\ConcatDecorator(func_get_args()); + } + + public static function replace($str, $params) { + return new \PHPPgAdmin\Decorators\replaceDecorator($str, $params); + } public static function field($fieldName, $default = null) { return new FieldDecorator($fieldName, $default); } diff --git a/src/classes/decorators/FieldDecorator.php b/src/decorators/FieldDecorator.php index f97cf860..0f9e36c5 100644 --- a/src/classes/decorators/FieldDecorator.php +++ b/src/decorators/FieldDecorator.php @@ -11,7 +11,7 @@ class FieldDecorator extends Decorator { } function value($fields) { - return isset($fields[$this->f]) ? value($fields[$this->f], $fields) : (isset($this->d) ? $this->d : null); + return isset($fields[$this->f]) ? Decorator::get_sanitized_value($fields[$this->f], $fields) : (isset($this->d) ? $this->d : null); } }
\ No newline at end of file diff --git a/src/classes/decorators/IfEmptyDecorator.php b/src/decorators/IfEmptyDecorator.php index f80754b7..4a690563 100644 --- a/src/classes/decorators/IfEmptyDecorator.php +++ b/src/decorators/IfEmptyDecorator.php @@ -12,11 +12,11 @@ class IfEmptyDecorator extends Decorator { } function value($fields) { - $val = value($this->v, $fields); + $val = Decorator::get_sanitized_value($this->v, $fields); if (empty($val)) { - return value($this->e, $fields); + return Decorator::get_sanitized_value($this->e, $fields); } else { - return isset($this->f) ? value($this->f, $fields) : $val; + return isset($this->f) ? Decorator::get_sanitized_value($this->f, $fields) : $val; } } diff --git a/src/classes/decorators/RedirectUrlDecorator.php b/src/decorators/RedirectUrlDecorator.php index 8f125f90..144a1bff 100644 --- a/src/classes/decorators/RedirectUrlDecorator.php +++ b/src/decorators/RedirectUrlDecorator.php @@ -4,7 +4,7 @@ namespace PHPPgAdmin\Decorators; class RedirectUrlDecorator extends Decorator { function __construct($base, $queryVars = null) { - \PC::debug($base, 'RedirectUrlDecorator'); + //\PC::debug($base, 'RedirectUrlDecorator'); $this->b = $base; if ($queryVars !== null) { @@ -14,19 +14,19 @@ class RedirectUrlDecorator extends Decorator { } function value($fields) { - $url = value($this->b, $fields); + $url = Decorator::get_sanitized_value($this->b, $fields); if ($url === false) { return ''; } if (!empty($this->q)) { - $queryVars = value($this->q, $fields); + $queryVars = Decorator::get_sanitized_value($this->q, $fields); $sep = '?'; foreach ($queryVars as $var => $value) { - $varname = value_url($var, $fields); - $varvalue = value_url($value, $fields); + $varname = Decorator::value_url($var, $fields); + $varvalue = Decorator::value_url($value, $fields); if ($varname == 'subject') { $url = '/' . str_replace('.php', '/' . $varvalue, $url); } diff --git a/src/classes/decorators/UrlDecorator.php b/src/decorators/UrlDecorator.php index 95148615..db5f688e 100644 --- a/src/classes/decorators/UrlDecorator.php +++ b/src/decorators/UrlDecorator.php @@ -12,18 +12,18 @@ class UrlDecorator extends Decorator { } function value($fields) { - $url = value($this->b, $fields); + $url = Decorator::get_sanitized_value($this->b, $fields); if ($url === false) { return ''; } if (!empty($this->q)) { - $queryVars = value($this->q, $fields); + $queryVars = Decorator::get_sanitized_value($this->q, $fields); $sep = '?'; foreach ($queryVars as $var => $value) { - $url .= $sep . value_url($var, $fields) . '=' . value_url($value, $fields); + $url .= $sep . Decorator::value_url($var, $fields) . '=' . Decorator::value_url($value, $fields); $sep = '&'; } } diff --git a/src/classes/decorators/replaceDecorator.php b/src/decorators/replaceDecorator.php index 4909faef..9cf11909 100644 --- a/src/classes/decorators/replaceDecorator.php +++ b/src/decorators/replaceDecorator.php @@ -10,7 +10,7 @@ class replaceDecorator extends Decorator { function value($fields) { $str = $this->s; foreach ($this->p as $k => $v) { - $str = str_replace($k, value($v, $fields), $str); + $str = str_replace($k, Decorator::get_sanitized_value($v, $fields), $str); } return $str; } diff --git a/src/errorhandler.inc.php b/src/errorhandler.inc.php index dee49c70..6e796628 100644 --- a/src/errorhandler.inc.php +++ b/src/errorhandler.inc.php @@ -6,10 +6,6 @@ * $Id: errorhandler.inc.php,v 1.20 2005/11/13 08:39:49 chriskl Exp $ */ -if (!defined('ADODB_ERROR_HANDLER')) { - define('ADODB_ERROR_HANDLER', 'Error_Handler'); -} - /** * Default Error Handler. This will be called with the following params * @@ -25,33 +21,33 @@ function Error_Handler($dbms, $fn, $errno, $errmsg, $p1 = false, $p2 = false) { global $misc, $appName, $appVersion, $appLangFiles; switch ($fn) { - case 'EXECUTE': - $sql = $p1; - $inputparams = $p2; + case 'EXECUTE': + $sql = $p1; + $inputparams = $p2; - $s = "<p><b>{$lang['strsqlerror']}</b><br />" . $misc->printVal($errmsg, 'errormsg') . "</p> + $s = "<p><b>{$lang['strsqlerror']}</b><br />" . $misc->printVal($errmsg, 'errormsg') . "</p> <p><b>{$lang['strinstatement']}</b><br />" . $misc->printVal($sql) . "</p> "; - echo "<table class=\"error\" cellpadding=\"5\"><tr><td>{$s}</td></tr></table><br />\n"; + echo "<table class=\"error\" cellpadding=\"5\"><tr><td>{$s}</td></tr></table><br />\n"; - break; + break; - case 'PCONNECT': - case 'CONNECT': - $_failed = true; - global $_reload_browser; - $_reload_browser = true; - unset($_SESSION['sharedUsername']); - unset($_SESSION['sharedPassword']); - unset($_SESSION['webdbLogin'][$_REQUEST['server']]); - $msg = $lang['strloginfailed']; - include './login.php'; - exit; - break; - default: - $s = "$dbms error: [$errno: $errmsg] in $fn($p1, $p2)\n"; - echo "<table class=\"error\" cellpadding=\"5\"><tr><td>{$s}</td></tr></table><br />\n"; - break; + case 'PCONNECT': + case 'CONNECT': + $_failed = true; + global $_reload_browser; + $_reload_browser = true; + unset($_SESSION['sharedUsername']); + unset($_SESSION['sharedPassword']); + unset($_SESSION['webdbLogin'][$_REQUEST['server']]); + $msg = $lang['strloginfailed']; + $login_controller = new \PHPPgAdmin\Controller\LoginController($container); + return $login_controller->render(); + break; + default: + $s = "$dbms error: [$errno: $errmsg] in $fn($p1, $p2)\n"; + echo "<table class=\"error\" cellpadding=\"5\"><tr><td>{$s}</td></tr></table><br />\n"; + break; } /* * Log connection error somewhere diff --git a/src/highlight.php b/src/highlight.php deleted file mode 100644 index 7c5e234a..00000000 --- a/src/highlight.php +++ /dev/null @@ -1,1082 +0,0 @@ -<?php -/* This software is licensed through a BSD-style License. - * http://www.opensource.org/licenses/bsd-license.php - -Copyright (c) 2003, 2004, Jacob D. Cohen -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. -Neither the name of Jacob D. Cohen nor the names of his contributors -may be used to endorse or promote products derived from this software -without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - */ - -function keyword_replace($keywords, $text, $ncs = false) { - $cm = ($ncs) ? "i" : ""; - foreach ($keywords as $keyword) { - $search[] = "/(\\b$keyword\\b)/" . $cm; - $replace[] = '<span class="keyword">\\0</span>'; - } - - $search[] = "/(\\bclass\s)/"; - $replace[] = '<span class="keyword">\\0</span>'; - - return preg_replace($search, $replace, $text); -} - -function preproc_replace($preproc, $text) { - foreach ($preproc as $proc) { - $search[] = "/(\\s*#\s*$proc\\b)/"; - $replace[] = '<span class="keyword">\\0</span>'; - } - - return preg_replace($search, $replace, $text); -} - -function sch_syntax_helper($text) { - return $text; -} - -function syntax_highlight_helper($text, $language) { - $preproc = array(); - $preproc["C++"] = array( - "if", "ifdef", "ifndef", "elif", "else", - "endif", "include", "define", "undef", "line", - "error", "pragma"); - $preproc["C89"] = &$preproc["C++"]; - $preproc["C"] = &$preproc["C89"]; - - $keywords = array( - "C++" => array( - "asm", "auto", "bool", "break", "case", - "catch", "char", /*class*/"const", "const_cast", - "continue", "default", "delete", "do", "double", - "dynamic_cast", "else", "enum", "explicit", "export", - "extern", "false", "float", "for", "friend", - "goto", "if", "inline", "int", "long", - "mutable", "namespace", "new", "operator", "private", - "protected", "public", "register", "reinterpret_cast", "return", - "short", "signed", "sizeof", "static", "static_cast", - "struct", "switch", "template", "this", "throw", - "true", "try", "typedef", "typeid", "typename", - "union", "unsigned", "using", "virtual", "void", - "volatile", "wchar_t", "while"), - - "C89" => array( - "auto", "break", "case", "char", "const", - "continue", "default", "do", "double", "else", - "enum", "extern", "float", "for", "goto", - "if", "int", "long", "register", "return", - "short", "signed", "sizeof", "static", "struct", - "switch", "typedef", "union", "unsigned", "void", - "volatile", "while"), - - "C" => array( - "auto", "break", "case", "char", "const", - "continue", "default", "do", "double", "else", - "enum", "extern", "float", "for", "goto", - "if", "int", "long", "register", "return", - "short", "signed", "sizeof", "static", "struct", - "switch", "typedef", "union", "unsigned", "void", - "volatile", "while", "__restrict", "_Bool"), - - "PHP" => array( - "and", "or", "xor", "__FILE__", "__LINE__", - "array", "as", "break", "case", "cfunction", - /*class*/"const", "continue", "declare", "default", - "die", "do", "echo", "else", "elseif", - "empty", "enddeclare", "endfor", "endforeach", "endif", - "endswitch", "endwhile", "eval", "exit", "extends", - "for", "foreach", "function", "global", "if", - "include", "include_once", "isset", "list", "new", - "old_function", "print", "require", "require_once", "return", - "static", "switch", "unset", "use", "var", - "while", "__FUNCTION__", "__CLASS__"), - - "Perl" => array( - "-A", "-B", "-C", "-M", "-O", - "-R", "-S", "-T", "-W", "-X", - "-b", "-c", "-d", "-e", "-f", - "-g", "-k", "-l", "-o", "-p", - "-r", "-s", "-t", "-u", "-w", - "-x", "-z", "ARGV", "DATA", "ENV", - "SIG", "STDERR", "STDIN", "STDOUT", "atan2", - "bind", "binmode", "bless", "caller", "chdir", - "chmod", "chomp", "chop", "chown", "chr", - "chroot", "close", "closedir", "cmp", "connect", - "continue", "cos", "crypt", "dbmclose", "dbmopen", - "defined", "delete", "die", "do", "dump", - "each", "else", "elsif", "endgrent", "endhostent", - "endnetent", "endprotent", "endpwent", "endservent", "eof", - "eq", "eval", "exec", "exists", "exit", - "exp", "fcntl", "fileno", "flock", "for", - "foreach", "fork", "format", "formline", "ge", - "getc", "getgrent", "getgrid", "getgrnam", "gethostbyaddr", - "gethostbyname", "gethostent", "getlogin", "getnetbyaddr", "getnetbyname", - "getnetent", "getpeername", "getpgrp", "getppid", "getpriority", - "getprotobyname", "getprotobynumber", "getprotoent", "getpwent", "getpwnam", - "getpwuid", "getservbyname", "getservbyport", "getservent", "getsockname", - "getsockopt", "glob", "gmtime", "goto", "grep", - /*gt*/"hex", "if", "import", "index", - "int", "ioctl", "join", "keys", "kill", - "last", "lc", "lcfirst", "le", "length", - "link", "listen", "local", "localtime", "log", - "lstat", /*lt*/"m", "map", "mkdir", - "msgctl", "msgget", "msgrcv", "msgsnd", "my", - "ne", "next", "no", "oct", "open", - "opendir", "ord", "pack", "package", "pipe", - "pop", "pos", "print", "printf", "push", - "q", "qq", "quotemeta", "qw", "qx", - "rand", "read", "readdir", "readlink", "recv", - "redo", "ref", "refname", "require", "reset", - "return", "reverse", "rewinddir", "rindex", "rmdir", - "s", "scalar", "seek", "seekdir", "select", - "semctl", "semget", "semop", "send", "setgrent", - "sethostent", "setnetent", "setpgrp", "setpriority", "setprotoent", - "setpwent", "setservent", "setsockopt", "shift", "shmctl", - "shmget", "shmread", "shmwrite", "shutdown", "sin", - "sleep", "socket", "socketpair", "sort", "splice", - "split", "sprintf", "sqrt", "srand", "stat", - "study", "sub", "substr", "symlink", "syscall", - "sysopen", "sysread", "system", "syswrite", "tell", - "telldir", "tie", "tied", "time", "times", - "tr", "truncate", "uc", "ucfirst", "umask", - "undef", "unless", "unlink", "unpack", "unshift", - "untie", "until", "use", "utime", "values", - "vec", "wait", "waitpid", "wantarray", "warn", - "while", "write", "y", "or", "and", - "not"), - - "Java" => array( - "abstract", "boolean", "break", "byte", "case", - "catch", "char", /*class*/"const", "continue", - "default", "do", "double", "else", "extends", - "final", "finally", "float", "for", "goto", - "if", "implements", "import", "instanceof", "int", - "interface", "long", "native", "new", "package", - "private", "protected", "public", "return", "short", - "static", "strictfp", "super", "switch", "synchronized", - "this", "throw", "throws", "transient", "try", - "void", "volatile", "while"), - - "VB" => array( - "AddressOf", "Alias", "And", "Any", "As", - "Binary", "Boolean", "ByRef", "Byte", "ByVal", - "Call", "Case", "CBool", "CByte", "CCur", - "CDate", "CDbl", "CInt", "CLng", "Close", - "Const", "CSng", "CStr", "Currency", "CVar", - "CVErr", "Date", "Debug", "Declare", "DefBool", - "DefByte", "DefCur", "DefDate", "DefDbl", "DefInt", - "DefLng", "DefObj", "DefSng", "DefStr", "DefVar", - "Dim", "Do", "Double", "Each", "Else", - "End", "Enum", "Eqv", "Erase", "Error", - "Event", "Exit", "For", "Friend", "Function", - "Get", "Get", "Global", "GoSub", "GoTo", - "If", "Imp", "Implements", "In", "Input", - "Integer", "Is", "LBound", "Len", "Let", - "Lib", "Like", "Line", "Lock", "Long", - "Loop", "LSet", "Mod", "Name", "Next", - "Not", "Nothing", "Null", "Object", "On", - "Open", "Option Base 1", "Option Compare Binary", - "Option Compare Database", "Option Compare Text", "Option Explicit", - "Option Private Module", "Optional", "Or", "Output", - "ParamArray", "Preserve", "Print", "Private", "Property", - "Public", "Put", "RaiseEvent", "Random", "Read", - "ReDim", "Resume", "Return", "RSet", "Seek", - "Select", "Set", "Single", "Spc", "Static", - "Step", "Stop", "String", "Sub", "Tab", - "Then", "To", "Type", "UBound", "Unlock", - "Variant", "Wend", "While", "With", "WithEvents", - "Write", "Xor"), - - "C#" => array( - "abstract", "as", "base", "bool", "break", - "byte", "case", "catch", "char", "checked", - /*class*/"const", "continue", "decimal", "default", - "delegate", "do", "double", "else", "enum", - "event", "explicit", "extern", "false", "finally", - "fixed", "float", "for", "foreach", "goto", - "if", "implicit", "in", "int", "interface", - "internal", "is", "lock", "long", "namespace", - "new", "null", "object", "operator", "out", - "override", "params", "private", "protected", "public", - "readonly", "ref", "return", "sbyte", "sealed", - "short", "sizeof", "stackalloc", "static", "string", - "struct", "switch", "this", "throw", "true", - "try", "typeof", "uint", "ulong", "unchecked", - "unsafe", "ushort", "using", "virtual", "volatile", - "void", "while"), - - "Ruby" => array( - "alias", "and", "begin", "break", "case", - /*class*/"def", "defined", "do", "else", - "elsif", "end", "ensure", "false", "for", - "if", "in", "module", "next", "module", - "next", "nil", "not", "or", "redo", - "rescue", "retry", "return", "self", "super", - "then", "true", "undef", "unless", "until", - "when", "while", "yield"), - - "Python" => array( - "and", "assert", "break", /*"class",*/"continue", - "def", "del", "elif", "else", "except", - "exec", "finally", "for", "from", "global", - "if", "import", "in", "is", "lambda", - "not", "or", "pass", "print", "raise", - "return", "try", "while", "yield"), - - "Pascal" => array( - "Absolute", "Abstract", "All", "And", "And_then", - "Array", "Asm", "Begin", "Bindable", "Case", - /*"Class",*/"Const", "Constructor", "Destructor", "Div", - "Do", "Downto", "Else", "End", "Export", - "File", "For", "Function", "Goto", "If", - "Import", "Implementation", "Inherited", "In", "Inline", - "Interface", "Is", "Label", "Mod", "Module", - "Nil", "Not", "Object", "Of", "Only", - "Operator", "Or", "Or_else", "Otherwise", "Packed", - "Pow", "Procedure", "Program", "Property", "Protected", - "Qualified", "Record", "Repeat", "Restricted", "Set", - "Shl", "Shr", "Then", "To", "Type", - "Unit", "Until", "Uses", "Value", "Var", - "View", "Virtual", "While", "With", "Xor"), - - "mIRC" => array( - ), - - "PL/I" => array( - "A", "ABS", "ACOS", "%ACTIVATE", "ACTUALCOUNT", - "ADD", "ADDR", "ADDREL", "ALIGNED", "ALLOCATE", - "ALLOC", "ALLOCATION", "ALLOCN", "ANY", "ANYCONDITION", - "APPEND", "AREA", "ASIN", "ATAN", "ATAND", - "ATANH", "AUTOMATIC", "AUTO", "B", "B1", - "B2", "B3", "B4", "BACKUP_DATE", "BASED", - "BATCH", "BEGIN", "BINARY", "BIN", "BIT", - "BLOCK_BOUNDARY_FORMAT", "BLOCK_IO", "BLOCK_SIZE", "BOOL", - "BUCKET_SIZE", "BUILTIN", "BY", "BYTE", "BYTESIZE", - "CALL", "CANCEL_CONTROL_O", "CARRIAGE_RETURN_FORMAT", - "CEIL", "CHAR", "CHARACTER", "CLOSE", "COLLATE", "COLUMN", - "CONDITION", "CONTIGUOUS", "CONTIGUOUS_BEST_TRY", "CONTROLLED", - "CONVERSION", "COPY", "COS", "COSD", "COSH", - "CREATION_DATE", "CURRENT_POSITION", "DATE", - "DATETIME", "%DEACTIVATE", "DECIMAL", "DEC", "%DECLARE", - "%DCL", "DECLARE", "DCL", "DECODE", "DEFAULT_FILE_NAME", - "DEFERRED_WRITE", "DEFINED", "DEF", "DELETE", - "DESCRIPTOR", "%DICTIONARY", "DIMENSION", "DIM", "DIRECT", - "DISPLAY", "DIVIDE", "%DO", "DO", "E", - "EDIT", "%ELSE", "ELSE", "EMPTY", "ENCODE", - "%END", "END", "ENDFILE", "ENDPAGE", "ENTRY", - "ENVIRONMENT", "ENV", "%ERROR", "ERROR", "EVERY", - "EXP", "EXPIRATION_DATE", "EXTEND", "EXTENSION_SIZE", - "EXTERNAL", "EXT", "F", "FAST_DELETE", "%FATAL", - "FILE", "FILE_ID", "FILE_ID_TO", "FILE_SIZE", - "FINISH", "FIXED", "FIXEDOVERFLOW", "FOFL", - "FIXED_CONTROL_FROM", "FIXED_CONTROL_SIZE", "FIXED_CONTROL_SIZE_TO", - "FIXED_CONTROL_TO", "FIXED_LENGTH_RECORDS", "FLOAT", - "FLOOR", "FLUSH", "FORMAT", "FREE", "FROM", - "GET", "GLOBALDEF", "GLOBALREF", "%GOTO", - "GOTO", "GO", "TO", "GROUP_PROTETION", "HBOUND", - "HIGH", "INDENT", "%IF", "IF", "IGNORE_LINE_MARKS", - "IN", "%INCLUDE", "INDEX", "INDEXED", "INDEX_NUMBER", - "%INFORM", "INFORM", "INITIAL", "INIT", "INITIAL_FILL", - "INPUT", "INT", "INTERNAL", "INTO", "KEY", - "KEYED", "KEYFROM", "KEYTO", "LABEL", "LBOUND", - "LEAVE", "LENGTH", "LIKE", "LINE", "LINENO", - "LINESIZE", "%LIST", "LIST", "LOCK_ON_READ", "LOCK_ON_WRITE", - "LOG", "LOG10", "LOG2", "LOW", "LTRIM", - "MAIN", "MANUAL_UNLOCKING", "MATCH_GREATER", - "MATCH_GREATER_EQUAL", "MATCH_NEXT", "MATCH_NEXT_EQUAL", - "MAX", "MAXIMUM_RECORD_NUMBER", "MAXIMUM_RECORD_SIZE", - "MAXLENGTH", "MEMBER", "MIN", "MOD", "MULTIBLOCK_COUNT", - "MULTIBUFFER_COUNT", "MULTIPLY", "NEXT_VOLUME", "%NOLIST", - "NOLOCK", "NONEXISTENT_RECORD", "NONRECURSIVE", "NONVARYING", - "NONVAR", "NORESCAN", "NO_ECHO", "NO_FILTER", "NO_SHARE", - "NULL", "OFFSET", "ON", "ONARGSLIST", "ONCHAR", - "ONCODE", "ONFILE", "ONKEY", "ONSOURCE", "OPEN", - "OPTIONAL", "OPTIONS", "OTHERWISE", "OTHER", "OUTPUT", - "OVERFLOW", "OFL", "OWNER_GROUP", "OWNER_ID", - "OWNER_MEMBER", "OWNER_PROTECTION", "P", "%PAGE", - "PAGE", "PAGENO", "PAGESIZE", "PARAMETER", "PARM", - "PICTURE", "PIC", "POINTER", "PTR", "POSINT", - "POSITION", "POS", "PRECISION", "PREC", "PRESENT", - "PRINT", "PRINTER_FORMAT", "%PROCEDURE", "%PROC", - "PROCEDURE", "PROC", "PROD", "PROMPT", "PURGE_TYPE_AHEAD", - "PUT", "R", "RANK", "READ", "READONLY", - "READ_AHEAD", "READ_CHECK", "READ_REGARDLESS", "RECORD", - "RECORD_ID", "RECORD_ID_ACCESS", "RECORD_ID_TO", "RECURSIVE", - "REFER", "REFERENCE", "RELEASE", "REPEAT", "%REPLACE", - "RESCAN", "RESIGNAL", "RETRIEVAL_POINTERS", "%RETURN", - "RETURN", "RETURNS", "REVERSE", "REVERT", "REVISION_DATE", - "REWIND", "REWIND_ON_CLOSE", "REWIND_ON_OPEN", - "REWRITE", "ROUND", "RTRIM", "%SBTTL", "SCALARVARYING", - "SEARCH", "SELECT", "SEQUENTIAL", "SEQL", - "SET", "SHARED_READ", "SHARED_WRITE", "SIGN", - "SIGNAL", "SIN", "SIND", "SINH", "SIZE", - "SKIP", "SNAP", "SOME", "SPACEBLOCK", "SPOOL", - "SQRT", "STATEMENT", "STATIC", "STOP", "STORAGE", - "STREAM", "STRING", "STRINGRANGE", "STRG", - "STRUCTURE", "SUBSCRIPTRANGE", "SUBRG", "SUBSTR", - "SUBTRACT", "SUM", "SUPERCEDE", "SYSIN", "SYSPRINT", - "SYSTEM", "SYSTEM_PROTECTION", "TAB", "TAN", - "TAND", "TANH", "TEMPORARY", "%THEN", "THEN", - "TIME", "TIMEOUT_PERIOD", "%TITLE", "TITLE", - "TO", "TRANSLATE", "TRIM", "TRUNC", "TRUNCATE", - "UNALIGNED", "UNAL", "UNDEFINED", "UNDF", "UNDERFLOW", - "UFL", "UNION", "UNSPEC", "UNTIL", "UPDATE", - "USER_OPEN", "VALID", "VALUE", "VAL", "VARIABLE", - "VARIANT", "VARYING", "VAR", "VAXCONDITION", "VERIFY", - "WAIT_FOR_RECORD", "%WARN", "WARN", "WHEN", - "WHILE", "WORLD_PROTECTION", "WRITE", "WRITE_BEHIND", - "WRITE_CHECK", "X", "ZERODIVIDE"), - - "SQL" => array( - "abort", "abs", "absolute", "access", - "action", "ada", "add", "admin", - "after", "aggregate", "alias", "all", - "allocate", "alter", "analyse", "analyze", - "and", "any", "are", "array", - "as", "asc", "asensitive", "assertion", - "assignment", "asymmetric", "at", "atomic", - "authorization", "avg", "backward", "before", - "begin", "between", "bigint", "binary", - "bit", "bitvar", "bit_length", "blob", - "boolean", "both", "breadth", "by", - "c", "cache", "call", "called", - "cardinality", "cascade", "cascaded", "case", - "cast", "catalog", "catalog_name", "chain", - "char", "character", "characteristics", "character_length", - "character_set_catalog", "character_set_name", "character_set_schema", "char_length", - "check", "checked", "checkpoint", /* "class", */ - "class_origin", "clob", "close", "cluster", - "coalesce", "cobol", "collate", "collation", - "collation_catalog", "collation_name", "collation_schema", "column", - "column_name", "command_function", "command_function_code", "comment", - "commit", "committed", "completion", "condition_number", - "connect", "connection", "connection_name", "constraint", - "constraints", "constraint_catalog", "constraint_name", "constraint_schema", - "constructor", "contains", "continue", "conversion", - "convert", "copy", "corresponding", "count", - "create", "createdb", "createuser", "cross", - "cube", "current", "current_date", "current_path", - "current_role", "current_time", "current_timestamp", "current_user", - "cursor", "cursor_name", "cycle", "data", - "database", "date", "datetime_interval_code", "datetime_interval_precision", - "day", "deallocate", "dec", "decimal", - "declare", "default", "defaults", "deferrable", - "deferred", "defined", "definer", "delete", - "delimiter", "delimiters", "depth", "deref", - "desc", "describe", "descriptor", "destroy", - "destructor", "deterministic", "diagnostics", "dictionary", - "disconnect", "dispatch", "distinct", "do", - "domain", "double", "drop", "dynamic", - "dynamic_function", "dynamic_function_code", "each", "else", - "encoding", "encrypted", "end", "end-exec", - "equals", "escape", "every", "except", - "exception", "excluding", "exclusive", "exec", - "execute", "existing", "exists", "explain", - "external", "extract", "false", "fetch", - "final", "first", "float", "for", - "force", "foreign", "fortran", "forward", - "found", "free", "freeze", "from", - "full", "function", "g", "general", - "generated", "get", "global", "go", - "goto", "grant", "granted", "group", - "grouping", "handler", "having", "hierarchy", - "hold", "host", "hour", "identity", - "ignore", "ilike", "immediate", "immutable", - "implementation", "implicit", "in", "including", - "increment", "index", "indicator", "infix", - "inherits", "initialize", "initially", "inner", - "inout", "input", "insensitive", "insert", - "instance", "instantiable", "instead", "int", - "integer", "intersect", "interval", "into", - "invoker", "is", "isnull", "isolation", - "iterate", "join", "k", "key", - "key_member", "key_type", "lancompiler", "language", - "large", "last", "lateral", "leading", - "left", "length", "less", "level", - "like", "limit", "listen", "load", - "local", "localtime", "localtimestamp", "location", - "locator", "lock", "lower", "m", - "map", "match", "max", "maxvalue", - "message_length", "message_octet_length", "message_text", "method", - "min", "minute", "minvalue", "mod", - "mode", "modifies", "modify", "module", - "month", "more", "move", "mumps", - "name", "names", "national", "natural", - "nchar", "nclob", "new", "next", - "no", "nocreatedb", "nocreateuser", "none", - "not", "nothing", "notify", "notnull", - "null", "nullable", "nullif", "number", - "numeric", "object", "octet_length", "of", - "off", "offset", "oids", "old", - "on", "only", "open", "operation", - "operator", "option", "options", "or", - "order", "ordinality", "out", "outer", - "output", "overlaps", "overlay", "overriding", - "owner", "pad", "parameter", "parameters", - "parameter_mode", "parameter_name", "parameter_ordinal_position", "parameter_specific_catalog", - "parameter_specific_name", "parameter_specific_schema", "partial", "pascal", - "password", "path", "pendant", "placing", - "pli", "position", "postfix", "precision", - "prefix", "preorder", "prepare", "preserve", - "primary", "prior", "privileges", "procedural", - "procedure", "public", "read", "reads", - "real", "recheck", "recursive", "ref", - "references", "referencing", "reindex", "relative", - "rename", "repeatable", "replace", "reset", - "restart", "restrict", "result", "return", - "returned_length", "returned_octet_length", "returned_sqlstate", "returns", - "revoke", "right", "role", "rollback", - "rollup", "routine", "routine_catalog", "routine_name", - "routine_schema", "row", "rows", "row_count", - "rule", "savepoint", "scale", "schema", - "schema_name", "scope", "scroll", "search", - "second", "section", "security", "select", - "self", "sensitive", "sequence", "serializable", - "server_name", "session", "session_user", "set", - "setof", "sets", "share", "show", - "similar", "simple", "size", "smallint", - "some", "source", "space", "specific", - "specifictype", "specific_name", "sql", "sqlcode", - "sqlerror", "sqlexception", "sqlstate", "sqlwarning", - "stable", "start", "state", "statement", - "static", "statistics", "stdin", "stdout", - "storage", "strict", "structure", "style", - "subclass_origin", "sublist", "substring", "sum", - "symmetric", "sysid", "system", "system_user", - "table", "table_name", "temp", "template", - "temporary", "terminate", "text", "than", "then", - "time", "timestamp", "timezone_hour", "timezone_minute", - "to", "toast", "trailing", "transaction", - "transactions_committed", "transactions_rolled_back", "transaction_active", "transform", - "transforms", "translate", "translation", "treat", - "trigger", "trigger_catalog", "trigger_name", "trigger_schema", - "trim", "true", "truncate", "trusted", - "type", "uncommitted", "under", "unencrypted", - "union", "unique", "unknown", "unlisten", - "unnamed", "unnest", "until", "update", - "upper", "usage", "user", "user_defined_type_catalog", - "user_defined_type_name", "user_defined_type_schema", "using", "vacuum", - "valid", "validator", "value", "values", - "varchar", "variable", "varying", "verbose", - "version", "view", "volatile", "when", - "whenever", "where", "with", "without", - "work", "write", "year", "zone"), - - ); - - $case_insensitive = array( - "VB" => true, - "Pascal" => true, - "PL/I" => true, - "SQL" => true, - ); - $ncs = false; - if (array_key_exists($language, $case_insensitive)) { - $ncs = true; - } - - $text = (array_key_exists($language, $preproc)) ? - preproc_replace($preproc[$language], $text) : - $text; - $text = (array_key_exists($language, $keywords)) ? - keyword_replace($keywords[$language], $text, $ncs) : - $text; - - return $text; -} - -function rtrim1($span, $lang, $ch) { - return syntax_highlight_helper(substr($span, 0, -1), $lang); -} - -function rtrim1_htmlesc($span, $lang, $ch) { - return htmlspecialchars(substr($span, 0, -1)); -} - -function sch_rtrim1($span, $lang, $ch) { - return sch_syntax_helper(substr($span, 0, -1)); -} - -function rtrim2($span, $lang, $ch) { - return substr($span, 0, -2); -} - -function syn_proc($span, $lang, $ch) { - return syntax_highlight_helper($span, $lang); -} - -function dash_putback($span, $lang, $ch) { - return syntax_highlight_helper('-' . $span, $lang); -} - -function slash_putback($span, $lang, $ch) { - return syntax_highlight_helper('/' . $span, $lang); -} - -function slash_putback_rtrim1($span, $lang, $ch) { - return rtrim1('/' . $span, $lang, $ch); -} - -function lparen_putback($span, $lang, $ch) { - return syntax_highlight_helper('(' . $span, $lang); -} - -function lparen_putback_rtrim1($span, $lang, $ch) { - return rtrim1('(' . $span, $lang, $ch); -} - -function prepend_xml_opentag($span, $lang, $ch) { - return '<span class="xml_tag"><' . $span; -} - -function proc_void($span, $lang, $ch) { - return $span; -} - -/** - * Syntax highlight function - * Does the bulk of the syntax highlighting by lexing the input - * string, then calling the helper function to highlight keywords. - */ -function syntax_highlight($text, $language) { - if ($language == "Plain Text") { - return $text; - } - - define("normal_text", 1, true); - define("dq_literal", 2, true); - define("dq_escape", 3, true); - define("sq_literal", 4, true); - define("sq_escape", 5, true); - define("slash_begin", 6, true); - define("star_comment", 7, true); - define("star_end", 8, true); - define("line_comment", 9, true); - define("html_entity", 10, true); - define("lc_escape", 11, true); - define("block_comment", 12, true); - define("paren_begin", 13, true); - define("dash_begin", 14, true); - define("bt_literal", 15, true); - define("bt_escape", 16, true); - define("xml_tag_begin", 17, true); - define("xml_tag", 18, true); - define("xml_pi", 19, true); - define("sch_normal", 20, true); - define("sch_stresc", 21, true); - define("sch_idexpr", 22, true); - define("sch_numlit", 23, true); - define("sch_chrlit", 24, true); - define("sch_strlit", 25, true); - - $initial_state["Scheme"] = sch_normal; - - $sch[sch_normal][0] = sch_normal; - $sch[sch_normal]['"'] = sch_strlit; - $sch[sch_normal]["#"] = sch_chrlit; - $sch[sch_normal]["0"] = sch_numlit; - $sch[sch_normal]["1"] = sch_numlit; - $sch[sch_normal]["2"] = sch_numlit; - $sch[sch_normal]["3"] = sch_numlit; - $sch[sch_normal]["4"] = sch_numlit; - $sch[sch_normal]["5"] = sch_numlit; - $sch[sch_normal]["6"] = sch_numlit; - $sch[sch_normal]["7"] = sch_numlit; - $sch[sch_normal]["8"] = sch_numlit; - $sch[sch_normal]["9"] = sch_numlit; - - $sch[sch_strlit]['"'] = sch_normal; - $sch[sch_strlit]["\n"] = sch_normal; - $sch[sch_strlit]["\\"] = sch_stresc; - $sch[sch_strlit][0] = sch_strlit; - - $sch[sch_chrlit][" "] = sch_normal; - $sch[sch_chrlit]["\t"] = sch_normal; - $sch[sch_chrlit]["\n"] = sch_normal; - $sch[sch_chrlit]["\r"] = sch_normal; - $sch[sch_chrlit][0] = sch_chrlit; - - $sch[sch_numlit][" "] = sch_normal; - $sch[sch_numlit]["\t"] = sch_normal; - $sch[sch_numlit]["\n"] = sch_normal; - $sch[sch_numlit]["\r"] = sch_normal; - $sch[sch_numlit][0] = sch_numlit; - - // - // State transitions for C - // - $c89[normal_text]["\""] = dq_literal; - $c89[normal_text]["'"] = sq_literal; - $c89[normal_text]["/"] = slash_begin; - $c89[normal_text][0] = normal_text; - - $c89[dq_literal]["\""] = normal_text; - $c89[dq_literal]["\n"] = normal_text; - $c89[dq_literal]["\\"] = dq_escape; - $c89[dq_literal][0] = dq_literal; - - $c89[dq_escape][0] = dq_literal; - - $c89[sq_literal]["'"] = normal_text; - $c89[sq_literal]["\n"] = normal_text; - $c89[sq_literal]["\\"] = sq_escape; - $c89[sq_literal][0] = sq_literal; - - $c89[sq_escape][0] = sq_literal; - - $c89[slash_begin]["*"] = star_comment; - $c89[slash_begin][0] = normal_text; - - $c89[star_comment]["*"] = star_end; - $c89[star_comment][0] = star_comment; - - $c89[star_end]["/"] = normal_text; - $c89[star_end]["*"] = star_end; - $c89[star_end][0] = star_comment; - - // - // State transitions for C++ - // Inherit transitions from C, and add line comment support - // - $cpp = $c89; - $cpp[slash_begin]["/"] = line_comment; - $cpp[line_comment]["\n"] = normal_text; - $cpp[line_comment]["\\"] = lc_escape; - $cpp[line_comment][0] = line_comment; - - $cpp[lc_escape]["\r"] = lc_escape; - $cpp[lc_escape][0] = line_comment; - - // - // State transitions for C99. - // C99 supports line comments like C++ - // - $c99 = $cpp; - - // State transitions for PL/I - // Kinda like C - $pli = $c89; - - // - // State transitions for PHP - // Inherit transitions from C++, and add perl-style line comment support - $php = $cpp; - $php[normal_text]["#"] = line_comment; - $php[sq_literal]["\n"] = sq_literal; - $php[dq_literal]["\n"] = dq_literal; - - // - // State transitions for Perl - $perl[normal_text]["#"] = line_comment; - $perl[normal_text]["\""] = dq_literal; - $perl[normal_text]["'"] = sq_literal; - $perl[normal_text][0] = normal_text; - - $perl[dq_literal]["\""] = normal_text; - $perl[dq_literal]["\\"] = dq_escape; - $perl[dq_literal][0] = dq_literal; - - $perl[dq_escape][0] = dq_literal; - - $perl[sq_literal]["'"] = normal_text; - $perl[sq_literal]["\\"] = sq_escape; - $perl[sq_literal][0] = sq_literal; - - $perl[sq_escape][0] = sq_literal; - - $perl[line_comment]["\n"] = normal_text; - $perl[line_comment][0] = line_comment; - - $mirc[normal_text]["\""] = dq_literal; - $mirc[normal_text][";"] = line_comment; - $mirc[normal_text][0] = normal_text; - - $mirc[dq_literal]["\""] = normal_text; - $mirc[dq_literal]["\\"] = dq_escape; - $mirc[dq_literal][0] = dq_literal; - - $mirc[dq_escape][0] = dq_literal; - - $mirc[line_comment]["\n"] = normal_text; - $mirc[line_comment][0] = line_comment; - - $ruby = $perl; - - $python = $perl; - - $java = $cpp; - - $vb = $perl; - $vb[normal_text]["#"] = normal_text; - $vb[normal_text]["'"] = line_comment; - - $cs = $java; - - $pascal = $c89; - $pascal[normal_text]["("] = paren_begin; - $pascal[normal_text]["/"] = slash_begin; - $pascal[normal_text]["{"] = block_comment; - - $pascal[paren_begin]["*"] = star_comment; - $pascal[paren_begin]["'"] = sq_literal; - $pascal[paren_begin]['"'] = dq_literal; - $pascal[paren_begin][0] = normal_text; - - $pascal[slash_begin]["'"] = sq_literal; - $pascal[slash_begin]['"'] = dq_literal; - $pascal[slash_begin]['/'] = line_comment; - $pascal[slash_begin][0] = normal_text; - - $pascal[star_comment]["*"] = star_end; - $pascal[star_comment][0] = star_comment; - - $pascal[block_comment]["}"] = normal_text; - $pascal[block_comment][0] = block_comment; - - $pascal[line_comment]["\n"] = normal_text; - $pascal[line_comment][0] = line_comment; - - $pascal[star_end][")"] = normal_text; - $pascal[star_end]["*"] = star_end; - $pascal[star_end][0] = star_comment; - - $sql[normal_text]['"'] = dq_literal; - $sql[normal_text]["'"] = sq_literal; - $sql[normal_text]['`'] = bt_literal; - $sql[normal_text]['-'] = dash_begin; - $sql[normal_text][0] = normal_text; - - $sql[dq_literal]['"'] = normal_text; - $sql[dq_literal]['\\'] = dq_escape; - $sql[dq_literal][0] = dq_literal; - - $sql[sq_literal]["'"] = normal_text; - $sql[sq_literal]['\\'] = sq_escape; - $sql[sq_literal][0] = sq_literal; - - $sql[bt_literal]['`'] = normal_text; - $sql[bt_literal]['\\'] = bt_escape; - $sql[bt_literal][0] = bt_literal; - - $sql[dq_escape][0] = dq_literal; - $sql[sq_escape][0] = sq_literal; - $sql[bt_escape][0] = bt_literal; - - $sql[dash_begin]["-"] = line_comment; - $sql[dash_begin][0] = normal_text; - - $sql[line_comment]["\n"] = normal_text; - $sql[line_comment]["\\"] = lc_escape; - $sql[line_comment][0] = line_comment; - - $sql[lc_escape]["\r"] = lc_escape; - $sql[lc_escape][0] = line_comment; - - $xml[normal_text]["<"] = xml_tag_begin; - $xml[normal_text]["&"] = html_entity; - $xml[normal_text][0] = normal_text; - $xml[html_entity][";"] = normal_text; - $xml[html_entity]["<"] = xml_tag_begin; - $xml[html_entity][0] = html_entity; - $xml[xml_tag_begin]["?"] = xml_pi; - $xml[xml_tag_begin]["!"] = line_comment; - $xml[xml_tag_begin][0] = xml_tag; - $xml[xml_tag][">"] = normal_text; - $xml[xml_tag]["\""] = dq_literal; - $xml[xml_tag]["'"] = sq_literal; - $xml[xml_tag][0] = xml_tag; - $xml[xml_pi][">"] = normal_text; - $xml[xml_pi][0] = xml_tag; - $xml[line_comment][">"] = normal_text; - $xml[line_comment][0] = line_comment; - $xml[dq_literal]["\""] = xml_tag; - $xml[dq_literal]["&"] = dq_escape; - $xml[dq_literal][0] = dq_literal; - $xml[sq_literal]["'"] = xml_tag; - $xml[sq_literal]["&"] = sq_escape; - $xml[sq_literal][0] = sq_literal; - $xml[dq_escape][";"] = dq_literal; - $xml[dq_escape][0] = dq_escape; - - // - // Main state transition table - // - $states = array( - "C89" => $c89, - "C" => $c99, - "C++" => $cpp, - "PHP" => $php, - "Perl" => $perl, - "Java" => $java, - "VB" => $vb, - "C#" => $cs, - "Ruby" => $ruby, - "Python" => $python, - "Pascal" => $pascal, - "mIRC" => $mirc, - "PL/I" => $pli, - "SQL" => $sql, - "XML" => $xml, - "Scheme" => $sch, - ); - - // - // Process functions - // - $process["C89"][normal_text][sq_literal] = "rtrim1"; - $process["C89"][normal_text][dq_literal] = "rtrim1"; - $process["C89"][normal_text][slash_begin] = "rtrim1"; - $process["C89"][normal_text][0] = "syn_proc"; - - $process["C89"][slash_begin][star_comment] = "rtrim1"; - $process["C89"][slash_begin][0] = "slash_putback"; - - $process["Scheme"][sch_normal][sch_strlit] = "sch_rtrim1"; - $process["Scheme"][sch_normal][sch_chrlit] = "sch_rtrim1"; - $process["Scheme"][sch_normal][sch_numlit] = "sch_rtrim1"; - - $process["SQL"][normal_text][sq_literal] = "rtrim1"; - $process["SQL"][normal_text][dq_literal] = "rtrim1"; - $process["SQL"][normal_text][bt_literal] = "rtrim1"; - $process["SQL"][normal_text][dash_begin] = "rtrim1"; - $process["SQL"][normal_text][0] = "syn_proc"; - - $process["SQL"][dash_begin][line_comment] = "rtrim1"; - $process["SQL"][dash_begin][0] = "dash_putback"; - - $process["PL/I"] = $process["C89"]; - - $process["C++"] = $process["C89"]; - $process["C++"][slash_begin][line_comment] = "rtrim1"; - - $process["C"] = $process["C++"]; - - $process["PHP"] = $process["C++"]; - $process["PHP"][normal_text][line_comment] = "rtrim1"; - - $process["Perl"][normal_text][sq_literal] = "rtrim1"; - $process["Perl"][normal_text][dq_literal] = "rtrim1"; - $process["Perl"][normal_text][line_comment] = "rtrim1"; - $process["Perl"][normal_text][0] = "syn_proc"; - - $process["Ruby"] = $process["Perl"]; - $process["Python"] = $process["Perl"]; - - $process["mIRC"][normal_text][dq_literal] = "rtrim1"; - $process["mIRC"][normal_text][line_comment] = "rtrim1"; - $process["mIRC"][normal_text][0] = "syn_proc"; - - $process["VB"] = $process["Perl"]; - - $process["Java"] = $process["C++"]; - - $process["C#"] = $process["Java"]; - - $process["Pascal"] = $process["C++"]; - $process["Pascal"][normal_text][line_comment] = "rtrim1"; - $process["Pascal"][normal_text][block_comment] = "rtrim1"; - $process["Pascal"][normal_text][paren_begin] = "rtrim1"; - $process["Pascal"][slash_begin][sq_literal] = "slash_putback_rtrim1"; - $process["Pascal"][slash_begin][dq_literal] = "slash_putback_rtrim1"; - $process["Pascal"][slash_begin][0] = "slash_putback"; - $process["Pascal"][paren_begin][sq_literal] = "lparen_putback_rtrim1"; - $process["Pascal"][paren_begin][dq_literal] = "lparen_putback_rtrim1"; - $process["Pascal"][paren_begin][star_comment] = "rtrim1"; - $process["Pascal"][paren_begin][0] = "lparen_putback"; - - $process["XML"][normal_text][xml_tag_begin] = "rtrim1"; - $process["XML"][normal_text][html_entity] = "rtrim1"; - $process["XML"][html_entity][xml_tag_begin] = "rtrim1"; - $process["XML"][html_entity][0] = "proc_void"; - $process["XML"][xml_tag_begin][xml_tag] = "prepend_xml_opentag"; - $process["XML"][xml_tag_begin][xml_pi] = "rtrim1"; - $process["XML"][xml_tag_begin][line_comment] = "rtrim1"; - $process["XML"][line_comment][normal_text] = "rtrim1_htmlesc"; - $process["XML"][xml_tag][normal_text] = "rtrim1"; - $process["XML"][xml_tag][dq_literal] = "rtrim1"; - $process["XML"][dq_literal][xml_tag] = "rtrim1"; - $process["XML"][dq_literal][dq_escape] = "rtrim1"; - - $process_end["C89"] = "syntax_highlight_helper"; - $process_end["C++"] = $process_end["C89"]; - $process_end["C"] = $process_end["C89"]; - $process_end["PHP"] = $process_end["C89"]; - $process_end["Perl"] = $process_end["C89"]; - $process_end["Java"] = $process_end["C89"]; - $process_end["VB"] = $process_end["C89"]; - $process_end["C#"] = $process_end["C89"]; - $process_end["Ruby"] = $process_end["C89"]; - $process_end["Python"] = $process_end["C89"]; - $process_end["Pascal"] = $process_end["C89"]; - $process_end["mIRC"] = $process_end["C89"]; - $process_end["PL/I"] = $process_end["C89"]; - $process_end["SQL"] = $process_end["C89"]; - $process_end["Scheme"] = "sch_syntax_helper"; - - $edges["C89"][normal_text . "," . dq_literal] = '<span class="literal">"'; - $edges["C89"][normal_text . "," . sq_literal] = '<span class="literal">\''; - $edges["C89"][slash_begin . "," . star_comment] = '<span class="comment">/*'; - $edges["C89"][dq_literal . "," . normal_text] = '</span>'; - $edges["C89"][sq_literal . "," . normal_text] = '</span>'; - $edges["C89"][star_end . "," . normal_text] = '</span>'; - - $edges["Scheme"][sch_normal . "," . sch_strlit] = '<span class="sch_str">"'; - $edges["Scheme"][sch_normal . "," . sch_numlit] = '<span class="sch_num">'; - $edges["Scheme"][sch_normal . "," . sch_chrlit] = '<span class="sch_chr">#'; - $edges["Scheme"][sch_strlit . "," . sch_normal] = '</span>'; - $edges["Scheme"][sch_numlit . "," . sch_normal] = '</span>'; - $edges["Scheme"][sch_chrlit . "," . sch_normal] = '</span>'; - - $edges["SQL"][normal_text . "," . dq_literal] = '<span class="literal">"'; - $edges["SQL"][normal_text . "," . sq_literal] = '<span class="literal">\''; - $edges["SQL"][dash_begin . "," . line_comment] = '<span class="comment">--'; - $edges["SQL"][normal_text . "," . bt_literal] = '`'; - $edges["SQL"][dq_literal . "," . normal_text] = '</span>'; - $edges["SQL"][sq_literal . "," . normal_text] = '</span>'; - $edges["SQL"][line_comment . "," . normal_text] = '</span>'; - - $edges["PL/I"] = $edges["C89"]; - - $edges["C++"] = $edges["C89"]; - $edges["C++"][slash_begin . "," . line_comment] = '<span class="comment">//'; - $edges["C++"][line_comment . "," . normal_text] = '</span>'; - - $edges["C"] = $edges["C++"]; - - $edges["PHP"] = $edges["C++"]; - $edges["PHP"][normal_text . "," . line_comment] = '<span class="comment">#'; - - $edges["Perl"][normal_text . "," . dq_literal] = '<span class="literal">"'; - $edges["Perl"][normal_text . "," . sq_literal] = '<span class="literal">\''; - $edges["Perl"][dq_literal . "," . normal_text] = '</span>'; - $edges["Perl"][sq_literal . "," . normal_text] = '</span>'; - $edges["Perl"][normal_text . "," . line_comment] = '<span class="comment">#'; - $edges["Perl"][line_comment . "," . normal_text] = '</span>'; - - $edges["Ruby"] = $edges["Perl"]; - - $edges["Python"] = $edges["Perl"]; - - $edges["mIRC"][normal_text . "," . dq_literal] = '<span class="literal">"'; - $edges["mIRC"][normal_text . "," . line_comment] = '<span class="comment">;'; - $edges["mIRC"][dq_literal . "," . normal_text] = '</span>'; - $edges["mIRC"][line_comment . "," . normal_text] = '</span>'; - - $edges["VB"] = $edges["Perl"]; - $edges["VB"][normal_text . "," . line_comment] = '<span class="comment">\''; - - $edges["Java"] = $edges["C++"]; - - $edges["C#"] = $edges["Java"]; - - $edges["Pascal"] = $edges["C89"]; - $edges["Pascal"][paren_begin . "," . star_comment] = '<span class="comment">(*'; - $edges["Pascal"][paren_begin . "," . dq_literal] = '<span class="literal">"'; - $edges["Pascal"][paren_begin . "," . sq_literal] = '<span class="literal">\''; - $edges["Pascal"][slash_begin . "," . dq_literal] = '<span class="literal">"'; - $edges["Pascal"][slash_begin . "," . sq_literal] = '<span class="literal">\''; - $edges["Pascal"][slash_begin . "," . line_comment] = '<span class="comment">//'; - $edges["Pascal"][normal_text . "," . block_comment] = '<span class="comment">{'; - $edges["Pascal"][line_comment . "," . normal_text] = '</span>'; - $edges["Pascal"][block_comment . "," . normal_text] = '</span>'; - - $edges["XML"][normal_text . "," . html_entity] = '<span class="html_entity">&'; - $edges["XML"][html_entity . "," . normal_text] = '</span>'; - $edges["XML"][html_entity . "," . xml_tag_begin] = '</span>'; - $edges["XML"][xml_tag . "," . normal_text] = '></span>'; - $edges["XML"][xml_tag_begin . "," . xml_pi] = '<span class="xml_pi"><?'; - $edges["XML"][xml_tag_begin . "," . line_comment] = '<span class="comment"><!'; - $edges["XML"][line_comment . "," . normal_text] = '></span>'; - $edges["XML"][xml_tag . "," . dq_literal] = '<span class="literal">"'; - $edges["XML"][dq_literal . "," . xml_tag] = '"</span>'; - $edges["XML"][dq_literal . "," . dq_escape] = '<span class="html_entity">&'; - $edges["XML"][dq_escape . "," . dq_literal] = '</span>'; - $edges["XML"][xml_tag . "," . sq_literal] = '<span class="literal">\''; - $edges["XML"][sq_literal . "," . xml_tag] = '\'</span>'; - $edges["XML"][sq_literal . "," . sq_escape] = '<span class="html_entity">&'; - $edges["XML"][sq_escape . "," . sq_literal] = '</span>'; - - // - // The State Machine - // - if (array_key_exists($language, $initial_state)) { - $state = $initial_state[$language]; - } else { - $state = normal_text; - } - - $output = ""; - $span = ""; - while (strlen($text) > 0) { - $ch = substr($text, 0, 1); - $text = substr($text, 1); - - $oldstate = $state; - $state = (array_key_exists($ch, $states[$language][$state])) ? - $states[$language][$state][$ch] : - $states[$language][$state][0]; - - $span .= $ch; - - if ($oldstate != $state) { - if (array_key_exists($language, $process) && - array_key_exists($oldstate, $process[$language])) { - if (array_key_exists($state, $process[$language][$oldstate])) { - $pf = $process[$language][$oldstate][$state]; - $output .= $pf($span, $language, $ch); - } else { - $pf = $process[$language][$oldstate][0]; - $output .= $pf($span, $language, $ch); - } - } else { - $output .= $span; - } - - if (array_key_exists($language, $edges) && - array_key_exists("$oldstate,$state", $edges[$language])) { - $output .= $edges[$language]["$oldstate,$state"]; - } - - $span = ""; - } - } - - if (array_key_exists($language, $process_end) && $state == normal_text) { - $output .= $process_end[$language]($span, $language); - } else { - $output .= $span; - } - - if ($state != normal_text) { - if (array_key_exists($language, $edges) && - array_key_exists("$state," . normal_text, $edges[$language])) { - $output .= $edges[$language]["$state," . normal_text]; - } - - } - - return $output; -} diff --git a/src/lib.inc.php b/src/lib.inc.php index 5d3b2655..eefef9c0 100644 --- a/src/lib.inc.php +++ b/src/lib.inc.php @@ -5,44 +5,34 @@ * * $Id: lib.inc.php,v 1.123 2008/04/06 01:10:35 xzilla Exp $ */ -DEFINE('BASE_PATH', dirname(__DIR__)); +DEFINE('BASE_PATH', dirname(__DIR__)); ini_set('error_log', BASE_PATH . '/temp/logs/phppga.php_error.log'); +$debugmode = true; + +if ($debugmode) { + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + error_reporting(E_ALL); +} + +require_once BASE_PATH . '/src/errorhandler.inc.php'; + +if (!defined('ADODB_ERROR_HANDLER_TYPE')) { + define('ADODB_ERROR_HANDLER_TYPE', E_USER_ERROR); +} +if (!defined('ADODB_ERROR_HANDLER')) { + define('ADODB_ERROR_HANDLER', 'Error_Handler'); +} require_once BASE_PATH . '/vendor/autoload.php'; -include_once BASE_PATH . '/src/errorhandler.inc.php'; -include_once BASE_PATH . '/src/decorator.inc.php'; Kint::enabled(true); $handler = PhpConsole\Handler::getInstance(); -/* You can override default Handler behavior: -$handler->setHandleErrors(false); // disable errors handling -$handler->setHandleExceptions(false); // disable exceptions handling -$handler->setCallOldHandlers(false); // disable passing errors & exceptions to prviously defined handlers - */ -$handler->start(); // initialize handlers +$handler->start(); // initialize handlers*/ PhpConsole\Helper::register(); // it will register global PC class -// Set error reporting level to max -error_reporting(E_ALL); - -// Application name -$appName = 'phpPgAdmin'; - -// Application version -$appVersion = '6.0.0-alpha'; - -// PostgreSQL and PHP minimum version -$postgresqlMinVer = '9.3'; -$phpMinVer = '5.5'; -$debugmode = true; - -// Check the version of PHP -if (version_compare(phpversion(), $phpMinVer, '<')) { - exit(sprintf('Version of PHP not supported. Please upgrade to version %s or later.', $phpMinVer)); -} - // Check to see if the configuration file exists, if not, explain if (file_exists(BASE_PATH . '/config.inc.php')) { $conf = []; @@ -77,7 +67,6 @@ if (!ini_get('session.auto_start')) { session_name('PPA_ID'); session_start(); } -//Kint::dump($_SERVER); $config = [ 'msg' => '', @@ -85,10 +74,18 @@ $config = [ 'conf' => $conf, 'lang' => $lang, 'language' => $_language, + 'settings' => [ + 'base_path' => BASE_PATH, 'debug' => $debugmode, - 'appVersion' => $appVersion, - 'appName' => htmlspecialchars($appName), + // Application version + 'appVersion' => '6.0.0-alpha', + // Application name + 'appName' => 'phpPgAdmin', + + // PostgreSQL and PHP minimum version + 'postgresqlMinVer' => '9.3', + 'phpMinVer' => '5.5', 'displayErrorDetails' => true, 'addContentLengthHeader' => false, ], @@ -99,8 +96,7 @@ $app = new \Slim\App($config); // Fetch DI Container $container = $app->getContainer(); -$plugin_manager = new \PHPPgAdmin\PluginManager($app); -$container['plugin_manager'] = $plugin_manager; +$container['plugin_manager'] = new \PHPPgAdmin\PluginManager($app); $container['serializer'] = function ($c) { $serializerbuilder = \JMS\Serializer\SerializerBuilder::create(); @@ -132,6 +128,7 @@ $container['misc'] = $misc; // 4. Check for theme by server/db/user $_server_info = $misc->getServerInfo(); + include_once BASE_PATH . '/src/themes.php'; $container['appThemes'] = $appThemes; @@ -157,31 +154,9 @@ if (!function_exists('pg_connect')) { exit; } -//PC::debug($_server_info, 'server_info'); +$container['action'] = (isset($_REQUEST['action'])) ? $_REQUEST['action'] : ''; -// Create data accessor object, if necessary -if (!isset($_no_db_connection)) { - if ($misc->getServerId() === null) { - echo $lang['strnoserversupplied']; - exit; - } - - /* starting with PostgreSQL 9.0, we can set the application name */ - if (isset($_server_info['pgVersion']) && $_server_info['pgVersion'] >= 9) { - putenv("PGAPPNAME={$appName}_{$appVersion}"); - } - - // Redirect to the login form if not logged in - if (!isset($_server_info['username'])) { - include BASE_PATH . '/src/views/login.php'; - exit; - } - - // Connect to database and set the global $data variable - $data = $misc->getDatabaseAccessor(); - -} -$action = (isset($_REQUEST['action'])) ? $_REQUEST['action'] : ''; if (!isset($msg)) { $msg = ''; -}
\ No newline at end of file +} +$container['msg'] = $msg;
\ No newline at end of file diff --git a/src/themes/datatables.min.css b/src/themes/datatables.min.css new file mode 100644 index 00000000..0592cc87 --- /dev/null +++ b/src/themes/datatables.min.css @@ -0,0 +1,625 @@ +/* + * This combined file was created by the DataTables downloader builder: + * https://datatables.net/download + * + * To rebuild or modify this file with the latest versions of the included + * software please visit: + * https://datatables.net/download/#ju/dt-1.10.12/fh-3.1.2 + * + * Included libraries: + * DataTables 1.10.12, FixedHeader 3.1.2 + */ + +table.dataTable { + width: auto; + clear: both; + min-width: 100%; +} + +table.dataTable thead th, +table.dataTable tfoot th { + font-weight: bold +} + +table.dataTable thead th { + padding: 2px 4px +} + +table.dataTable thead td { + padding: 10px 18px +} + +table.dataTable thead th:active, +table.dataTable thead td:active { + outline: none +} + +table.dataTable tbody tr.selected { + background-color: #B0BED9 +} + +table.dataTable.row-border tbody th, +table.dataTable.row-border tbody td, +table.dataTable.display tbody th, +table.dataTable.display tbody td { + border-top: 1px solid #ddd +} + +table.dataTable.row-border tbody tr:first-child th, +table.dataTable.row-border tbody tr:first-child td, +table.dataTable.display tbody tr:first-child th, +table.dataTable.display tbody tr:first-child td { + border-top: none +} + +table.dataTable.cell-border tbody th, +table.dataTable.cell-border tbody td { + border-top: 1px solid #ddd; + border-right: 1px solid #ddd +} + +table.dataTable.cell-border tbody tr th:first-child, +table.dataTable.cell-border tbody tr td:first-child { + border-left: 1px solid #ddd +} + +table.dataTable.cell-border tbody tr:first-child th, +table.dataTable.cell-border tbody tr:first-child td { + border-top: none +} + +table.dataTable.stripe tbody tr.odd, +table.dataTable.display tbody tr.odd { + background-color: #f9f9f9 +} + +table.dataTable.stripe tbody tr.odd.selected, +table.dataTable.display tbody tr.odd.selected { + background-color: #acbad4 +} + +table.dataTable.hover tbody tr:hover, +table.dataTable.display tbody tr:hover { + background-color: #f6f6f6 +} + +table.dataTable.hover tbody tr:hover.selected, +table.dataTable.display tbody tr:hover.selected { + background-color: #aab7d1 +} + +table.dataTable thead .sorting, +table.dataTable thead .sorting_asc, +table.dataTable thead .sorting_desc { + cursor: pointer; + *cursor: hand +} + +table.dataTable thead .sorting, +table.dataTable thead .sorting_asc, +table.dataTable thead .sorting_desc, +table.dataTable thead .sorting_asc_disabled, +table.dataTable thead .sorting_desc_disabled { + background-repeat: no-repeat; + background-position: center right +} + +tab table.dataTable thead .sorting { + background-image: url("/images/datatables/sort_both.png") +} + +table.dataTable thead .sorting_asc { + background-image: url("/images/datatables/sort_asc.png") +} + +table.dataTable thead .sorting_desc { + background-image: url("/images/datatables/sort_desc.png") +} + +table.dataTable thead .sorting_asc_disabled { + background-image: url("/images/datatables/sort_asc_disabled.png") +} + +table.dataTable thead .sorting_desc_disabled { + background-image: url("/images/datatables/sort_desc_disabled.png") +} + +table.dataTable.order-column tbody tr>.sorting_1, +table.dataTable.order-column tbody tr>.sorting_2, +table.dataTable.order-column tbody tr>.sorting_3, +table.dataTable.display tbody tr>.sorting_1, +table.dataTable.display tbody tr>.sorting_2, +table.dataTable.display tbody tr>.sorting_3 { + background-color: #fafafa +} + +table.dataTable.order-column tbody tr.selected>.sorting_1, +table.dataTable.order-column tbody tr.selected>.sorting_2, +table.dataTable.order-column tbody tr.selected>.sorting_3, +table.dataTable.display tbody tr.selected>.sorting_1, +table.dataTable.display tbody tr.selected>.sorting_2, +table.dataTable.display tbody tr.selected>.sorting_3 { + background-color: #acbad5 +} + +table.dataTable.display tbody tr.odd>.sorting_1, +table.dataTable.order-column.stripe tbody tr.odd>.sorting_1 { + background-color: #f1f1f1 +} + +table.dataTable.display tbody tr.odd>.sorting_2, +table.dataTable.order-column.stripe tbody tr.odd>.sorting_2 { + background-color: #f3f3f3 +} + +table.dataTable.display tbody tr.odd>.sorting_3, +table.dataTable.order-column.stripe tbody tr.odd>.sorting_3 { + background-color: whitesmoke +} + +table.dataTable.display tbody tr.odd.selected>.sorting_1, +table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1 { + background-color: #a6b4cd +} + +table.dataTable.display tbody tr.odd.selected>.sorting_2, +table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2 { + background-color: #a8b5cf +} + +table.dataTable.display tbody tr.odd.selected>.sorting_3, +table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3 { + background-color: #a9b7d1 +} + +table.dataTable.display tbody tr.even>.sorting_1, +table.dataTable.order-column.stripe tbody tr.even>.sorting_1 { + background-color: #fafafa +} + +table.dataTable.display tbody tr.even>.sorting_2, +table.dataTable.order-column.stripe tbody tr.even>.sorting_2 { + background-color: #fcfcfc +} + +table.dataTable.display tbody tr.even>.sorting_3, +table.dataTable.order-column.stripe tbody tr.even>.sorting_3 { + background-color: #fefefe +} + +table.dataTable.display tbody tr.even.selected>.sorting_1, +table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1 { + background-color: #acbad5 +} + +table.dataTable.display tbody tr.even.selected>.sorting_2, +table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2 { + background-color: #aebcd6 +} + +table.dataTable.display tbody tr.even.selected>.sorting_3, +table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3 { + background-color: #afbdd8 +} + +table.dataTable.display tbody tr:hover>.sorting_1, +table.dataTable.order-column.hover tbody tr:hover>.sorting_1 { + background-color: #eaeaea +} + +table.dataTable.display tbody tr:hover>.sorting_2, +table.dataTable.order-column.hover tbody tr:hover>.sorting_2 { + background-color: #ececec +} + +table.dataTable.display tbody tr:hover>.sorting_3, +table.dataTable.order-column.hover tbody tr:hover>.sorting_3 { + background-color: #efefef +} + +table.dataTable.display tbody tr:hover.selected>.sorting_1, +table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1 { + background-color: #a2aec7 +} + +table.dataTable.display tbody tr:hover.selected>.sorting_2, +table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2 { + background-color: #a3b0c9 +} + +table.dataTable.display tbody tr:hover.selected>.sorting_3, +table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3 { + background-color: #a5b2cb +} + +table.dataTable.no-footer { + border-bottom: 1px solid #111 +} + +table.dataTable.nowrap th, +table.dataTable.nowrap td { + white-space: nowrap +} + +table.dataTable.compact thead th, +table.dataTable.compact thead td { + padding: 4px 17px 4px 4px +} + +table.dataTable.compact tfoot th, +table.dataTable.compact tfoot td { + padding: 4px +} + +table.dataTable.compact tbody th, +table.dataTable.compact tbody td { + padding: 4px +} + +table.dataTable th.dt-left, +table.dataTable td.dt-left { + text-align: left +} + +table.dataTable th.dt-center, +table.dataTable td.dt-center, +table.dataTable td.dataTables_empty { + text-align: center +} + +table.dataTable th.dt-right, +table.dataTable td.dt-right { + text-align: right +} + +table.dataTable th.dt-justify, +table.dataTable td.dt-justify { + text-align: justify +} + +table.dataTable th.dt-nowrap, +table.dataTable td.dt-nowrap { + white-space: nowrap +} + +table.dataTable thead th.dt-head-left, +table.dataTable thead td.dt-head-left, +table.dataTable tfoot th.dt-head-left, +table.dataTable tfoot td.dt-head-left { + text-align: left +} + +table.dataTable thead th.dt-head-center, +table.dataTable thead td.dt-head-center, +table.dataTable tfoot th.dt-head-center, +table.dataTable tfoot td.dt-head-center { + text-align: center +} + +table.dataTable thead th.dt-head-right, +table.dataTable thead td.dt-head-right, +table.dataTable tfoot th.dt-head-right, +table.dataTable tfoot td.dt-head-right { + text-align: right +} + +table.dataTable thead th.dt-head-justify, +table.dataTable thead td.dt-head-justify, +table.dataTable tfoot th.dt-head-justify, +table.dataTable tfoot td.dt-head-justify { + text-align: justify +} + +table.dataTable thead th.dt-head-nowrap, +table.dataTable thead td.dt-head-nowrap, +table.dataTable tfoot th.dt-head-nowrap, +table.dataTable tfoot td.dt-head-nowrap { + white-space: nowrap +} + +table.dataTable tbody th.dt-body-left, +table.dataTable tbody td.dt-body-left { + text-align: left +} + +table.dataTable tbody th.dt-body-center, +table.dataTable tbody td.dt-body-center { + text-align: center +} + +table.dataTable tbody th.dt-body-right, +table.dataTable tbody td.dt-body-right { + text-align: right +} + +table.dataTable tbody th.dt-body-justify, +table.dataTable tbody td.dt-body-justify { + text-align: justify +} + +table.dataTable tbody th.dt-body-nowrap, +table.dataTable tbody td.dt-body-nowrap { + white-space: nowrap +} + +table.dataTable, +table.dataTable th, +table.dataTable td { + -webkit-box-sizing: content-box; + box-sizing: content-box +} + +.dataTables_wrapper { + margin-top: 10px; + position: relative; + clear: both; + *zoom: 1; + zoom: 1 +} + +.dataTables_wrapper .dataTables_length { + margin: 5px 5px; + padding: 1px 2px 1px 5px; + float: left +} + +.dataTables_wrapper .dataTables_filter { + margin: 5px 15px; + float: right; + text-align: right +} + +.dataTables_wrapper .dataTables_filter input { + margin-left: 0.5em +} + +.dataTables_wrapper .dataTables_info { + clear: both; + float: left; + padding-top: 0.755em +} + +.dataTables_wrapper .dataTables_paginate { + float: right; + text-align: right; + padding-top: 0.25em +} + +.dataTables_wrapper .dataTables_paginate .paginate_button { + box-sizing: border-box; + display: inline-block; + min-width: 1.5em; + padding: 0.5em 1em; + margin-left: 2px; + text-align: center; + text-decoration: none !important; + cursor: pointer; + *cursor: hand; + color: #333 !important; + border: 1px solid transparent; + border-radius: 2px +} + +.dataTables_wrapper .dataTables_paginate .paginate_button.current, +.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { + color: #333 !important; + border: 1px solid #979797; + background-color: white; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(100%, #dcdcdc)); + background: -webkit-linear-gradient(top, #fff 0%, #dcdcdc 100%); + background: -moz-linear-gradient(top, #fff 0%, #dcdcdc 100%); + background: -ms-linear-gradient(top, #fff 0%, #dcdcdc 100%); + background: -o-linear-gradient(top, #fff 0%, #dcdcdc 100%); + background: linear-gradient(to bottom, #fff 0%, #dcdcdc 100%) +} + +.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, +.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, +.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active { + cursor: default; + color: #666 !important; + border: 1px solid transparent; + background: transparent; + box-shadow: none +} + +.dataTables_wrapper .dataTables_paginate .paginate_button:hover { + color: white !important; + border: 1px solid #111; + background-color: #585858; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); + background: -webkit-linear-gradient(top, #585858 0%, #111 100%); + background: -moz-linear-gradient(top, #585858 0%, #111 100%); + background: -ms-linear-gradient(top, #585858 0%, #111 100%); + background: -o-linear-gradient(top, #585858 0%, #111 100%); + background: linear-gradient(to bottom, #585858 0%, #111 100%) +} + +.dataTables_wrapper .dataTables_paginate .paginate_button:active { + outline: none; + background-color: #2b2b2b; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); + background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); + box-shadow: inset 0 0 3px #111 +} + +.dataTables_wrapper .dataTables_paginate .ellipsis { + padding: 0 1em +} + +.dataTables_wrapper .dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 100%; + height: 40px; + margin-left: -50%; + margin-top: -25px; + padding-top: 20px; + text-align: center; + font-size: 1.2em; + background-color: white; + background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0))); + background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%) +} + +.dataTables_wrapper .dataTables_length, +.dataTables_wrapper .dataTables_filter, +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_processing, +.dataTables_wrapper .dataTables_paginate { + color: #333 +} + +.dataTables_wrapper .dataTables_scroll { + clear: both +} + +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody { + *margin-top: -1px; + -webkit-overflow-scrolling: touch +} + +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody th, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody td { + vertical-align: middle +} + +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody th>div.dataTables_sizing, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody td>div.dataTables_sizing { + height: 0; + overflow: hidden; + margin: 0 !important; + padding: 0 !important +} + +.dataTables_wrapper.no-footer .dataTables_scrollBody { + border-bottom: 1px solid #111 +} + +.dataTables_wrapper.no-footer div.dataTables_scrollHead table, +.dataTables_wrapper.no-footer div.dataTables_scrollBody table { + border-bottom: none +} + +.dataTables_wrapper:after { + visibility: hidden; + display: block; + content: ""; + clear: both; + height: 0 +} + +@media screen and (max-width: 767px) { + .dataTables_wrapper .dataTables_info, + .dataTables_wrapper .dataTables_paginate { + float: none; + text-align: center + } + .dataTables_wrapper .dataTables_paginate { + margin-top: 0.5em + } +} + +@media screen and (max-width: 640px) { + .dataTables_wrapper .dataTables_length, + .dataTables_wrapper .dataTables_filter { + float: none; + text-align: center + } + .dataTables_wrapper .dataTables_filter { + margin-top: 0.5em + } +} + +table.dataTable thead th div.DataTables_sort_wrapper { + position: relative +} + +table.dataTable thead th div.DataTables_sort_wrapper span { + position: absolute; + top: 50%; + margin-top: -8px; + right: -18px +} + +table.dataTable thead th.ui-state-default, +table.dataTable tfoot th.ui-state-default { + border-left-width: 0 +} + +table.dataTable thead th.ui-state-default:first-child, +table.dataTable tfoot th.ui-state-default:first-child { + border-left-width: 1px +} + +.dataTables_wrapper .dataTables_paginate .fg-button { + box-sizing: border-box; + display: inline-block; + min-width: 1.5em; + padding: 0.5em; + margin-left: 2px; + text-align: center; + text-decoration: none !important; + cursor: pointer; + *cursor: hand; + border: 1px solid transparent +} + +.dataTables_wrapper .dataTables_paginate .fg-button:active { + outline: none +} + +.dataTables_wrapper .dataTables_paginate .fg-button:first-child { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px +} + +.dataTables_wrapper .dataTables_paginate .fg-button:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px +} + +.dataTables_wrapper .ui-widget-header { + font-weight: normal +} + +.dataTables_wrapper .ui-toolbar { + padding: 8px +} + +.dataTables_wrapper.no-footer .dataTables_scrollBody { + border-bottom: none +} + +.dataTables_wrapper .dataTables_length, +.dataTables_wrapper .dataTables_filter, +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_processing, +.dataTables_wrapper .dataTables_paginate { + color: inherit +} + +table.fixedHeader-floating { + position: fixed !important; + background-color: white +} + +table.fixedHeader-locked { + position: absolute !important; + background-color: white +} + +@media print { + table.fixedHeader-floating { + display: none + } +} diff --git a/src/themes/default/global.css b/src/themes/default/global.css index 4407be23..4c80f79b 100644 --- a/src/themes/default/global.css +++ b/src/themes/default/global.css @@ -10,9 +10,9 @@ body { background-color: #FFFFFF; - margin: 4px; + margin: 0; font-family: roboto; - padding: 0px; + padding: 0; font-size: smaller; /*0.8em;*/ } @@ -36,7 +36,7 @@ body.bottombar { } h2 { - color: #666633; + color: #336699; font-size: medium; /*1.3em;*/ font-family: roboto; @@ -48,7 +48,7 @@ h2 { } h3 { - color: #666633; + color: #336699; font-size: small; font-family: roboto; font-weight: bold; @@ -85,7 +85,7 @@ th.data { color: #000000; background-color: #cfd8dc; font-family: roboto; - font-size: 1.2em; + font-size: 1em; padding: 2px 4px; } @@ -115,6 +115,10 @@ td.topbar { text-align: left; } +.topbar .toplink { + margin-right: 20px; +} + ul.toplink, ul.navlink { list-style: none; diff --git a/src/themes/global.css b/src/themes/global.css index bd8c90a3..2a5a4160 100644 --- a/src/themes/global.css +++ b/src/themes/global.css @@ -27,6 +27,55 @@ p.comment { padding-left: 5pt; } +.flexbox_wrapper, +html, +body.flexbox_body { + height: 100%; + margin: 0; +} + +.flexbox_wrapper { + display: flex; +} + +.flexbox_wrapper > .browser_container { + height: 100%; + margin: 0px; + padding: 0px; + text-align: left; + float: left; + flex: 0 0 300px; +} + +.flexbox_wrapper >.detail_container { + height: 100%; + width: 100%; + margin: 0px; + padding: 0px; + text-align: left; + float: left; + flex: 1 1; +} + +.topbar { + position: fixed; + z-index: 100; + width: 100%; + top: 0; +} + +.trail { + position: fixed; + z-index: 100; + width: 100%; + top: 27px; +} + +.detailbody { + margin-top: 58px; + margin-bottom: -58px; +} + /** Browser Tree using XLoadTree 2 **/ diff --git a/src/tree/aggregates.php b/src/tree/aggregates.php deleted file mode 100644 index fb318b49..00000000 --- a/src/tree/aggregates.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; -/** - * Manage aggregates in a database - * - * $Id: aggregates.php,v 1.27 2008/01/19 13:46:15 ioguix Exp $ - */ - -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $aggregates = $data->getAggregates(); - - $proto = concat(Decorator::field('proname'), ' (', Decorator::field('proargtypes'), ')'); - $reqvars = $misc->getRequestVars('aggregate'); - - $attrs = [ - 'text' => $proto, - 'icon' => 'Aggregate', - 'toolTip' => Decorator::field('aggcomment'), - 'action' => Decorator::redirecturl('redirect.php', - $reqvars, - [ - 'action' => 'properties', - 'aggrname' => Decorator::field('proname'), - 'aggrtype' => Decorator::field('proargtypes'), - ] - ), - ]; - - return $misc->printTree($aggregates, $attrs, 'aggregates', false); -} diff --git a/src/tree/all_db.php b/src/tree/all_db.php deleted file mode 100644 index f713f784..00000000 --- a/src/tree/all_db.php +++ /dev/null @@ -1,24 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; - -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $databases = $data->getDatabases(); - - $reqvars = $misc->getRequestVars('database'); - - $attrs = [ - 'text' => Decorator::field('datname'), - 'icon' => 'Database', - 'toolTip' => Decorator::field('datcomment'), - 'action' => Decorator::redirecturl('redirect.php', $reqvars, ['database' => Decorator::field('datname')]), - 'branch' => Decorator::branchurl('database.php', $reqvars, ['action' => 'tree', 'database' => Decorator::field('datname')]), - ]; - - return $misc->printTree($databases, $attrs, 'databases', false); -} diff --git a/src/tree/casts.php b/src/tree/casts.php deleted file mode 100644 index c1d0137c..00000000 --- a/src/tree/casts.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; -/** - * Manage casts in a database - * - * $Id: casts.php,v 1.16 2007/09/25 16:08:05 ioguix Exp $ - */ - -/** - * Generate XML for the browser tree. - */ -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $casts = $data->getCasts(); - - $proto = concat(Decorator::field('castsource'), ' AS ', Decorator::field('casttarget')); - - $attrs = [ - 'text' => $proto, - 'icon' => 'Cast', - ]; - - $misc->printTree($casts, $attrs, 'casts'); - exit; -} diff --git a/src/tree/constraints.php b/src/tree/constraints.php deleted file mode 100644 index 702aca28..00000000 --- a/src/tree/constraints.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; -/** - * List constraints on a table - * - * $Id: constraints.php,v 1.56 2007/12/31 16:46:07 xzilla Exp $ - */ - -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $constraints = $data->getConstraints($_REQUEST['table']); - - $reqvars = $misc->getRequestVars('schema'); - - function getIcon($f) { - switch ($f['contype']) { - case 'u': - return 'UniqueConstraint'; - case 'c': - return 'CheckConstraint'; - case 'f': - return 'ForeignKey'; - case 'p': - return 'PrimaryKey'; - - } - } - - $attrs = [ - 'text' => Decorator::field('conname'), - 'icon' => callback('getIcon'), - ]; - - $misc->printTree($constraints, $attrs, 'constraints'); - exit; -} diff --git a/src/tree/conversions.php b/src/tree/conversions.php deleted file mode 100644 index b1afc6bb..00000000 --- a/src/tree/conversions.php +++ /dev/null @@ -1,29 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; -/** - * Manage conversions in a database - * - * $Id: conversions.php,v 1.15 2007/08/31 18:30:10 ioguix Exp $ - */ - -/** - * Generate XML for the browser tree. - */ -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $conversions = $data->getconversions(); - - $attrs = [ - 'text' => Decorator::field('conname'), - 'icon' => 'Conversion', - 'toolTip' => Decorator::field('concomment'), - ]; - - $misc->printTree($conversions, $attrs, 'conversions'); - exit; -} diff --git a/src/tree/database.php b/src/tree/database.php deleted file mode 100755 index 3eeca0ee..00000000 --- a/src/tree/database.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; -/** - * Manage schemas within a database - * - * $Id: database.php,v 1.104 2007/11/30 06:04:43 xzilla Exp $ - */ -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $reqvars = $misc->getRequestVars('database'); - - $tabs = $misc->getNavTabs('database'); - - $items = $misc->adjustTabsForTree($tabs); - \PC::debug($reqvars, 'reqvars'); - $attrs = [ - 'text' => Decorator::field('title'), - 'icon' => Decorator::field('icon'), - 'action' => Decorator::actionurl(Decorator::field('url'), $reqvars, Decorator::field('urlvars', [])), - 'branch' => Decorator::branchurl(Decorator::field('url'), $reqvars, Decorator::field('urlvars'), ['action' => 'tree']), - ]; - - return $misc->printTree($items, $attrs, 'database', false); - -} diff --git a/src/tree/domains.php b/src/tree/domains.php deleted file mode 100644 index f3faa1b4..00000000 --- a/src/tree/domains.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; -/** - * Manage domains in a database - * - * $Id: domains.php,v 1.34 2007/09/13 13:41:01 ioguix Exp $ - */ - -/** - * Generate XML for the browser tree. - */ -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $domains = $data->getDomains(); - - $reqvars = $misc->getRequestVars('domain'); - - $attrs = [ - 'text' => Decorator::field('domname'), - 'icon' => 'Domain', - 'toolTip' => Decorator::field('domcomment'), - 'action' => Decorator::actionurl('domains.php', - $reqvars, - [ - 'action' => 'properties', - 'domain' => Decorator::field('domname'), - ] - ), - ]; - - return $misc->printTree($domains, $attrs, 'domains', false); -} diff --git a/src/tree/fulltext.php b/src/tree/fulltext.php deleted file mode 100644 index b8d4eb43..00000000 --- a/src/tree/fulltext.php +++ /dev/null @@ -1,91 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; - -/** - * Manage fulltext configurations, dictionaries and mappings - * - * $Id: fulltext.php,v 1.6 2008/03/17 21:35:48 ioguix Exp $ - */ - -/** - * Generate XML for the browser tree. - */ -function doTree($container) { - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $tabs = $misc->getNavTabs('fulltext'); - $items = $misc->adjustTabsForTree($tabs); - - $reqvars = $misc->getRequestVars('ftscfg'); - - $attrs = [ - 'text' => Decorator::field('title'), - 'icon' => Decorator::field('icon'), - 'action' => Decorator::actionurl('fulltext.php', - $reqvars, - field('urlvars') - ), - 'branch' => Decorator::branchurl('fulltext.php', - $reqvars, - [ - 'action' => 'subtree', - 'what' => Decorator::field('icon'), // IZ: yeah, it's ugly, but I do not want to change navigation tabs arrays - ] - ), - ]; - - return $misc->printTree($items, $attrs, 'fts', false); -} - -function doSubTree($container, $what) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - switch ($what) { - case 'FtsCfg': - $items = $data->getFtsConfigurations(false); - $urlvars = ['action' => 'viewconfig', 'ftscfg' => Decorator::field('name')]; - break; - case 'FtsDict': - $items = $data->getFtsDictionaries(false); - $urlvars = ['action' => 'viewdicts']; - break; - case 'FtsParser': - $items = $data->getFtsParsers(false); - $urlvars = ['action' => 'viewparsers']; - break; - default: - exit; - } - - $reqvars = $misc->getRequestVars('ftscfg'); - - $attrs = [ - 'text' => Decorator::field('name'), - 'icon' => $what, - 'toolTip' => Decorator::field('comment'), - 'action' => Decorator::actionurl('fulltext.php', - $reqvars, - $urlvars - ), - 'branch' => Decorator::ifempty(Decorator::field('branch'), - '', - url('fulltext.php', - $reqvars, - [ - 'action' => 'subtree', - 'ftscfg' => Decorator::field('name'), - ] - ) - ), - ]; - - return $misc->printTree($items, $attrs, strtolower($what), false); - -} diff --git a/src/tree/functions.php b/src/tree/functions.php deleted file mode 100644 index 2768cabf..00000000 --- a/src/tree/functions.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; - -/** - * Manage functions in a database - * - * $Id: functions.php,v 1.78 2008/01/08 22:50:29 xzilla Exp $ - */ - -/** - * Generate XML for the browser tree. - */ -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $funcs = $data->getFunctions(); - - $proto = concat(Decorator::field('proname'), ' (', Decorator::field('proarguments'), ')'); - - $reqvars = $misc->getRequestVars('function'); - - $attrs = [ - 'text' => $proto, - 'icon' => 'Function', - 'toolTip' => Decorator::field('procomment'), - 'action' => Decorator::redirecturl('redirect.php', - $reqvars, - [ - 'action' => 'properties', - 'function' => $proto, - 'function_oid' => Decorator::field('prooid'), - ] - ), - ]; - - return $misc->printTree($funcs, $attrs, 'functions', false); -} diff --git a/src/tree/indexes.php b/src/tree/indexes.php deleted file mode 100644 index 673cf5a2..00000000 --- a/src/tree/indexes.php +++ /dev/null @@ -1,39 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; - -/** - * List indexes on a table - * - * $Id: indexes.php,v 1.46 2008/01/08 22:50:29 xzilla Exp $ - */ - -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $indexes = $data->getIndexes($_REQUEST['table']); - - $reqvars = $misc->getRequestVars('table'); - - function getIcon($f) { - if ($f['indisprimary'] == 't') { - return 'PrimaryKey'; - } - - if ($f['indisunique'] == 't') { - return 'UniqueConstraint'; - } - - return 'Index'; - } - - $attrs = [ - 'text' => Decorator::field('indname'), - 'icon' => callback('getIcon'), - ]; - - return $misc->printTree($indexes, $attrs, 'indexes', false); -} diff --git a/src/tree/languages.php b/src/tree/languages.php deleted file mode 100644 index 5b03dad2..00000000 --- a/src/tree/languages.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; -/** - * Manage languages in a database - * - * $Id: languages.php,v 1.13 2007/08/31 18:30:11 ioguix Exp $ - */ - -/** - * Generate XML for the browser tree. - */ -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $languages = $data->getLanguages(); - - $attrs = [ - 'text' => Decorator::field('lanname'), - 'icon' => 'Language', - ]; - - return $misc->printTree($languages, $attrs, 'languages', false); - -} diff --git a/src/tree/opclasses.php b/src/tree/opclasses.php deleted file mode 100644 index 4a0c2907..00000000 --- a/src/tree/opclasses.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; - -/** - * Manage opclasss in a database - * - * $Id: opclasses.php,v 1.10 2007/08/31 18:30:11 ioguix Exp $ - */ - -/** - * Generate XML for the browser tree. - */ -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $opclasses = $data->getOpClasses(); - - // OpClass prototype: "op_class/access_method" - $proto = concat(Decorator::field('opcname'), '/', Decorator::field('amname')); - - $attrs = [ - 'text' => $proto, - 'icon' => 'OperatorClass', - 'toolTip' => Decorator::field('opccomment'), - ]; - - return $misc->printTree($opclasses, $attrs, 'opclasses', false); -} diff --git a/src/tree/operators.php b/src/tree/operators.php deleted file mode 100644 index 47b6db64..00000000 --- a/src/tree/operators.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; - -/** - * Manage operators in a database - * - * $Id: operators.php,v 1.29 2007/08/31 18:30:11 ioguix Exp $ - */ - -/** - * Generate XML for the browser tree. - */ -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $operators = $data->getOperators(); - - // Operator prototype: "type operator type" - $proto = concat(Decorator::field('oprleftname'), ' ', Decorator::field('oprname'), ' ', Decorator::field('oprrightname')); - - $reqvars = $misc->getRequestVars('operator'); - - $attrs = [ - 'text' => $proto, - 'icon' => 'Operator', - 'toolTip' => Decorator::field('oprcomment'), - 'action' => Decorator::actionurl('operators.php', - $reqvars, - [ - 'action' => 'properties', - 'operator' => $proto, - 'operator_oid' => Decorator::field('oid'), - ] - ), - ]; - - return $misc->printTree($operators, $attrs, 'operators', false); -} diff --git a/src/tree/rules.php b/src/tree/rules.php deleted file mode 100644 index 17cebdbe..00000000 --- a/src/tree/rules.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; - -/** - * List rules on a table OR view - * - * $Id: rules.php,v 1.33 2007/08/31 18:30:11 ioguix Exp $ - */ -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $rules = $data->getRules($_REQUEST[$_REQUEST['subject']]); - - $reqvars = $misc->getRequestVars($_REQUEST['subject']); - - $attrs = [ - 'text' => Decorator::field('rulename'), - 'icon' => 'Rule', - ]; - - return $misc->printTree($rules, $attrs, 'rules', false); -} diff --git a/src/tree/schemas.php b/src/tree/schemas.php deleted file mode 100755 index 7f607ab3..00000000 --- a/src/tree/schemas.php +++ /dev/null @@ -1,77 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; - -/** - * Manage schemas in a database - * - * $Id: schemas.php,v 1.22 2007/12/15 22:57:43 ioguix Exp $ - */ - -/** - * Generate XML for the browser tree. - */ -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $schemas = $data->getSchemas(); - - $reqvars = $misc->getRequestVars('schema'); - - $attrs = [ - 'text' => Decorator::field('nspname'), - 'icon' => 'Schema', - 'toolTip' => Decorator::field('nspcomment'), - 'action' => Decorator::redirecturl('redirect.php', - $reqvars, - [ - 'subject' => 'schema', - 'schema' => Decorator::field('nspname'), - ] - ), - 'branch' => Decorator::branchurl('schemas.php', - $reqvars, - [ - 'action' => 'subtree', - 'schema' => Decorator::field('nspname'), - ] - ), - ]; - - return $misc->printTree($schemas, $attrs, 'schemas', false); - -} - -function doSubTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $tabs = $misc->getNavTabs('schema'); - - $items = $misc->adjustTabsForTree($tabs); - - $reqvars = $misc->getRequestVars('schema'); - - $attrs = [ - 'text' => Decorator::field('title'), - 'icon' => Decorator::field('icon'), - 'action' => Decorator::actionurl(Decorator::field('url'), - $reqvars, - Decorator::field('urlvars', []) - ), - 'branch' => Decorator::branchurl(Decorator::field('url'), - $reqvars, - Decorator::field('urlvars'), - ['action' => 'tree'] - ), - ]; - - return $misc->printTree($items, $attrs, 'schema', false); - -} diff --git a/src/tree/sequences.php b/src/tree/sequences.php deleted file mode 100644 index 23a067ac..00000000 --- a/src/tree/sequences.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; - -/** - * Manage sequences in a database - * - * $Id: sequences.php,v 1.49 2007/12/15 22:21:54 ioguix Exp $ - */ - -/** - * Generate XML for the browser tree. - */ -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $sequences = $data->getSequences(); - - $reqvars = $misc->getRequestVars('sequence'); - - $attrs = [ - 'text' => Decorator::field('seqname'), - 'icon' => 'Sequence', - 'toolTip' => Decorator::field('seqcomment'), - 'action' => Decorator::actionurl('sequences.php', - $reqvars, - [ - 'action' => 'properties', - 'sequence' => Decorator::field('seqname'), - ] - ), - ]; - - return $misc->printTree($sequences, $attrs, 'sequences', false); -} diff --git a/src/tree/servers.php b/src/tree/servers.php deleted file mode 100644 index a99956a1..00000000 --- a/src/tree/servers.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - - $nodes = []; - $group_id = isset($_GET['group']) ? $_GET['group'] : false; - - /* root with srv_groups */ - if (isset($conf['srv_groups']) and count($conf['srv_groups']) > 0 - and $group_id === false) { - $nodes = $misc->getServersGroups(true); - } else if (isset($conf['srv_groups']) and $group_id !== false) { - /* group subtree */ - if ($group_id !== 'all') { - $nodes = $misc->getServersGroups(false, $group_id); - } - - $nodes = array_merge($nodes, $misc->getServers(false, $group_id)); - $nodes = new \PHPPgAdmin\ArrayRecordSet($nodes); - } else { - /* no srv_group */ - $nodes = $misc->getServers(true, false); - } - - $reqvars = $misc->getRequestVars('server'); - - $attrs = [ - 'text' => Decorator::field('desc'), - - // Show different icons for logged in/out - 'icon' => Decorator::field('icon'), - - 'toolTip' => Decorator::field('id'), - - 'action' => Decorator::field('action'), - - // Only create a branch url if the user has - // logged into the server. - 'branch' => Decorator::field('branch'), - ]; - PC::debug($nodes, 'printTree'); - return $misc->printTree($nodes, $attrs, 'servers', false); - -} diff --git a/src/tree/tables.php b/src/tree/tables.php deleted file mode 100644 index 546fa96d..00000000 --- a/src/tree/tables.php +++ /dev/null @@ -1,78 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; -/** - * List tables in a database - * - * $Id: tables.php,v 1.112 2008/06/16 22:38:46 ioguix Exp $ - */ -/** - * Generate XML for the browser tree. - */ -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - \PC::debug($misc->getDatabase(), 'getDatabase'); - - $tables = $data->getTables(); - - $reqvars = $misc->getRequestVars('table'); - - $attrs = [ - 'text' => Decorator::field('relname'), - 'icon' => 'Table', - 'iconAction' => Decorator::url('display.php', - $reqvars, - ['table' => Decorator::field('relname')] - ), - 'toolTip' => Decorator::field('relcomment'), - 'action' => Decorator::redirecturl('redirect.php', - $reqvars, - ['table' => Decorator::field('relname')] - ), - 'branch' => Decorator::branchurl('tables.php', - $reqvars, - [ - 'action' => 'subtree', - 'table' => Decorator::field('relname'), - ] - ), - ]; - - return $misc->printTree($tables, $attrs, 'tables', false); -} - -function doSubTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $tabs = $misc->getNavTabs('table'); - $items = $misc->adjustTabsForTree($tabs); - $reqvars = $misc->getRequestVars('table'); - - $attrs = [ - 'text' => Decorator::field('title'), - 'icon' => Decorator::field('icon'), - 'action' => Decorator::actionurl( - Decorator::field('url'), - $reqvars, - Decorator::field('urlvars'), - ['table' => $_REQUEST['table']] - ), - 'branch' => Decorator::ifempty( - Decorator::field('branch'), '', Decorator::branchurl(Decorator::field('url'), $reqvars, [ - 'action' => 'tree', - 'table' => $_REQUEST['table'], - ] - ) - ), - ]; - - return $misc->printTree($items, $attrs, 'table', false); -} diff --git a/src/tree/tblproperties.php b/src/tree/tblproperties.php deleted file mode 100644 index f3f5d852..00000000 --- a/src/tree/tblproperties.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; - -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $columns = $data->getTableAttributes($_REQUEST['table']); - $reqvars = $misc->getRequestVars('column'); - - $attrs = [ - 'text' => Decorator::field('attname'), - 'action' => Decorator::actionurl('colproperties.php', - $reqvars, - [ - 'table' => $_REQUEST['table'], - 'column' => Decorator::field('attname'), - ] - ), - 'icon' => 'Column', - 'iconAction' => Decorator::url('display.php', - $reqvars, - [ - 'table' => $_REQUEST['table'], - 'column' => Decorator::field('attname'), - 'query' => replace( - 'SELECT "%column%", count(*) AS "count" FROM "%table%" GROUP BY "%column%" ORDER BY "%column%"', - [ - '%column%' => Decorator::field('attname'), - '%table%' => $_REQUEST['table'], - ] - ), - ] - ), - 'toolTip' => Decorator::field('comment'), - ]; - - return $misc->printTree($columns, $attrs, 'tblcolumns', false); -} diff --git a/src/tree/triggers.php b/src/tree/triggers.php deleted file mode 100644 index 775f44aa..00000000 --- a/src/tree/triggers.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; -/** - * List triggers on a table - * - * $Id: triggers.php,v 1.37 2007/09/19 14:42:12 ioguix Exp $ - */ - -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - $triggers = $data->getTriggers($_REQUEST['table']); - - $reqvars = $misc->getRequestVars('table'); - - $attrs = [ - 'text' => Decorator::field('tgname'), - 'icon' => 'Trigger', - ]; - - $misc->printTree($triggers, $attrs, 'triggers'); - exit; -} diff --git a/src/tree/types.php b/src/tree/types.php deleted file mode 100644 index f4235955..00000000 --- a/src/tree/types.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; -/** - * Manage types in a database - * - * $Id: types.php,v 1.42 2007/11/30 15:25:23 soranzo Exp $ - */ - -/** - * Generate XML for the browser tree. - */ -function doTree() { - global $misc, $data; - - $types = $data->getTypes(); - - $reqvars = $misc->getRequestVars('type'); - - $attrs = [ - 'text' => Decorator::field('typname'), - 'icon' => 'Type', - 'toolTip' => Decorator::field('typcomment'), - 'action' => Decorator::actionurl('types.php', - $reqvars, - [ - 'action' => 'properties', - 'type' => Decorator::field('basename'), - ] - ), - ]; - - $misc->printTree($types, $attrs, 'types'); - exit; -} diff --git a/src/tree/viewproperties.php b/src/tree/viewproperties.php deleted file mode 100755 index 3cb2acce..00000000 --- a/src/tree/viewproperties.php +++ /dev/null @@ -1,45 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; -/** - * List views in a database - * - * $Id: viewproperties.php,v 1.34 2007/12/11 14:17:17 ioguix Exp $ - */ - -function doTree() { - global $misc, $data; - - $reqvars = $misc->getRequestVars('column'); - $columns = $data->getTableAttributes($_REQUEST['view']); - - $attrs = [ - 'text' => Decorator::field('attname'), - 'action' => Decorator::actionurl('colproperties.php', - $reqvars, - [ - 'view' => $_REQUEST['view'], - 'column' => Decorator::field('attname'), - ] - ), - 'icon' => 'Column', - 'iconAction' => Decorator::url('display.php', - $reqvars, - [ - 'view' => $_REQUEST['view'], - 'column' => Decorator::field('attname'), - 'query' => replace( - 'SELECT "%column%", count(*) AS "count" FROM %view% GROUP BY "%column%" ORDER BY "%column%"', - [ - '%column%' => Decorator::field('attname'), - '%view%' => $_REQUEST['view'], - ] - ), - ] - ), - 'toolTip' => Decorator::field('comment'), - ]; - - $misc->printTree($columns, $attrs, 'viewcolumns'); - - exit; -} diff --git a/src/tree/views.php b/src/tree/views.php deleted file mode 100644 index 08e7caec..00000000 --- a/src/tree/views.php +++ /dev/null @@ -1,65 +0,0 @@ -<?php -use \PHPPgAdmin\Decorators\Decorator; -/** - * Manage views in a database - * - * $Id: views.php,v 1.75 2007/12/15 22:57:43 ioguix Exp $ - */ - -/** - * Generate XML for the browser tree. - */ -function doTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $views = $data->getViews(); - - $reqvars = $misc->getRequestVars('view'); - - $attrs = [ - 'text' => Decorator::field('relname'), - 'icon' => 'View', - 'iconAction' => Decorator::url('display.php', $reqvars, ['view' => Decorator::field('relname')]), - 'toolTip' => Decorator::field('relcomment'), - 'action' => Decorator::redirecturl('redirect.php', $reqvars, ['view' => Decorator::field('relname')]), - 'branch' => Decorator::branchurl('views/subtree', $reqvars, - [ - 'view' => Decorator::field('relname'), - ] - ), - ]; - - return $misc->printTree($views, $attrs, 'views', false); -} - -function doSubTree($container) { - - $conf = $container->get('conf'); - $misc = $container->get('misc'); - $lang = $container->get('lang'); - $data = $misc->getDatabaseAccessor(); - - $tabs = $misc->getNavTabs('view'); - $items = $misc->adjustTabsForTree($tabs); - $reqvars = $misc->getRequestVars('view'); - - $attrs = [ - 'text' => Decorator::field('title'), - 'icon' => Decorator::field('icon'), - 'action' => Decorator::actionurl(Decorator::field('url'), $reqvars, Decorator::field('urlvars'), ['view' => $_REQUEST['view']]), - 'branch' => Decorator::ifempty( - Decorator::field('branch'), '', Decorator::branchurl(Decorator::field('url'), Decorator::field('urlvars'), $reqvars, - [ - 'action' => 'tree', - 'view' => $_REQUEST['view'], - ] - ) - ), - ]; - - return $misc->printTree($items, $attrs, 'view', false); -} diff --git a/src/views/aggregates.php b/src/views/aggregates.php index f6c51c9f..aee82b44 100644 --- a/src/views/aggregates.php +++ b/src/views/aggregates.php @@ -9,51 +9,5 @@ // Include application functions require_once '../lib.inc.php'; -$misc->printHeader($lang['straggregates']); -$misc->printBody(); - $aggregate_controller = new \PHPPgAdmin\Controller\AggregateController($container); - -switch ($action) { - case 'create': - $aggregate_controller->doCreate(); - break; - case 'save_create': - if (isset($_POST['cancel'])) { - $aggregate_controller->doDefault(); - } else { - $aggregate_controller->doSaveCreate(); - } - - break; - case 'alter': - $aggregate_controller->doAlter(); - break; - case 'save_alter': - if (isset($_POST['alter'])) { - $aggregate_controller->doSaveAlter(); - } else { - $aggregate_controller->doProperties(); - } - - break; - case 'drop': - if (isset($_POST['drop'])) { - $aggregate_controller->doDrop(false); - } else { - $aggregate_controller->doDefault(); - } - - break; - case 'confirm_drop': - $aggregate_controller->doDrop(true); - break; - default: - $aggregate_controller->doDefault(); - break; - case 'properties': - $aggregate_controller->doProperties(); - break; -} - -$misc->printFooter(); +$aggregate_controller->render(); diff --git a/src/views/ajax-ac-insert.php b/src/views/ajax-ac-insert.php index 6367c760..a0436500 100644 --- a/src/views/ajax-ac-insert.php +++ b/src/views/ajax-ac-insert.php @@ -1,94 +1,7 @@ <?php -require_once '../lib.inc.php'; - -if (isset($_POST['offset'])) { - $offset = " OFFSET {$_POST['offset']}"; -} else { - $_POST['offset'] = 0; - $offset = " OFFSET 0"; -} - -$keynames = []; -foreach ($_POST['fkeynames'] as $k => $v) { - $fkeynames[$k] = html_entity_decode($v, ENT_QUOTES); -} - -$keyspos = array_combine($fkeynames, $_POST['keys']); - -$f_schema = html_entity_decode($_POST['f_schema'], ENT_QUOTES); -$data->fieldClean($f_schema); -$f_table = html_entity_decode($_POST['f_table'], ENT_QUOTES); -$data->fieldClean($f_table); -$f_attname = $fkeynames[$_POST['fattpos'][0]]; -$data->fieldClean($f_attname); - -$q = "SELECT * - FROM \"{$f_schema}\".\"{$f_table}\" - WHERE \"{$f_attname}\"::text LIKE '{$_POST['fvalue']}%' - ORDER BY \"{$f_attname}\" LIMIT 12 {$offset};"; - -$res = $data->selectSet($q); - -if (!$res->EOF) { - echo "<table class=\"ac_values\">"; - echo '<tr>'; - foreach (array_keys($res->fields) as $h) { - echo '<th>'; - if (in_array($h, $fkeynames)) { - echo '<img src="' . $misc->icon('ForeignKey') . '" alt="[referenced key]" />'; - } - - echo htmlentities($h, ENT_QUOTES, 'UTF-8'), '</th>'; - - } - echo "</tr>\n"; - $i = 0; - while ((!$res->EOF) && ($i < 11)) { - $j = 0; - echo "<tr class=\"acline\">"; - foreach ($res->fields as $n => $v) { - $finfo = $res->fetchField($j++); - if (in_array($n, $fkeynames)) { - echo "<td><a href=\"javascript:void(0)\" class=\"fkval\" name=\"{$keyspos[$n]}\">", - $misc->printVal($v, $finfo->type, ['clip' => 'collapsed']), - "</a></td>"; - } else { - echo "<td><a href=\"javascript:void(0)\">", - $misc->printVal($v, $finfo->type, ['clip' => 'collapsed']), - "</a></td>"; - } - - } - echo "</tr>\n"; - $i++; - $res->moveNext(); - } - echo "</table>\n"; - - $page_tests = ''; - - $js = "<script type=\"text/javascript\">\n"; - - if ($_POST['offset']) { - echo "<a href=\"javascript:void(0)\" id=\"fkprev\"><< Prev</a>"; - $js .= "fkl_hasprev=true;\n"; - } else { - $js .= "fkl_hasprev=false;\n"; - } - - if ($res->recordCount() == 12) { - $js .= "fkl_hasnext=true;\n"; - echo " <a href=\"javascript:void(0)\" id=\"fknext\">Next >></a>"; - } else { - $js .= "fkl_hasnext=false;\n"; - } +require_once '../lib.inc.php'; - echo $js . "</script>"; -} else { - printf("<p>{$lang['strnofkref']}</p>", "\"{$_POST['f_schema']}\".\"{$_POST['f_table']}\".\"{$fkeynames[$_POST['fattpos']]}\""); +$acinsert_controller = new \PHPPgAdmin\Controller\ACInsertController($container); - if ($_POST['offset']) { - echo "<a href=\"javascript:void(0)\" class=\"fkprev\">Prev <<</a>"; - } -} +$acinsert_controller->render(); diff --git a/src/views/all_db.php b/src/views/all_db.php index 1e75659b..6ccb3ade 100644 --- a/src/views/all_db.php +++ b/src/views/all_db.php @@ -13,50 +13,4 @@ if (!defined('BASE_PATH')) { $all_db_controller = new \PHPPgAdmin\Controller\AllDBController($container); -$misc->printHeader($lang['strdatabases']); -$misc->printBody(); - -switch ($action) { - case 'export': - $all_db_controller->doExport(); - break; - case 'save_create': - if (isset($_POST['cancel'])) { - $all_db_controller->doDefault(); - } else { - $all_db_controller->doSaveCreate(); - } - - break; - case 'create': - $all_db_controller->doCreate(); - break; - case 'drop': - if (isset($_REQUEST['drop'])) { - $all_db_controller->doDrop(false); - } else { - $all_db_controller->doDefault(); - } - - break; - case 'confirm_drop': - doDrop(true); - break; - case 'alter': - if (isset($_POST['oldname']) && isset($_POST['newname']) && !isset($_POST['cancel'])) { - $all_db_controller->doAlter(false); - } else { - $all_db_controller->doDefault(); - } - - break; - case 'confirm_alter': - $all_db_controller->doAlter(true); - break; - default: - $all_db_controller->doDefault(); - - break; -} - -$misc->printFooter(); +$all_db_controller->render(); diff --git a/src/views/browser.php b/src/views/browser.php new file mode 100644 index 00000000..142e9a9f --- /dev/null +++ b/src/views/browser.php @@ -0,0 +1,9 @@ +<?php + +if (!defined('BASE_PATH')) { + require_once '../lib.inc.php'; +} + +$browser_controller = new \PHPPgAdmin\Controller\BrowserController($container); + +$browser_controller->render(); diff --git a/src/views/casts.php b/src/views/casts.php index b46b7d56..201c0332 100644 --- a/src/views/casts.php +++ b/src/views/casts.php @@ -11,14 +11,4 @@ require_once '../lib.inc.php'; $cast_controller = new \PHPPgAdmin\Controller\CastController($container); -$misc->printHeader($lang['strcasts']); -$misc->printBody(); - -switch ($action) { - - default: - $cast_controller->doDefault(); - break; -} - -$misc->printFooter(); +$cast_controller->render();
\ No newline at end of file diff --git a/src/views/colproperties.php b/src/views/colproperties.php index 1b504422..c45fd01c 100644 --- a/src/views/colproperties.php +++ b/src/views/colproperties.php @@ -9,35 +9,6 @@ // Include application functions require_once '../lib.inc.php'; -if (isset($_REQUEST['table'])) { - $tableName = &$_REQUEST['table']; -} elseif (isset($_REQUEST['view'])) { - $tableName = &$_REQUEST['view']; -} else { - die($lang['strnotableprovided']); -} - $colproperty_controller = new \PHPPgAdmin\Controller\ColPropertyController($container); -$misc->printHeader($lang['strtables'] . ' - ' . $tableName); -$misc->printBody(); - -if (isset($_REQUEST['view'])) { - $colproperty_controller->doDefault(null, false); -} else { - switch ($action) { - case 'properties': - if (isset($_POST['cancel'])) { - $colproperty_controller->doDefault(); - } else { - $colproperty_controller->doAlter(); - } - - break; - default: - $colproperty_controller->doDefault(); - break; - } -} - -$misc->printFooter(); +$colproperty_controller->render(); diff --git a/src/views/constraints.php b/src/views/constraints.php index 53ba58d8..0bcd06ba 100644 --- a/src/views/constraints.php +++ b/src/views/constraints.php @@ -10,83 +10,4 @@ require_once '../lib.inc.php'; $constraint_controller = new \PHPPgAdmin\Controller\ConstraintController($container); - -$misc->printHeader($lang['strtables'] . ' - ' . $_REQUEST['table'] . ' - ' . $lang['strconstraints'], - "<script src=\"/js/indexes.js\" type=\"text/javascript\"></script>"); - -if ($action == 'add_unique_key' || $action == 'save_add_unique_key' - || $action == 'add_primary_key' || $action == 'save_add_primary_key' - || $action == 'add_foreign_key' || $action == 'save_add_foreign_key') { - echo "<body onload=\"init();\">"; -} else { - $misc->printBody(); -} - -switch ($action) { - case 'add_foreign_key': - $constraint_controller->addForeignKey(1); - break; - case 'save_add_foreign_key': - if (isset($_POST['cancel'])) { - $constraint_controller->doDefault(); - } else { - $constraint_controller->addForeignKey($_REQUEST['stage']); - } - - break; - case 'add_unique_key': - $constraint_controller->addPrimaryOrUniqueKey('unique', true); - break; - case 'save_add_unique_key': - if (isset($_POST['cancel'])) { - $constraint_controller->doDefault(); - } else { - $constraint_controller->addPrimaryOrUniqueKey('unique', false); - } - - break; - case 'add_primary_key': - $constraint_controller->addPrimaryOrUniqueKey('primary', true); - break; - case 'save_add_primary_key': - if (isset($_POST['cancel'])) { - $constraint_controller->doDefault(); - } else { - $constraint_controller->addPrimaryOrUniqueKey('primary', false); - } - - break; - case 'add_check': - $constraint_controller->addCheck(true); - break; - case 'save_add_check': - if (isset($_POST['cancel'])) { - $constraint_controller->doDefault(); - } else { - $constraint_controller->addCheck(false); - } - - break; - case 'save_create': - $constraint_controller->doSaveCreate(); - break; - case 'create': - $constraint_controller->doCreate(); - break; - case 'drop': - if (isset($_POST['drop'])) { - $constraint_controller->doDrop(false); - } else { - $constraint_controller->doDefault(); - } - - break; - case 'confirm_drop': - $constraint_controller->doDrop(true); - break; - default: - $constraint_controller->doDefault(); - break; -} - -$misc->printFooter(); +$constraint_controller->render(); diff --git a/src/views/conversions.php b/src/views/conversions.php index e96b73a1..aa3cf70c 100644 --- a/src/views/conversions.php +++ b/src/views/conversions.php @@ -10,14 +10,4 @@ require_once '../lib.inc.php'; $conversion_controller = new \PHPPgAdmin\Controller\ConversionController($container); - -$misc->printHeader($lang['strconversions']); -$misc->printBody(); - -switch ($action) { - default: - $conversion_controller->doDefault(); - break; -} - -$misc->printFooter(); +$conversion_controller->render(); diff --git a/src/views/database.php b/src/views/database.php index 05e0dbc7..021777f8 100755 --- a/src/views/database.php +++ b/src/views/database.php @@ -11,70 +11,4 @@ require_once '../lib.inc.php'; $database_controller = new \PHPPgAdmin\Controller\DatabaseController($container); -if ($action == 'refresh_locks') { - $database_controller->currentLocks(true); -} - -if ($action == 'refresh_processes') { - $database_controller->currentProcesses(true); -} -$scripts = ''; -/* normal flow */ -if ($action == 'locks' or $action == 'processes') { - $scripts .= "<script src=\"/js/database.js\" type=\"text/javascript\"></script>"; - - $refreshTime = $conf['ajax_refresh'] * 1000; - - $scripts .= "<script type=\"text/javascript\">\n"; - $scripts .= "var Database = {\n"; - $scripts .= "ajax_time_refresh: {$refreshTime},\n"; - $scripts .= "str_start: {text:'{$lang['strstart']}',icon: '" . $misc->icon('Execute') . "'},\n"; - $scripts .= "str_stop: {text:'{$lang['strstop']}',icon: '" . $misc->icon('Stop') . "'},\n"; - $scripts .= "load_icon: '" . $misc->icon('Loading') . "',\n"; - $scripts .= "server:'{$_REQUEST['server']}',\n"; - $scripts .= "dbname:'{$_REQUEST['database']}',\n"; - $scripts .= "action:'refresh_{$action}',\n"; - $scripts .= "errmsg: '" . str_replace("'", "\'", $lang['strconnectionfail']) . "'\n"; - $scripts .= "};\n"; - $scripts .= "</script>\n"; -} - -$misc->printHeader($lang['strdatabase'], $scripts); -$misc->printBody(); - -switch ($action) { - case 'find': - if (isset($_REQUEST['term'])) { - $database_controller->doFind(false); - } else { - $database_controller->doFind(true); - } - - break; - case 'sql': - $database_controller->doSQL(); - break; - case 'variables': - $database_controller->doVariables(); - break; - case 'processes': - $database_controller->doProcesses(); - break; - case 'locks': - $database_controller->doLocks(); - break; - case 'export': - $database_controller->doExport(); - break; - case 'signal': - $database_controller->doSignal(); - break; - default: - if (adminActions($action, 'database') === false) { - $database_controller->doSQL(); - } - - break; -} - -$misc->printFooter(); +$database_controller->render(); diff --git a/src/views/dataexport.php b/src/views/dataexport.php index 0cb2aab2..03504c91 100644 --- a/src/views/dataexport.php +++ b/src/views/dataexport.php @@ -7,369 +7,8 @@ * $Id: dataexport.php,v 1.26 2007/07/12 19:26:22 xzilla Exp $ */ -$extensions = [ - 'sql' => 'sql', - 'copy' => 'sql', - 'csv' => 'csv', - 'tab' => 'txt', - 'html' => 'html', - 'xml' => 'xml', -]; +require_once '../lib.inc.php'; -// Prevent timeouts on large exports (non-safe mode only) -if (!ini_get('safe_mode')) { - set_time_limit(0); -} +$dataexport_controller = new \PHPPgAdmin\Controller\DataExportController($container); -// if (!isset($_REQUEST['table']) && !isset($_REQUEST['query'])) -// What must we do in this case? Maybe redirect to the homepage? - -// If format is set, then perform the export -if (isset($_REQUEST['what'])) { - - // Include application functions - $misc->setNoOutput(true); - require_once '../lib.inc.php'; - - switch ($_REQUEST['what']) { - case 'dataonly': - // Check to see if they have pg_dump set up and if they do, use that - // instead of custom dump code - if ($misc->isDumpEnabled() - && ($_REQUEST['d_format'] == 'copy' || $_REQUEST['d_format'] == 'sql')) { - include './dbexport.php'; - exit; - } else { - $format = $_REQUEST['d_format']; - $oids = isset($_REQUEST['d_oids']); - } - break; - case 'structureonly': - // Check to see if they have pg_dump set up and if they do, use that - // instead of custom dump code - if ($misc->isDumpEnabled()) { - include './dbexport.php'; - exit; - } else { - $clean = isset($_REQUEST['s_clean']); - } - - break; - case 'structureanddata': - // Check to see if they have pg_dump set up and if they do, use that - // instead of custom dump code - if ($misc->isDumpEnabled()) { - include './dbexport.php'; - exit; - } else { - $format = $_REQUEST['sd_format']; - $clean = isset($_REQUEST['sd_clean']); - $oids = isset($_REQUEST['sd_oids']); - } - break; - } - - // Make it do a download, if necessary - if ($_REQUEST['output'] == 'download') { - // Set headers. MSIE is totally broken for SSL downloading, so - // we need to have it download in-place as plain text - if (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE') && isset($_SERVER['HTTPS'])) { - header('Content-Type: text/plain'); - } else { - header('Content-Type: application/download'); - - if (isset($extensions[$format])) { - $ext = $extensions[$format]; - } else { - $ext = 'txt'; - } - - header('Content-Disposition: attachment; filename=dump.' . $ext); - } - } else { - header('Content-Type: text/plain'); - } - - if (isset($_REQUEST['query'])) { - $_REQUEST['query'] = trim(urldecode($_REQUEST['query'])); - } - - // Set the schema search path - if (isset($_REQUEST['search_path'])) { - $data->setSearchPath(array_map('trim', explode(',', $_REQUEST['search_path']))); - } - - // Set up the dump transaction - $status = $data->beginDump(); - - // If the dump is not dataonly then dump the structure prefix - if ($_REQUEST['what'] != 'dataonly') { - echo $data->getTableDefPrefix($_REQUEST['table'], $clean); - } - - // If the dump is not structureonly then dump the actual data - if ($_REQUEST['what'] != 'structureonly') { - // Get database encoding - $dbEncoding = $data->getDatabaseEncoding(); - - // Set fetch mode to NUM so that duplicate field names are properly returned - $data->conn->setFetchMode(ADODB_FETCH_NUM); - - // Execute the query, if set, otherwise grab all rows from the table - if (isset($_REQUEST['table'])) { - $rs = $data->dumpRelation($_REQUEST['table'], $oids); - } else { - $rs = $data->conn->Execute($_REQUEST['query']); - } - - if ($format == 'copy') { - $data->fieldClean($_REQUEST['table']); - echo "COPY \"{$_REQUEST['table']}\""; - if ($oids) { - echo " WITH OIDS"; - } - - echo " FROM stdin;\n"; - while (!$rs->EOF) { - $first = true; - while (list($k, $v) = each($rs->fields)) { - // Escape value - $v = $data->escapeBytea($v); - - // We add an extra escaping slash onto octal encoded characters - $v = preg_replace('/\\\\([0-7]{3})/', '\\\\\1', $v); - if ($first) { - echo (is_null($v)) ? '\\N' : $v; - $first = false; - } else { - echo "\t", (is_null($v)) ? '\\N' : $v; - } - - } - echo "\n"; - $rs->moveNext(); - } - echo "\\.\n"; - } elseif ($format == 'html') { - echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n"; - echo "<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n"; - echo "<head>\r\n"; - echo "\t<title></title>\r\n"; - echo "\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\r\n"; - echo "</head>\r\n"; - echo "<body>\r\n"; - echo "<table class=\"phppgadmin\">\r\n"; - echo "\t<tr>\r\n"; - if (!$rs->EOF) { - // Output header row - $j = 0; - foreach ($rs->fields as $k => $v) { - $finfo = $rs->fetchField($j++); - if ($finfo->name == $data->id && !$oids) { - continue; - } - - echo "\t\t<th>", $misc->printVal($finfo->name, true), "</th>\r\n"; - } - } - echo "\t</tr>\r\n"; - while (!$rs->EOF) { - echo "\t<tr>\r\n"; - $j = 0; - foreach ($rs->fields as $k => $v) { - $finfo = $rs->fetchField($j++); - if ($finfo->name == $data->id && !$oids) { - continue; - } - - echo "\t\t<td>", $misc->printVal($v, true, $finfo->type), "</td>\r\n"; - } - echo "\t</tr>\r\n"; - $rs->moveNext(); - } - echo "</table>\r\n"; - echo "</body>\r\n"; - echo "</html>\r\n"; - } elseif ($format == 'xml') { - echo "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"; - echo "<data>\n"; - if (!$rs->EOF) { - // Output header row - $j = 0; - echo "\t<header>\n"; - foreach ($rs->fields as $k => $v) { - $finfo = $rs->fetchField($j++); - $name = htmlspecialchars($finfo->name); - $type = htmlspecialchars($finfo->type); - echo "\t\t<column name=\"{$name}\" type=\"{$type}\" />\n"; - } - echo "\t</header>\n"; - } - echo "\t<records>\n"; - while (!$rs->EOF) { - $j = 0; - echo "\t\t<row>\n"; - foreach ($rs->fields as $k => $v) { - $finfo = $rs->fetchField($j++); - $name = htmlspecialchars($finfo->name); - if (!is_null($v)) { - $v = htmlspecialchars($v); - } - - echo "\t\t\t<column name=\"{$name}\"", (is_null($v) ? ' null="null"' : ''), ">{$v}</column>\n"; - } - echo "\t\t</row>\n"; - $rs->moveNext(); - } - echo "\t</records>\n"; - echo "</data>\n"; - } elseif ($format == 'sql') { - $data->fieldClean($_REQUEST['table']); - while (!$rs->EOF) { - echo "INSERT INTO \"{$_REQUEST['table']}\" ("; - $first = true; - $j = 0; - foreach ($rs->fields as $k => $v) { - $finfo = $rs->fetchField($j++); - $k = $finfo->name; - // SQL (INSERT) format cannot handle oids - // if ($k == $data->id) continue; - // Output field - $data->fieldClean($k); - if ($first) { - echo "\"{$k}\""; - } else { - echo ", \"{$k}\""; - } - - if (!is_null($v)) { - // Output value - // addCSlashes converts all weird ASCII characters to octal representation, - // EXCEPT the 'special' ones like \r \n \t, etc. - $v = addCSlashes($v, "\0..\37\177..\377"); - // We add an extra escaping slash onto octal encoded characters - $v = preg_replace('/\\\\([0-7]{3})/', '\\\1', $v); - // Finally, escape all apostrophes - $v = str_replace("'", "''", $v); - } - if ($first) { - $values = (is_null($v) ? 'NULL' : "'{$v}'"); - $first = false; - } else { - $values .= ', ' . ((is_null($v) ? 'NULL' : "'{$v}'")); - } - - } - echo ") VALUES ({$values});\n"; - $rs->moveNext(); - } - } else { - switch ($format) { - case 'tab': - $sep = "\t"; - break; - case 'csv': - default: - $sep = ','; - break; - } - if (!$rs->EOF) { - // Output header row - $first = true; - foreach ($rs->fields as $k => $v) { - $finfo = $rs->fetchField($k); - $v = $finfo->name; - if (!is_null($v)) { - $v = str_replace('"', '""', $v); - } - - if ($first) { - echo "\"{$v}\""; - $first = false; - } else { - echo "{$sep}\"{$v}\""; - } - - } - echo "\r\n"; - } - while (!$rs->EOF) { - $first = true; - foreach ($rs->fields as $k => $v) { - if (!is_null($v)) { - $v = str_replace('"', '""', $v); - } - - if ($first) { - echo (is_null($v)) ? "\"\\N\"" : "\"{$v}\""; - $first = false; - } else { - echo is_null($v) ? "{$sep}\"\\N\"" : "{$sep}\"{$v}\""; - } - - } - echo "\r\n"; - $rs->moveNext(); - } - } - } - - // If the dump is not dataonly then dump the structure suffix - if ($_REQUEST['what'] != 'dataonly') { - // Set fetch mode back to ASSOC for the table suffix to work - $data->conn->setFetchMode(ADODB_FETCH_ASSOC); - echo $data->getTableDefSuffix($_REQUEST['table']); - } - - // Finish the dump transaction - $status = $data->endDump(); -} else { - // Include application functions - require_once '../lib.inc.php'; - - if (!isset($_REQUEST['query']) or empty($_REQUEST['query'])) { - $_REQUEST['query'] = $_SESSION['sqlquery']; - } - - $misc->printHeader($lang['strexport']); - $misc->printBody(); - $misc->printTrail(isset($_REQUEST['subject']) ? $_REQUEST['subject'] : 'database'); - $misc->printTitle($lang['strexport']); - if (isset($msg)) { - $misc->printMsg($msg); - } - - echo "<form action=\"/src/views/dataexport.php\" method=\"post\">\n"; - echo "<table>\n"; - echo "<tr><th class=\"data\">{$lang['strformat']}:</th><td><select name=\"d_format\">\n"; - // COPY and SQL require a table - if (isset($_REQUEST['table'])) { - echo "<option value=\"copy\">COPY</option>\n"; - echo "<option value=\"sql\">SQL</option>\n"; - } - echo "<option value=\"csv\">CSV</option>\n"; - echo "<option value=\"tab\">{$lang['strtabbed']}</option>\n"; - echo "<option value=\"html\">XHTML</option>\n"; - echo "<option value=\"xml\">XML</option>\n"; - echo "</select></td></tr>"; - echo "</table>\n"; - - echo "<h3>{$lang['stroptions']}</h3>\n"; - echo "<p><input type=\"radio\" id=\"output1\" name=\"output\" value=\"show\" checked=\"checked\" /><label for=\"output1\">{$lang['strshow']}</label>\n"; - echo "<br/><input type=\"radio\" id=\"output2\" name=\"output\" value=\"download\" /><label for=\"output2\">{$lang['strdownload']}</label></p>\n"; - - echo "<p><input type=\"hidden\" name=\"action\" value=\"export\" />\n"; - echo "<input type=\"hidden\" name=\"what\" value=\"dataonly\" />\n"; - if (isset($_REQUEST['table'])) { - echo "<input type=\"hidden\" name=\"table\" value=\"", htmlspecialchars($_REQUEST['table']), "\" />\n"; - } - echo "<input type=\"hidden\" name=\"query\" value=\"", htmlspecialchars(urlencode($_REQUEST['query'])), "\" />\n"; - if (isset($_REQUEST['search_path'])) { - echo "<input type=\"hidden\" name=\"search_path\" value=\"", htmlspecialchars($_REQUEST['search_path']), "\" />\n"; - } - echo $misc->form; - echo "<input type=\"submit\" value=\"{$lang['strexport']}\" /></p>\n"; - echo "</form>\n"; - - $misc->printFooter(); -} +$dataexport_controller->render();
\ No newline at end of file diff --git a/src/views/dataimport.php b/src/views/dataimport.php index 3ac2207c..ecf4aecd 100644 --- a/src/views/dataimport.php +++ b/src/views/dataimport.php @@ -6,300 +6,8 @@ * $Id: dataimport.php,v 1.11 2007/01/22 16:33:01 soranzo Exp $ */ -// Prevent timeouts on large exports (non-safe mode only) -if (!ini_get('safe_mode')) { - set_time_limit(0); -} - -// Include application functions require_once '../lib.inc.php'; -// Default state for XML parser -$state = 'XML'; -$curr_col_name = null; -$curr_col_val = null; -$curr_col_null = false; -$curr_row = []; - -/** - * Open tag handler for XML import feature - */ -function _startElement($parser, $name, $attrs) { - global $data, $misc, $lang; - global $state, $curr_row, $curr_col_name, $curr_col_val, $curr_col_null; - - switch ($name) { - case 'DATA': - if ($state != 'XML') { - $data->rollbackTransaction(); - $misc->printMsg($lang['strimporterror']); - exit; - } - $state = 'DATA'; - break; - case 'HEADER': - if ($state != 'DATA') { - $data->rollbackTransaction(); - $misc->printMsg($lang['strimporterror']); - exit; - } - $state = 'HEADER'; - break; - case 'RECORDS': - if ($state != 'READ_HEADER') { - $data->rollbackTransaction(); - $misc->printMsg($lang['strimporterror']); - exit; - } - $state = 'RECORDS'; - break; - case 'ROW': - if ($state != 'RECORDS') { - $data->rollbackTransaction(); - $misc->printMsg($lang['strimporterror']); - exit; - } - $state = 'ROW'; - $curr_row = []; - break; - case 'COLUMN': - // We handle columns in rows - if ($state == 'ROW') { - $state = 'COLUMN'; - $curr_col_name = $attrs['NAME']; - $curr_col_null = isset($attrs['NULL']); - } - // And we ignore columns in headers and fail in any other context - elseif ($state != 'HEADER') { - $data->rollbackTransaction(); - $misc->printMsg($lang['strimporterror']); - exit; - } - break; - default: - // An unrecognised tag means failure - $data->rollbackTransaction(); - $misc->printMsg($lang['strimporterror']); - exit; - } -} - -/** - * Close tag handler for XML import feature - */ -function _endElement($parser, $name) { - global $data, $misc, $lang; - global $state, $curr_row, $curr_col_name, $curr_col_val, $curr_col_null; - - switch ($name) { - case 'DATA': - $state = 'READ_DATA'; - break; - case 'HEADER': - $state = 'READ_HEADER'; - break; - case 'RECORDS': - $state = 'READ_RECORDS'; - break; - case 'ROW': - // Build value map in order to insert row into table - $fields = []; - $vars = []; - $nulls = []; - $format = []; - $types = []; - $i = 0; - foreach ($curr_row as $k => $v) { - $fields[$i] = $k; - // Check for nulls - if ($v === null) { - $nulls[$i] = 'on'; - } - - // Add to value array - $vars[$i] = $v; - // Format is always VALUE - $format[$i] = 'VALUE'; - // Type is always text - $types[$i] = 'text'; - $i++; - } - $status = $data->insertRow($_REQUEST['table'], $fields, $vars, $nulls, $format, $types); - if ($status != 0) { - $data->rollbackTransaction(); - $misc->printMsg($lang['strimporterror']); - exit; - } - $curr_row = []; - $state = 'RECORDS'; - break; - case 'COLUMN': - $curr_row[$curr_col_name] = ($curr_col_null ? null : $curr_col_val); - $curr_col_name = null; - $curr_col_val = null; - $curr_col_null = false; - $state = 'ROW'; - break; - default: - // An unrecognised tag means failure - $data->rollbackTransaction(); - $misc->printMsg($lang['strimporterror']); - exit; - } -} - -/** - * Character data handler for XML import feature - */ -function _charHandler($parser, $cdata) { - global $data, $misc, $lang; - global $state, $curr_col_val; - - if ($state == 'COLUMN') { - $curr_col_val .= $cdata; - } -} - -function loadNULLArray() { - $array = []; - if (isset($_POST['allowednulls'])) { - foreach ($_POST['allowednulls'] as $null_char) { - $array[] = $null_char; - } - - } - return $array; -} - -function determineNull($field, $null_array) { - return in_array($field, $null_array); -} - -$misc->printHeader($lang['strimport']); -$misc->printTrail('table'); -$misc->printTabs('table', 'import'); - -// Check that file is specified and is an uploaded file -if (isset($_FILES['source']) && is_uploaded_file($_FILES['source']['tmp_name']) && is_readable($_FILES['source']['tmp_name'])) { - - $fd = fopen($_FILES['source']['tmp_name'], 'r'); - // Check that file was opened successfully - if ($fd !== false) { - $null_array = loadNULLArray(); - $status = $data->beginTransaction(); - if ($status != 0) { - $misc->printMsg($lang['strimporterror']); - exit; - } - - // If format is set to 'auto', then determine format automatically from file name - if ($_REQUEST['format'] == 'auto') { - $extension = substr(strrchr($_FILES['source']['name'], '.'), 1); - switch ($extension) { - case 'csv': - $_REQUEST['format'] = 'csv'; - break; - case 'txt': - $_REQUEST['format'] = 'tab'; - break; - case 'xml': - $_REQUEST['format'] = 'xml'; - break; - default: - $data->rollbackTransaction(); - $misc->printMsg($lang['strimporterror-fileformat']); - exit; - } - } - - // Do different import technique depending on file format - switch ($_REQUEST['format']) { - case 'csv': - case 'tab': - // XXX: Length of CSV lines limited to 100k - $csv_max_line = 100000; - // Set delimiter to tabs or commas - if ($_REQUEST['format'] == 'csv') { - $csv_delimiter = ','; - } else { - $csv_delimiter = "\t"; - } - - // Get first line of field names - $fields = fgetcsv($fd, $csv_max_line, $csv_delimiter); - $row = 2; //We start on the line AFTER the field names - while ($line = fgetcsv($fd, $csv_max_line, $csv_delimiter)) { - // Build value map - $t_fields = []; - $vars = []; - $nulls = []; - $format = []; - $types = []; - $i = 0; - foreach ($fields as $f) { - // Check that there is a column - if (!isset($line[$i])) { - $misc->printMsg(sprintf($lang['strimporterrorline-badcolumnnum'], $row)); - exit; - } - $t_fields[$i] = $f; - - // Check for nulls - if (determineNull($line[$i], $null_array)) { - $nulls[$i] = 'on'; - } - // Add to value array - $vars[$i] = $line[$i]; - // Format is always VALUE - $format[$i] = 'VALUE'; - // Type is always text - $types[$i] = 'text'; - $i++; - } - - $status = $data->insertRow($_REQUEST['table'], $t_fields, $vars, $nulls, $format, $types); - if ($status != 0) { - $data->rollbackTransaction(); - $misc->printMsg(sprintf($lang['strimporterrorline'], $row)); - exit; - } - $row++; - } - break; - case 'xml': - $parser = xml_parser_create(); - xml_set_element_handler($parser, '_startElement', '_endElement'); - xml_set_character_data_handler($parser, '_charHandler'); - - while (!feof($fd)) { - $line = fgets($fd, 4096); - xml_parse($parser, $line); - } - - xml_parser_free($parser); - break; - default: - // Unknown type - $data->rollbackTransaction(); - $misc->printMsg($lang['strinvalidparam']); - exit; - } - - $status = $data->endTransaction(); - if ($status != 0) { - $misc->printMsg($lang['strimporterror']); - exit; - } - fclose($fd); - - $misc->printMsg($lang['strfileimported']); - } else { - // File could not be opened - $misc->printMsg($lang['strimporterror']); - } -} else { - // Upload went wrong - $misc->printMsg($lang['strimporterror-uploadedfile']); -} +$dataimport_controller = new \PHPPgAdmin\Controller\DataImportController($container); -$misc->printFooter(); +$dataimport_controller->render();
\ No newline at end of file diff --git a/src/views/dbexport.php b/src/views/dbexport.php index 9edd025c..c6c6e7ad 100644 --- a/src/views/dbexport.php +++ b/src/views/dbexport.php @@ -6,148 +6,8 @@ * $Id: dbexport.php,v 1.22 2007/03/25 03:15:09 xzilla Exp $ */ -// Prevent timeouts on large exports (non-safe mode only) -if (!ini_get('safe_mode')) { - set_time_limit(0); -} - -// Include application functions -$f_schema = $f_object = ''; require_once '../lib.inc.php'; -$misc->setNoOutput(true); - -// Are we doing a cluster-wide dump or just a per-database dump -$dumpall = ($_REQUEST['subject'] == 'server'); - -// Check that database dumps are enabled. -if ($misc->isDumpEnabled($dumpall)) { - - $server_info = $misc->getServerInfo(); - - // Get the path of the pg_dump/pg_dumpall executable - $exe = $misc->escapeShellCmd($server_info[$dumpall ? 'pg_dumpall_path' : 'pg_dump_path']); - - // Obtain the pg_dump version number and check if the path is good - $version = []; - preg_match("/(\d+(?:\.\d+)?)(?:\.\d+)?.*$/", exec($exe . " --version"), $version); - - if (empty($version)) { - if ($dumpall) { - printf($lang['strbadpgdumpallpath'], $server_info['pg_dumpall_path']); - } else { - printf($lang['strbadpgdumppath'], $server_info['pg_dump_path']); - } - - exit; - } - - // Make it do a download, if necessary - switch ($_REQUEST['output']) { - case 'show': - header('Content-Type: text/plain'); - break; - case 'download': - // Set headers. MSIE is totally broken for SSL downloading, so - // we need to have it download in-place as plain text - if (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE') && isset($_SERVER['HTTPS'])) { - header('Content-Type: text/plain'); - } else { - header('Content-Type: application/download'); - header('Content-Disposition: attachment; filename=dump.sql'); - } - break; - case 'gzipped': - // MSIE in SSL mode cannot do this - it should never get to this point - header('Content-Type: application/download'); - header('Content-Disposition: attachment; filename=dump.sql.gz'); - break; - } - - // Set environmental variables that pg_dump uses - putenv('PGPASSWORD=' . $server_info['password']); - putenv('PGUSER=' . $server_info['username']); - $hostname = $server_info['host']; - if ($hostname !== null && $hostname != '') { - putenv('PGHOST=' . $hostname); - } - $port = $server_info['port']; - if ($port !== null && $port != '') { - putenv('PGPORT=' . $port); - } - - // Build command for executing pg_dump. '-i' means ignore version differences. - $cmd = $exe . " -i"; - - // we are PG 7.4+, so we always have a schema - if (isset($_REQUEST['schema'])) { - $f_schema = $_REQUEST['schema']; - $data->fieldClean($f_schema); - } - - // Check for a specified table/view - switch ($_REQUEST['subject']) { - case 'schema': - // This currently works for 8.2+ (due to the orthoganl -t -n issue introduced then) - $cmd .= " -n " . $misc->escapeShellArg("\"{$f_schema}\""); - break; - case 'table': - case 'view': - $f_object = $_REQUEST[$_REQUEST['subject']]; - $data->fieldClean($f_object); - - // Starting in 8.2, -n and -t are orthagonal, so we now schema qualify - // the table name in the -t argument and quote both identifiers - if (((float) $version[1]) >= 8.2) { - $cmd .= " -t " . $misc->escapeShellArg("\"{$f_schema}\".\"{$f_object}\""); - } else { - // If we are 7.4 or higher, assume they are using 7.4 pg_dump and - // set dump schema as well. Also, mixed case dumping has been fixed - // then.. - $cmd .= " -t " . $misc->escapeShellArg($f_object) - . " -n " . $misc->escapeShellArg($f_schema); - } - } - - // Check for GZIP compression specified - if ($_REQUEST['output'] == 'gzipped' && !$dumpall) { - $cmd .= " -Z 9"; - } - - switch ($_REQUEST['what']) { - case 'dataonly': - $cmd .= ' -a'; - if ($_REQUEST['d_format'] == 'sql') { - $cmd .= ' --inserts'; - } elseif (isset($_REQUEST['d_oids'])) { - $cmd .= ' -o'; - } - - break; - case 'structureonly': - $cmd .= ' -s'; - if (isset($_REQUEST['s_clean'])) { - $cmd .= ' -c'; - } - - break; - case 'structureanddata': - if ($_REQUEST['sd_format'] == 'sql') { - $cmd .= ' --inserts'; - } elseif (isset($_REQUEST['sd_oids'])) { - $cmd .= ' -o'; - } - - if (isset($_REQUEST['sd_clean'])) { - $cmd .= ' -c'; - } - - break; - } - if (!$dumpall) { - putenv('PGDATABASE=' . $_REQUEST['database']); - } +$dbexport_controller = new \PHPPgAdmin\Controller\DBExportController($container); - // Execute command and return the output to the screen - passthru($cmd); -} +$dbexport_controller->render();
\ No newline at end of file diff --git a/src/views/display.php b/src/views/display.php index 3db53807..31ef6f10 100644 --- a/src/views/display.php +++ b/src/views/display.php @@ -11,969 +11,11 @@ * $Id: display.php,v 1.68 2008/04/14 12:44:27 ioguix Exp $ */ -// Prevent timeouts on large exports (non-safe mode only) -if (!ini_get('safe_mode')) { - set_time_limit(0); -} - // Include application functions if (!defined('BASE_PATH')) { require_once '../lib.inc.php'; } -global $conf, $lang; - -$action = (isset($_REQUEST['action'])) ? $_REQUEST['action'] : ''; - -/** - * Show confirmation of edit and perform actual update - */ -function doEditRow($confirm, $msg = '') { - global $data, $misc, $conf; - global $lang; - - if (is_array($_REQUEST['key'])) { - $key = $_REQUEST['key']; - } else { - $key = unserialize(urldecode($_REQUEST['key'])); - } - - if ($confirm) { - $misc->printTrail($_REQUEST['subject']); - $misc->printTitle($lang['streditrow']); - $misc->printMsg($msg); - - $attrs = $data->getTableAttributes($_REQUEST['table']); - $rs = $data->browseRow($_REQUEST['table'], $key); - - if (($conf['autocomplete'] != 'disable')) { - $fksprops = $misc->getAutocompleteFKProperties($_REQUEST['table']); - if ($fksprops !== false) { - echo $fksprops['code']; - } - - } else { - $fksprops = false; - } - - echo "<form action=\"/src/views/display.php\" method=\"post\" id=\"ac_form\">\n"; - $elements = 0; - $error = true; - if ($rs->recordCount() == 1 && $attrs->recordCount() > 0) { - echo "<table>\n"; - - // Output table header - echo "<tr><th class=\"data\">{$lang['strcolumn']}</th><th class=\"data\">{$lang['strtype']}</th>"; - echo "<th class=\"data\">{$lang['strformat']}</th>\n"; - echo "<th class=\"data\">{$lang['strnull']}</th><th class=\"data\">{$lang['strvalue']}</th></tr>"; - - $i = 0; - while (!$attrs->EOF) { - - $attrs->fields['attnotnull'] = $data->phpBool($attrs->fields['attnotnull']); - $id = (($i % 2) == 0 ? '1' : '2'); - - // Initialise variables - if (!isset($_REQUEST['format'][$attrs->fields['attname']])) { - $_REQUEST['format'][$attrs->fields['attname']] = 'VALUE'; - } - - echo "<tr class=\"data{$id}\">\n"; - echo "<td style=\"white-space:nowrap;\">", $misc->printVal($attrs->fields['attname']), "</td>"; - echo "<td style=\"white-space:nowrap;\">\n"; - echo $misc->printVal($data->formatType($attrs->fields['type'], $attrs->fields['atttypmod'])); - echo "<input type=\"hidden\" name=\"types[", htmlspecialchars($attrs->fields['attname']), "]\" value=\"", - htmlspecialchars($attrs->fields['type']), "\" /></td>"; - $elements++; - echo "<td style=\"white-space:nowrap;\">\n"; - echo "<select name=\"format[", htmlspecialchars($attrs->fields['attname']), "]\">\n"; - echo "<option value=\"VALUE\"", ($_REQUEST['format'][$attrs->fields['attname']] == 'VALUE') ? ' selected="selected"' : '', ">{$lang['strvalue']}</option>\n"; - echo "<option value=\"EXPRESSION\"", ($_REQUEST['format'][$attrs->fields['attname']] == 'EXPRESSION') ? ' selected="selected"' : '', ">{$lang['strexpression']}</option>\n"; - echo "</select>\n</td>\n"; - $elements++; - echo "<td style=\"white-space:nowrap;\">"; - // Output null box if the column allows nulls (doesn't look at CHECKs or ASSERTIONS) - if (!$attrs->fields['attnotnull']) { - // Set initial null values - if ($_REQUEST['action'] == 'confeditrow' && $rs->fields[$attrs->fields['attname']] === null) { - $_REQUEST['nulls'][$attrs->fields['attname']] = 'on'; - } - echo "<label><span><input type=\"checkbox\" name=\"nulls[{$attrs->fields['attname']}]\"", - isset($_REQUEST['nulls'][$attrs->fields['attname']]) ? ' checked="checked"' : '', " /></span></label></td>\n"; - $elements++; - } else { - echo " </td>"; - } - - echo "<td id=\"row_att_{$attrs->fields['attnum']}\" style=\"white-space:nowrap;\">"; - - $extras = []; - - // If the column allows nulls, then we put a JavaScript action on the data field to unset the - // NULL checkbox as soon as anything is entered in the field. We use the $elements variable to - // keep track of which element offset we're up to. We can't refer to the null checkbox by name - // as it contains '[' and ']' characters. - if (!$attrs->fields['attnotnull']) { - $extras['onChange'] = 'elements[' . ($elements - 1) . '].checked = false;'; - } - - if (($fksprops !== false) && isset($fksprops['byfield'][$attrs->fields['attnum']])) { - $extras['id'] = "attr_{$attrs->fields['attnum']}"; - $extras['autocomplete'] = 'off'; - } - - echo $data->printField("values[{$attrs->fields['attname']}]", $rs->fields[$attrs->fields['attname']], $attrs->fields['type'], $extras); - - echo "</td>"; - $elements++; - echo "</tr>\n"; - $i++; - $attrs->moveNext(); - } - echo "</table>\n"; - - $error = false; - } elseif ($rs->recordCount() != 1) { - echo "<p>{$lang['strrownotunique']}</p>\n"; - } else { - echo "<p>{$lang['strinvalidparam']}</p>\n"; - } - - echo "<input type=\"hidden\" name=\"action\" value=\"editrow\" />\n"; - echo $misc->form; - if (isset($_REQUEST['table'])) { - echo "<input type=\"hidden\" name=\"table\" value=\"", htmlspecialchars($_REQUEST['table']), "\" />\n"; - } - - if (isset($_REQUEST['subject'])) { - echo "<input type=\"hidden\" name=\"subject\" value=\"", htmlspecialchars($_REQUEST['subject']), "\" />\n"; - } - - if (isset($_REQUEST['query'])) { - echo "<input type=\"hidden\" name=\"query\" value=\"", htmlspecialchars($_REQUEST['query']), "\" />\n"; - } - - if (isset($_REQUEST['count'])) { - echo "<input type=\"hidden\" name=\"count\" value=\"", htmlspecialchars($_REQUEST['count']), "\" />\n"; - } - - if (isset($_REQUEST['return'])) { - echo "<input type=\"hidden\" name=\"return\" value=\"", htmlspecialchars($_REQUEST['return']), "\" />\n"; - } - - echo "<input type=\"hidden\" name=\"page\" value=\"", htmlspecialchars($_REQUEST['page']), "\" />\n"; - echo "<input type=\"hidden\" name=\"sortkey\" value=\"", htmlspecialchars($_REQUEST['sortkey']), "\" />\n"; - echo "<input type=\"hidden\" name=\"sortdir\" value=\"", htmlspecialchars($_REQUEST['sortdir']), "\" />\n"; - echo "<input type=\"hidden\" name=\"strings\" value=\"", htmlspecialchars($_REQUEST['strings']), "\" />\n"; - echo "<input type=\"hidden\" name=\"key\" value=\"", htmlspecialchars(urlencode(serialize($key))), "\" />\n"; - echo "<p>"; - if (!$error) { - echo "<input type=\"submit\" name=\"save\" accesskey=\"r\" value=\"{$lang['strsave']}\" />\n"; - } - - echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n"; - - if ($fksprops !== false) { - if ($conf['autocomplete'] != 'default off') { - echo "<input type=\"checkbox\" id=\"no_ac\" value=\"1\" checked=\"checked\" /><label for=\"no_ac\">{$lang['strac']}</label>\n"; - } else { - echo "<input type=\"checkbox\" id=\"no_ac\" value=\"0\" /><label for=\"no_ac\">{$lang['strac']}</label>\n"; - } - - } - - echo "</p>\n"; - echo "</form>\n"; - } else { - if (!isset($_POST['values'])) { - $_POST['values'] = []; - } - - if (!isset($_POST['nulls'])) { - $_POST['nulls'] = []; - } - - $status = $data->editRow($_POST['table'], $_POST['values'], $_POST['nulls'], - $_POST['format'], $_POST['types'], $key); - if ($status == 0) { - doBrowse($lang['strrowupdated']); - } elseif ($status == -2) { - doEditRow(true, $lang['strrownotunique']); - } else { - doEditRow(true, $lang['strrowupdatedbad']); - } - - } - -} - -/** - * Show confirmation of drop and perform actual drop - */ -function doDelRow($confirm) { - global $data, $misc; - global $lang; - - if ($confirm) { - $misc->printTrail($_REQUEST['subject']); - $misc->printTitle($lang['strdeleterow']); - - $rs = $data->browseRow($_REQUEST['table'], $_REQUEST['key']); - - echo "<form action=\"/src/views/display.php\" method=\"post\">\n"; - echo $misc->form; - - if ($rs->recordCount() == 1) { - echo "<p>{$lang['strconfdeleterow']}</p>\n"; - - $fkinfo = []; - echo "<table><tr>"; - printTableHeaderCells($rs, false, true); - echo "</tr>"; - echo "<tr class=\"data1\">\n"; - printTableRowCells($rs, $fkinfo, true); - echo "</tr>\n"; - echo "</table>\n"; - echo "<br />\n"; - - echo "<input type=\"hidden\" name=\"action\" value=\"delrow\" />\n"; - echo "<input type=\"submit\" name=\"yes\" value=\"{$lang['stryes']}\" />\n"; - echo "<input type=\"submit\" name=\"no\" value=\"{$lang['strno']}\" />\n"; - } elseif ($rs->recordCount() != 1) { - echo "<p>{$lang['strrownotunique']}</p>\n"; - echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n"; - } else { - echo "<p>{$lang['strinvalidparam']}</p>\n"; - echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n"; - } - if (isset($_REQUEST['table'])) { - echo "<input type=\"hidden\" name=\"table\" value=\"", htmlspecialchars($_REQUEST['table']), "\" />\n"; - } - - if (isset($_REQUEST['subject'])) { - echo "<input type=\"hidden\" name=\"subject\" value=\"", htmlspecialchars($_REQUEST['subject']), "\" />\n"; - } - - if (isset($_REQUEST['query'])) { - echo "<input type=\"hidden\" name=\"query\" value=\"", htmlspecialchars($_REQUEST['query']), "\" />\n"; - } - - if (isset($_REQUEST['count'])) { - echo "<input type=\"hidden\" name=\"count\" value=\"", htmlspecialchars($_REQUEST['count']), "\" />\n"; - } - - if (isset($_REQUEST['return'])) { - echo "<input type=\"hidden\" name=\"return\" value=\"", htmlspecialchars($_REQUEST['return']), "\" />\n"; - } - - echo "<input type=\"hidden\" name=\"page\" value=\"", htmlspecialchars($_REQUEST['page']), "\" />\n"; - echo "<input type=\"hidden\" name=\"sortkey\" value=\"", htmlspecialchars($_REQUEST['sortkey']), "\" />\n"; - echo "<input type=\"hidden\" name=\"sortdir\" value=\"", htmlspecialchars($_REQUEST['sortdir']), "\" />\n"; - echo "<input type=\"hidden\" name=\"strings\" value=\"", htmlspecialchars($_REQUEST['strings']), "\" />\n"; - echo "<input type=\"hidden\" name=\"key\" value=\"", htmlspecialchars(urlencode(serialize($_REQUEST['key']))), "\" />\n"; - echo "</form>\n"; - } else { - $status = $data->deleteRow($_POST['table'], unserialize(urldecode($_POST['key']))); - if ($status == 0) { - doBrowse($lang['strrowdeleted']); - } elseif ($status == -2) { - doBrowse($lang['strrownotunique']); - } else { - doBrowse($lang['strrowdeletedbad']); - } - - } - -} - -/* build & return the FK information data structure - * used when deciding if a field should have a FK link or not*/ -function &getFKInfo() { - global $data, $misc, $lang; - - // Get the foreign key(s) information from the current table - $fkey_information = ['byconstr' => [], 'byfield' => []]; - - if (isset($_REQUEST['table'])) { - $constraints = $data->getConstraintsWithFields($_REQUEST['table']); - if ($constraints->recordCount() > 0) { - - $fkey_information['common_url'] = $misc->getHREF('schema') . '&subject=table'; - - /* build the FK constraints data structure */ - while (!$constraints->EOF) { - $constr = &$constraints->fields; - if ($constr['contype'] == 'f') { - - if (!isset($fkey_information['byconstr'][$constr['conid']])) { - $fkey_information['byconstr'][$constr['conid']] = [ - 'url_data' => 'table=' . urlencode($constr['f_table']) . '&schema=' . urlencode($constr['f_schema']), - 'fkeys' => [], - 'consrc' => $constr['consrc'], - ]; - } - - $fkey_information['byconstr'][$constr['conid']]['fkeys'][$constr['p_field']] = $constr['f_field']; - - if (!isset($fkey_information['byfield'][$constr['p_field']])) { - $fkey_information['byfield'][$constr['p_field']] = []; - } - - $fkey_information['byfield'][$constr['p_field']][] = $constr['conid']; - } - $constraints->moveNext(); - } - } - } - - return $fkey_information; -} - -/* Print table header cells - * @param $args - associative array for sort link parameters - * */ -function printTableHeaderCells(&$rs, $args, $withOid) { - global $misc, $data, $conf; - $j = 0; - - foreach ($rs->fields as $k => $v) { - - if (($k === $data->id) && (!($withOid && $conf['show_oids']))) { - $j++; - continue; - } - $finfo = $rs->fetchField($j); - - if ($args === false) { - echo "<th class=\"data\">", $misc->printVal($finfo->name), "</th>\n"; - } else { - $args['page'] = $_REQUEST['page']; - $args['sortkey'] = $j + 1; - // Sort direction opposite to current direction, unless it's currently '' - $args['sortdir'] = ( - $_REQUEST['sortdir'] == 'asc' - and $_REQUEST['sortkey'] == ($j + 1) - ) ? 'desc' : 'asc'; - - $sortLink = http_build_query($args); - - echo "<th class=\"data\"><a href=\"?{$sortLink}\">" - , $misc->printVal($finfo->name); - if ($_REQUEST['sortkey'] == ($j + 1)) { - if ($_REQUEST['sortdir'] == 'asc') { - echo '<img src="' . $misc->icon('RaiseArgument') . '" alt="asc">'; - } else { - echo '<img src="' . $misc->icon('LowerArgument') . '" alt="desc">'; - } - - } - echo "</a></th>\n"; - } - $j++; - } - - reset($rs->fields); -} - -/* Print data-row cells */ -function printTableRowCells(&$rs, &$fkey_information, $withOid) { - global $data, $misc, $conf; - $j = 0; - - if (!isset($_REQUEST['strings'])) { - $_REQUEST['strings'] = 'collapsed'; - } - - foreach ($rs->fields as $k => $v) { - $finfo = $rs->fetchField($j++); - - if (($k === $data->id) && (!($withOid && $conf['show_oids']))) { - continue; - } elseif ($v !== null && $v == '') { - echo "<td> </td>"; - } else { - echo "<td style=\"white-space:nowrap;\">"; - - if (($v !== null) && isset($fkey_information['byfield'][$k])) { - foreach ($fkey_information['byfield'][$k] as $conid) { - - $query_params = $fkey_information['byconstr'][$conid]['url_data']; - - foreach ($fkey_information['byconstr'][$conid]['fkeys'] as $p_field => $f_field) { - $query_params .= '&' . urlencode("fkey[{$f_field}]") . '=' . urlencode($rs->fields[$p_field]); - } - - /* $fkey_information['common_url'] is already urlencoded */ - $query_params .= '&' . $fkey_information['common_url']; - echo "<div style=\"display:inline-block;\">"; - echo "<a class=\"fk fk_" . htmlentities($conid, ENT_QUOTES, 'UTF-8') . "\" href=\"display.php?{$query_params}\">"; - echo "<img src=\"" . $misc->icon('ForeignKey') . "\" style=\"vertical-align:middle;\" alt=\"[fk]\" title=\"" - . htmlentities($fkey_information['byconstr'][$conid]['consrc'], ENT_QUOTES, 'UTF-8') - . "\" />"; - echo "</a>"; - echo "</div>"; - } - echo $misc->printVal($v, $finfo->type, ['null' => true, 'clip' => ($_REQUEST['strings'] == 'collapsed'), 'class' => 'fk_value']); - } else { - echo $misc->printVal($v, $finfo->type, ['null' => true, 'clip' => ($_REQUEST['strings'] == 'collapsed')]); - } - echo "</td>"; - } - } -} - -/* Print the FK row, used in ajax requests */ -function doBrowseFK() { - global $data, $misc, $lang; - - $ops = []; - foreach ($_REQUEST['fkey'] as $x => $y) { - $ops[$x] = '='; - } - $query = $data->getSelectSQL($_REQUEST['table'], [], $_REQUEST['fkey'], $ops); - $_REQUEST['query'] = $query; - - $fkinfo = &getFKInfo(); - - $max_pages = 1; - // Retrieve page from query. $max_pages is returned by reference. - $rs = $data->browseQuery('SELECT', $_REQUEST['table'], $_REQUEST['query'], - null, null, 1, 1, $max_pages); - - echo "<a href=\"\" style=\"display:table-cell;\" class=\"fk_delete\"><img alt=\"[delete]\" src=\"" . $misc->icon('Delete') . "\" /></a>\n"; - echo "<div style=\"display:table-cell;\">"; - - if (is_object($rs) && $rs->recordCount() > 0) { - /* we are browsing a referenced table here - * we should show OID if show_oids is true - * so we give true to withOid in functions bellow - * as 3rd paramter */ - - echo "<table><tr>"; - printTableHeaderCells($rs, false, true); - echo "</tr>"; - echo "<tr class=\"data1\">\n"; - printTableRowCells($rs, $fkinfo, true); - echo "</tr>\n"; - echo "</table>\n"; - } else { - echo $lang['strnodata']; - } - - echo "</div>"; - - exit; -} - -/** - * Displays requested data - */ -function doBrowse($msg = '') { - global $data, $conf, $misc, $lang, $plugin_manager; - - $save_history = false; - // If current page is not set, default to first page - if (!isset($_REQUEST['page'])) { - $_REQUEST['page'] = 1; - } - - if (!isset($_REQUEST['nohistory'])) { - $save_history = true; - } - - if (isset($_REQUEST['subject'])) { - $subject = $_REQUEST['subject']; - if (isset($_REQUEST[$subject])) { - $object = $_REQUEST[$subject]; - } - - } else { - $subject = ''; - } - - $misc->printTrail(isset($subject) ? $subject : 'database'); - $misc->printTabs($subject, 'browse'); - - /* This code is used when browsing FK in pure-xHTML (without js) */ - if (isset($_REQUEST['fkey'])) { - $ops = []; - foreach ($_REQUEST['fkey'] as $x => $y) { - $ops[$x] = '='; - } - $query = $data->getSelectSQL($_REQUEST['table'], [], $_REQUEST['fkey'], $ops); - $_REQUEST['query'] = $query; - } - - if (isset($object)) { - if (isset($_REQUEST['query'])) { - $_SESSION['sqlquery'] = $_REQUEST['query']; - $misc->printTitle($lang['strselect']); - $type = 'SELECT'; - } else { - $type = 'TABLE'; - } - } else { - $misc->printTitle($lang['strqueryresults']); - /*we comes from sql.php, $_SESSION['sqlquery'] has been set there */ - $type = 'QUERY'; - } - - $misc->printMsg($msg); - - // If 'sortkey' is not set, default to '' - if (!isset($_REQUEST['sortkey'])) { - $_REQUEST['sortkey'] = ''; - } - - // If 'sortdir' is not set, default to '' - if (!isset($_REQUEST['sortdir'])) { - $_REQUEST['sortdir'] = ''; - } - - // If 'strings' is not set, default to collapsed - if (!isset($_REQUEST['strings'])) { - $_REQUEST['strings'] = 'collapsed'; - } - - // Fetch unique row identifier, if this is a table browse request. - if (isset($object)) { - $key = $data->getRowIdentifier($object); - } else { - $key = []; - } - - // Set the schema search path - if (isset($_REQUEST['search_path'])) { - if ($data->setSearchPath(array_map('trim', explode(',', $_REQUEST['search_path']))) != 0) { - return; - } - } - - // Retrieve page from query. $max_pages is returned by reference. - $rs = $data->browseQuery($type, - isset($object) ? $object : null, - isset($_SESSION['sqlquery']) ? $_SESSION['sqlquery'] : null, - $_REQUEST['sortkey'], $_REQUEST['sortdir'], $_REQUEST['page'], - $conf['max_rows'], $max_pages); - - $fkey_information = &getFKInfo(); - - // Build strings for GETs in array - $_gets = [ - 'server' => $_REQUEST['server'], - 'database' => $_REQUEST['database'], - ]; - - if (isset($_REQUEST['schema'])) { - $_gets['schema'] = $_REQUEST['schema']; - } - - if (isset($object)) { - $_gets[$subject] = $object; - } - - if (isset($subject)) { - $_gets['subject'] = $subject; - } - - if (isset($_REQUEST['query'])) { - $_gets['query'] = $_REQUEST['query']; - } - - if (isset($_REQUEST['count'])) { - $_gets['count'] = $_REQUEST['count']; - } - - if (isset($_REQUEST['return'])) { - $_gets['return'] = $_REQUEST['return']; - } - - if (isset($_REQUEST['search_path'])) { - $_gets['search_path'] = $_REQUEST['search_path']; - } - - if (isset($_REQUEST['table'])) { - $_gets['table'] = $_REQUEST['table']; - } - - if (isset($_REQUEST['sortkey'])) { - $_gets['sortkey'] = $_REQUEST['sortkey']; - } - - if (isset($_REQUEST['sortdir'])) { - $_gets['sortdir'] = $_REQUEST['sortdir']; - } - - if (isset($_REQUEST['nohistory'])) { - $_gets['nohistory'] = $_REQUEST['nohistory']; - } - - $_gets['strings'] = $_REQUEST['strings']; - - if ($save_history && is_object($rs) && ($type == 'QUERY')) //{ - { - $misc->saveScriptHistory($_REQUEST['query']); - } - - echo '<form method="POST" action="' . $_SERVER['REQUEST_URI'] . '"><textarea width="90%" name="query" rows="5" cols="100" resizable="true">'; - if (isset($_REQUEST['query'])) { - $query = $_REQUEST['query']; - } else { - $query = "SELECT * FROM {$_REQUEST['schema']}"; - if ($_REQUEST['subject'] == 'view') { - $query = "{$query}.{$_REQUEST['view']};"; - } else { - $query = "{$query}.{$_REQUEST['table']};"; - } - } - //$query = isset($_REQUEST['query'])? $_REQUEST['query'] : "select * from {$_REQUEST['schema']}.{$_REQUEST['table']};"; - echo $query; - echo '</textarea><br><input type="submit"/></form>'; - - if (is_object($rs) && $rs->recordCount() > 0) { - // Show page navigation - $misc->printPages($_REQUEST['page'], $max_pages, $_gets); - - echo "<table id=\"data\">\n<tr>"; - - // Check that the key is actually in the result set. This can occur for select - // operations where the key fields aren't part of the select. XXX: We should - // be able to support this, somehow. - foreach ($key as $v) { - // If a key column is not found in the record set, then we - // can't use the key. - if (!in_array($v, array_keys($rs->fields))) { - $key = []; - break; - } - } - - $buttons = [ - 'edit' => [ - 'content' => $lang['stredit'], - 'attr' => [ - 'href' => [ - 'url' => 'display.php', - 'urlvars' => array_merge([ - 'action' => 'confeditrow', - 'strings' => $_REQUEST['strings'], - 'page' => $_REQUEST['page'], - ], $_gets), - ], - ], - ], - 'delete' => [ - 'content' => $lang['strdelete'], - 'attr' => [ - 'href' => [ - 'url' => 'display.php', - 'urlvars' => array_merge([ - 'action' => 'confdelrow', - 'strings' => $_REQUEST['strings'], - 'page' => $_REQUEST['page'], - ], $_gets), - ], - ], - ], - ]; - $actions = [ - 'actionbuttons' => &$buttons, - 'place' => 'display-browse', - ]; - $plugin_manager->do_hook('actionbuttons', $actions); - - foreach (array_keys($actions['actionbuttons']) as $action) { - $actions['actionbuttons'][$action]['attr']['href']['urlvars'] = array_merge( - $actions['actionbuttons'][$action]['attr']['href']['urlvars'], - $_gets - ); - } - - $edit_params = isset($actions['actionbuttons']['edit']) ? - $actions['actionbuttons']['edit'] : []; - $delete_params = isset($actions['actionbuttons']['delete']) ? - $actions['actionbuttons']['delete'] : []; - - // Display edit and delete actions if we have a key - $colspan = count($buttons); - if ($colspan > 0 and count($key) > 0) { - echo "<th colspan=\"{$colspan}\" class=\"data\">{$lang['stractions']}</th>\n"; - } - - /* we show OIDs only if we are in TABLE or SELECT type browsing */ - printTableHeaderCells($rs, $_gets, isset($object)); - - echo "</tr>\n"; - - $i = 0; - reset($rs->fields); - while (!$rs->EOF) { - $id = (($i % 2) == 0 ? '1' : '2'); - echo "<tr class=\"data{$id}\">\n"; - // Display edit and delete links if we have a key - if ($colspan > 0 and count($key) > 0) { - $keys_array = []; - $has_nulls = false; - foreach ($key as $v) { - if ($rs->fields[$v] === null) { - $has_nulls = true; - break; - } - $keys_array["key[{$v}]"] = $rs->fields[$v]; - } - if ($has_nulls) { - echo "<td colspan=\"{$colspan}\"> </td>\n"; - } else { - - if (isset($actions['actionbuttons']['edit'])) { - $actions['actionbuttons']['edit'] = $edit_params; - $actions['actionbuttons']['edit']['attr']['href']['urlvars'] = array_merge( - $actions['actionbuttons']['edit']['attr']['href']['urlvars'], - $keys_array - ); - } - - if (isset($actions['actionbuttons']['delete'])) { - $actions['actionbuttons']['delete'] = $delete_params; - $actions['actionbuttons']['delete']['attr']['href']['urlvars'] = array_merge( - $actions['actionbuttons']['delete']['attr']['href']['urlvars'], - $keys_array - ); - } - - foreach ($actions['actionbuttons'] as $action) { - echo "<td class=\"opbutton{$id}\">"; - $misc->printLink($action); - echo "</td>\n"; - } - } - } - - print printTableRowCells($rs, $fkey_information, isset($object)); - - echo "</tr>\n"; - $rs->moveNext(); - $i++; - } - echo "</table>\n"; - - echo "<p>", $rs->recordCount(), " {$lang['strrows']}</p>\n"; - // Show page navigation - $misc->printPages($_REQUEST['page'], $max_pages, $_gets); - } else { - echo "<p>{$lang['strnodata']}</p>\n"; - } - - // Navigation links - $navlinks = []; - - $fields = [ - 'server' => $_REQUEST['server'], - 'database' => $_REQUEST['database'], - ]; - - if (isset($_REQUEST['schema'])) { - $fields['schema'] = $_REQUEST['schema']; - } - - // Return - if (isset($_REQUEST['return'])) { - $urlvars = $misc->getSubjectParams($_REQUEST['return']); - - $navlinks['back'] = [ - 'attr' => [ - 'href' => [ - 'url' => $urlvars['url'], - 'urlvars' => $urlvars['params'], - ], - ], - 'content' => $lang['strback'], - ]; - } - - // Edit SQL link - if ($type == 'QUERY') { - $navlinks['edit'] = [ - 'attr' => [ - 'href' => [ - 'url' => 'database.php', - 'urlvars' => array_merge($fields, [ - 'action' => 'sql', - 'paginate' => 'on', - ]), - ], - ], - 'content' => $lang['streditsql'], - ]; - } - - // Expand/Collapse - if ($_REQUEST['strings'] == 'expanded') { - $navlinks['collapse'] = [ - 'attr' => [ - 'href' => [ - 'url' => 'display.php', - 'urlvars' => array_merge( - $_gets, - [ - 'strings' => 'collapsed', - 'page' => $_REQUEST['page'], - ]), - ], - ], - 'content' => $lang['strcollapse'], - ]; - } else { - $navlinks['collapse'] = [ - 'attr' => [ - 'href' => [ - 'url' => 'display.php', - 'urlvars' => array_merge( - $_gets, - [ - 'strings' => 'expanded', - 'page' => $_REQUEST['page'], - ]), - ], - ], - 'content' => $lang['strexpand'], - ]; - } - - // Create view and download - if (isset($_REQUEST['query']) && isset($rs) && is_object($rs) && $rs->recordCount() > 0) { - - // Report views don't set a schema, so we need to disable create view in that case - if (isset($_REQUEST['schema'])) { - - $navlinks['createview'] = [ - 'attr' => [ - 'href' => [ - 'url' => 'views.php', - 'urlvars' => array_merge($fields, [ - 'action' => 'create', - 'formDefinition' => $_REQUEST['query'], - ]), - ], - ], - 'content' => $lang['strcreateview'], - ]; - } - - $urlvars = []; - if (isset($_REQUEST['search_path'])) { - $urlvars['search_path'] = $_REQUEST['search_path']; - } - - $navlinks['download'] = [ - 'attr' => [ - 'href' => [ - 'url' => 'dataexport.php', - 'urlvars' => array_merge($fields, $urlvars), - ], - ], - 'content' => $lang['strdownload'], - ]; - } - - // Insert - if (isset($object) && (isset($subject) && $subject == 'table')) { - $navlinks['insert'] = [ - 'attr' => [ - 'href' => [ - 'url' => 'tables.php', - 'urlvars' => array_merge($fields, [ - 'action' => 'confinsertrow', - 'table' => $object, - ]), - ], - ], - 'content' => $lang['strinsert'], - ]; - } - - // Refresh - $navlinks['refresh'] = [ - 'attr' => [ - 'href' => [ - 'url' => 'display.php', - 'urlvars' => array_merge( - $_gets, - [ - 'strings' => $_REQUEST['strings'], - 'page' => $_REQUEST['page'], - ]), - ], - ], - 'content' => $lang['strrefresh'], - ]; - - $misc->printNavLinks($navlinks, 'display-browse', get_defined_vars()); -} - -/* shortcuts: this function exit the script for ajax purpose */ -if ($action == 'dobrowsefk') { - doBrowseFK(); -} - -$scripts = "<script src=\"/js/display.js\" type=\"text/javascript\"></script>"; - -$scripts .= "<script type=\"text/javascript\">\n"; -$scripts .= "var Display = {\n"; -$scripts .= "errmsg: '" . str_replace("'", "\'", $lang['strconnectionfail']) . "'\n"; -$scripts .= "};\n"; -$scripts .= "</script>\n"; - -// Set the title based on the subject of the request -if (isset($_REQUEST['subject']) && isset($_REQUEST[$_REQUEST['subject']])) { - if ($_REQUEST['subject'] == 'table') { - $misc->printHeader( - $lang['strtables'] . ': ' . $_REQUEST[$_REQUEST['subject']], - $scripts - ); - } else if ($_REQUEST['subject'] == 'view') { - $misc->printHeader( - $lang['strviews'] . ': ' . $_REQUEST[$_REQUEST['subject']], - $scripts - ); - } else if ($_REQUEST['subject'] == 'column') { - $misc->printHeader( - $lang['strcolumn'] . ': ' . $_REQUEST[$_REQUEST['subject']], - $scripts - ); - } -} else { - $misc->printHeader($lang['strqueryresults']); -} - -$misc->printBody(); - -switch ($action) { - case 'editrow': - if (isset($_POST['save'])) { - doEditRow(false); - } else { - doBrowse(); - } - - break; - case 'confeditrow': - doEditRow(true); - break; - case 'delrow': - if (isset($_POST['yes'])) { - doDelRow(false); - } else { - doBrowse(); - } - - break; - case 'confdelrow': - doDelRow(true); - break; - default: - doBrowse(); - break; -} +$display_controller = new \PHPPgAdmin\Controller\DisplayController($container); -$misc->printFooter(); +$display_controller->render(); diff --git a/src/views/domains.php b/src/views/domains.php index 809356f6..73f977bd 100644 --- a/src/views/domains.php +++ b/src/views/domains.php @@ -11,71 +11,4 @@ require_once '../lib.inc.php'; $domain_controller = new \PHPPgAdmin\Controller\DomainController($container); -$misc->printHeader($lang['strdomains']); -$misc->printBody(); - -switch ($action) { - case 'add_check': - $domain_controller->addCheck(true); - break; - case 'save_add_check': - if (isset($_POST['cancel'])) { - $domain_controller->doProperties(); - } else { - $domain_controller->addCheck(false); - } - - break; - case 'drop_con': - if (isset($_POST['drop'])) { - $domain_controller->doDropConstraint(false); - } else { - $domain_controller->doProperties(); - } - - break; - case 'confirm_drop_con': - $domain_controller->doDropConstraint(true); - break; - case 'save_create': - if (isset($_POST['cancel'])) { - $domain_controller->doDefault(); - } else { - $domain_controller->doSaveCreate(); - } - - break; - case 'create': - $domain_controller->doCreate(); - break; - case 'drop': - if (isset($_POST['drop'])) { - $domain_controller->doDrop(false); - } else { - $domain_controller->doDefault(); - } - - break; - case 'confirm_drop': - $domain_controller->doDrop(true); - break; - case 'save_alter': - if (isset($_POST['alter'])) { - $domain_controller->doSaveAlter(); - } else { - $domain_controller->doProperties(); - } - - break; - case 'alter': - $domain_controller->doAlter(); - break; - case 'properties': - $domain_controller->doProperties(); - break; - default: - $domain_controller->doDefault(); - break; -} - -$misc->printFooter(); +$domain_controller->render(); diff --git a/src/views/fulltext.php b/src/views/fulltext.php index 16959206..9392e06c 100644 --- a/src/views/fulltext.php +++ b/src/views/fulltext.php @@ -10,104 +10,4 @@ require_once '../lib.inc.php'; $fulltext_controller = new \PHPPgAdmin\Controller\FulltextController($container); - -$misc->printHeader($lang['strschemas']); -$misc->printBody(); - -if (isset($_POST['cancel'])) { - if (isset($_POST['prev_action'])) { - $action = $_POST['prev_action']; - } else { - $action = ''; - } -} - -switch ($action) { - case 'createconfig': - if (isset($_POST['create'])) { - $fulltext_controller->doSaveCreateConfig(); - } else { - $fulltext_controller->doCreateConfig(); - } - - break; - case 'alterconfig': - if (isset($_POST['alter'])) { - $fulltext_controller->doSaveAlterConfig(); - } else { - $fulltext_controller->doAlterConfig(); - } - - break; - case 'dropconfig': - if (isset($_POST['drop'])) { - $fulltext_controller->doDropConfig(false); - } else { - $fulltext_controller->doDropConfig(true); - } - - break; - case 'viewconfig': - $fulltext_controller->doViewConfig($_REQUEST['ftscfg']); - break; - case 'viewparsers': - $fulltext_controller->doViewParsers(); - break; - case 'viewdicts': - $fulltext_controller->doViewDicts(); - break; - case 'createdict': - if (isset($_POST['create'])) { - $fulltext_controller->doSaveCreateDict(); - } else { - doCreateDict(); - } - - break; - case 'alterdict': - if (isset($_POST['alter'])) { - $fulltext_controller->doSaveAlterDict(); - } else { - $fulltext_controller->doAlterDict(); - } - - break; - case 'dropdict': - if (isset($_POST['drop'])) { - $fulltext_controller->doDropDict(false); - } else { - $fulltext_controller->doDropDict(true); - } - - break; - case 'dropmapping': - if (isset($_POST['drop'])) { - $fulltext_controller->doDropMapping(false); - } else { - $fulltext_controller->doDropMapping(true); - } - - break; - case 'altermapping': - if (isset($_POST['alter'])) { - $fulltext_controller->doSaveAlterMapping(); - } else { - $fulltext_controller->doAlterMapping(); - } - - break; - case 'addmapping': - if (isset($_POST['add'])) { - $fulltext_controller->doSaveAddMapping(); - } else { - $fulltext_controller->doAddMapping(); - } - - break; - - default: - $fulltext_controller->doDefault(); - break; -} - -$misc->printFooter(); +$fulltext_controller->render(); diff --git a/src/views/functions.php b/src/views/functions.php index 20b85f09..a372ebdc 100644 --- a/src/views/functions.php +++ b/src/views/functions.php @@ -11,49 +11,4 @@ require_once '../lib.inc.php'; $function_controller = new \PHPPgAdmin\Controller\FunctionController($container); -$misc->printHeader($lang['strfunctions']); -$misc->printBody(); - -switch ($action) { - case 'save_create': - if (isset($_POST['cancel'])) { - $function_controller->doDefault(); - } else { - $function_controller->doSaveCreate(); - } - - break; - case 'create': - $function_controller->doCreate(); - break; - case 'drop': - if (isset($_POST['drop'])) { - $function_controller->doDrop(false); - } else { - $function_controller->doDefault(); - } - - break; - case 'confirm_drop': - $function_controller->doDrop(true); - break; - case 'save_edit': - if (isset($_POST['cancel'])) { - $function_controller->doDefault(); - } else { - $function_controller->doSaveEdit(); - } - - break; - case 'edit': - $function_controller->doEdit(); - break; - case 'properties': - $function_controller->doProperties(); - break; - default: - $function_controller->doDefault(); - break; -} - -$misc->printFooter(); +$function_controller->render(); diff --git a/src/views/indexes.php b/src/views/indexes.php index 6677cf70..6705f640 100644 --- a/src/views/indexes.php +++ b/src/views/indexes.php @@ -11,54 +11,4 @@ require_once '../lib.inc.php'; $index_controller = new \PHPPgAdmin\Controller\IndexController($container); -$misc->printHeader($lang['strindexes'], "<script src=\"/js/indexes.js\" type=\"text/javascript\"></script>"); - -if ($action == 'create_index' || $action == 'save_create_index') { - echo "<body onload=\"init();\">"; -} else { - $misc->printBody(); -} - -switch ($action) { - case 'cluster_index': - if (isset($_POST['cluster'])) { - $index_controller->doClusterIndex(false); - } else { - $index_controller->doDefault(); - } - - break; - case 'confirm_cluster_index': - $index_controller->doClusterIndex(true); - break; - case 'reindex': - $index_controller->doReindex(); - break; - case 'save_create_index': - if (isset($_POST['cancel'])) { - $index_controller->doDefault(); - } else { - $index_controller->doSaveCreateIndex(); - } - - break; - case 'create_index': - $index_controller->doCreateIndex(); - break; - case 'drop_index': - if (isset($_POST['drop'])) { - $index_controller->doDropIndex(false); - } else { - $index_controller->doDefault(); - } - - break; - case 'confirm_drop_index': - $index_controller->doDropIndex(true); - break; - default: - $index_controller->doDefault(); - break; -} - -$misc->printFooter(); +$index_controller->render(); diff --git a/src/views/intro.php b/src/views/intro.php index 6df90801..dd42304c 100755 --- a/src/views/intro.php +++ b/src/views/intro.php @@ -6,14 +6,4 @@ if (!defined('BASE_PATH')) { $intro_controller = new \PHPPgAdmin\Controller\IntroController($container); -$misc->printHeader($lang['strcasts']); -$misc->printBody(); - -switch ($action) { - - default: - $intro_controller->doDefault(); - break; -} - -$misc->printFooter(); +$intro_controller->render(); diff --git a/src/views/languages.php b/src/views/languages.php index 200fbb62..7a000e5f 100644 --- a/src/views/languages.php +++ b/src/views/languages.php @@ -11,13 +11,4 @@ require_once '../lib.inc.php'; $lang_controller = new \PHPPgAdmin\Controller\LangController($container); -$misc->printHeader($lang['strlanguages']); -$misc->printBody(); - -switch ($action) { - default: - $lang_controller->doDefault(); - break; -} - -$misc->printFooter(); +$lang_controller->render(); diff --git a/src/views/login.php b/src/views/login.php index a9db4788..99168595 100755 --- a/src/views/login.php +++ b/src/views/login.php @@ -5,89 +5,11 @@ * * $Id: login.php,v 1.38 2007/09/04 19:39:48 ioguix Exp $ */ -function doLoginForm($container, $msg) { +if (!defined('BASE_PATH')) { - $lang = $container->get('lang'); - $conf = $container->get('conf'); - $misc = $container->get('misc'); - //$msg = $container->msg; + require_once '../lib.inc.php'; +} - $login_html = $misc->printHeader($lang['strlogin'], null, false); - $login_html .= $misc->printBody(false); - $login_html .= $misc->printTrail('root', false); +$login_controller = new \PHPPgAdmin\Controller\LoginController($container); - if (!empty($_POST)) { - $vars = &$_POST; - } else { - $vars = &$_GET; - } - foreach ($_REQUEST as $key => $val) { - if (strpos($key, '?') !== FALSE) { - $namexploded = explode('?', $key); - $_REQUEST[$namexploded[1]] = htmlspecialchars($val); - } - } - - $server_info = $misc->getServerInfo($_REQUEST['server']); - $title = sprintf($lang['strlogintitle'], $server_info['desc']); - \PC::debug($title, 'title'); - $printTitle = $misc->printTitle($title, null, false); - \PC::debug($printTitle, 'printTitle'); - - $login_html .= $printTitle; - - if (isset($msg)) { - $login_html .= $misc->printMsg($msg, false); - } - - $login_html .= '<form id="login_form" method="post" name="login_form">'; - - $md5_server = md5($_REQUEST['server']); -// Pass request vars through form (is this a security risk???) - foreach ($vars as $key => $val) { - if (substr($key, 0, 5) == 'login') { - continue; - } - if (strpos($key, '?') !== FALSE) { - $key = explode('?', $key)[1]; - } - - $login_html .= '<input type="hidden" name="' . htmlspecialchars($key) . '" value="' . htmlspecialchars($val) . '" />' . "\n"; - } - - $login_html .= '<input type="hidden" name="loginServer" value="' . htmlspecialchars($_REQUEST['server']) . '" />'; - $login_html .= '<table class="navbar" border="0" cellpadding="5" cellspacing="3">'; - $login_html .= '<tr>'; - $login_html .= '<td>' . $lang['strusername'] . '</td>'; - $loginusername = isset($_POST['loginUsername']) ? htmlspecialchars($_POST['loginUsername']) : ''; - - $login_html .= '<td><input type="text" name="loginUsername" value="' . $loginusername . '" size="24" /></td>'; - $login_html .= '</tr>'; - $login_html .= '<tr>'; - $login_html .= '<td>' . $lang['strpassword'] . '</td>'; - $login_html .= '<td><input id="loginPassword" type="password" name="loginPassword_' . $md5_server . '" size="24" /></td>'; - $login_html .= '</tr>'; - $login_html .= '</table>'; - if (sizeof($conf['servers']) > 1) { - $checked = isset($_POST['loginShared']) ? 'checked="checked"' : ''; - $login_html .= '<p><input type="checkbox" id="loginShared" name="loginShared" ' . $checked . ' />'; - $login_html .= '<label for="loginShared">' . $lang['strtrycred'] . '</label></p>'; - } - $login_html .= '<p><input type="submit" name="loginSubmit" value="' . $lang['strlogin'] . '" /></p>'; - $login_html .= '</form>'; - - $login_html .= '<script type="text/javascript">'; - $login_html .= ' var uname = document.login_form.loginUsername;'; - $login_html .= ' var pword = document.login_form.loginPassword_' . $md5_server . ';'; - $login_html .= ' if (uname.value == "") {'; - $login_html .= ' uname.focus();'; - $login_html .= ' } else {'; - $login_html .= ' pword.focus();'; - $login_html .= ' }'; - $login_html .= '</script>'; - -// Output footer - $login_html .= $misc->printFooter(false); - return $login_html; - -}
\ No newline at end of file +$login_controller->render();
\ No newline at end of file diff --git a/src/views/materialized_views.php b/src/views/materialized_views.php new file mode 100644 index 00000000..e2df9c41 --- /dev/null +++ b/src/views/materialized_views.php @@ -0,0 +1,14 @@ +<?php + +/** + * Manage views in a database + * + * $Id: views.php,v 1.75 2007/12/15 22:57:43 ioguix Exp $ + */ + +// Include application functions +require_once '../lib.inc.php'; + +$materialized_view_controller = new \PHPPgAdmin\Controller\MaterializedViewController($container); + +$materialized_view_controller->render(); diff --git a/src/views/opclasses.php b/src/views/opclasses.php index 63e5f2fa..15acb1a2 100644 --- a/src/views/opclasses.php +++ b/src/views/opclasses.php @@ -10,14 +10,4 @@ require_once '../lib.inc.php'; $opclasses_controller = new \PHPPgAdmin\Controller\OpClassesController($container); - -$misc->printHeader($lang['stropclasses']); -$misc->printBody(); - -switch ($action) { - default: - $opclasses_controller->doDefault(); - break; -} - -$misc->printFooter(); +$opclasses_controller->render();
\ No newline at end of file diff --git a/src/views/operators.php b/src/views/operators.php index d6f33f27..5ef5b9bf 100644 --- a/src/views/operators.php +++ b/src/views/operators.php @@ -10,39 +10,4 @@ require_once '../lib.inc.php'; $operator_controller = new \PHPPgAdmin\Controller\OperatorController($container); - -$misc->printHeader($lang['stroperators']); -$misc->printBody(); - -switch ($action) { - case 'save_create': - if (isset($_POST['cancel'])) { - $operator_controller->doDefault(); - } else { - $operator_controller->doSaveCreate(); - } - - break; - case 'create': - doCreate(); - break; - case 'drop': - if (isset($_POST['cancel'])) { - $operator_controller->doDefault(); - } else { - $operator_controller->doDrop(false); - } - - break; - case 'confirm_drop': - $operator_controller->doDrop(true); - break; - case 'properties': - $operator_controller->doProperties(); - break; - default: - $operator_controller->doDefault(); - break; -} - -$misc->printFooter(); +$operator_controller->render();
\ No newline at end of file diff --git a/src/views/privileges.php b/src/views/privileges.php index d0d5be41..b48cc9ad 100644 --- a/src/views/privileges.php +++ b/src/views/privileges.php @@ -6,387 +6,8 @@ * $Id: privileges.php,v 1.45 2007/09/13 13:41:01 ioguix Exp $ */ -// Include application functions require_once '../lib.inc.php'; -$action = (isset($_REQUEST['action'])) ? $_REQUEST['action'] : ''; -if (!isset($msg)) { - $msg = ''; -} +$privilege_controller = new \PHPPgAdmin\Controller\PrivilegeController($container); -/** - * Grant permissions on an object to a user - * @param $confirm To show entry screen - * @param $mode 'grant' or 'revoke' - * @param $msg (optional) A message to show - */ -function doAlter($confirm, $mode, $msg = '') { - global $data, $misc; - global $lang; - - if (!isset($_REQUEST['username'])) { - $_REQUEST['username'] = []; - } - - if (!isset($_REQUEST['groupname'])) { - $_REQUEST['groupname'] = []; - } - - if (!isset($_REQUEST['privilege'])) { - $_REQUEST['privilege'] = []; - } - - if ($confirm) { - // Get users from the database - $users = $data->getUsers(); - // Get groups from the database - $groups = $data->getGroups(); - - $misc->printTrail($_REQUEST['subject']); - - switch ($mode) { - case 'grant': - $misc->printTitle($lang['strgrant'], 'pg.privilege.grant'); - break; - case 'revoke': - $misc->printTitle($lang['strrevoke'], 'pg.privilege.revoke'); - break; - } - $misc->printMsg($msg); - - echo "<form action=\"/src/views/privileges.php\" method=\"post\">\n"; - echo "<table>\n"; - echo "<tr><th class=\"data left\">{$lang['strusers']}</th>\n"; - echo "<td class=\"data1\"><select name=\"username[]\" multiple=\"multiple\" size=\"", min(6, $users->recordCount()), "\">\n"; - while (!$users->EOF) { - $uname = htmlspecialchars($users->fields['usename']); - echo "<option value=\"{$uname}\"", - in_array($users->fields['usename'], $_REQUEST['username']) ? ' selected="selected"' : '', ">{$uname}</option>\n"; - $users->moveNext(); - } - echo "</select></td></tr>\n"; - echo "<tr><th class=\"data left\">{$lang['strgroups']}</th>\n"; - echo "<td class=\"data1\">\n"; - echo "<input type=\"checkbox\" id=\"public\" name=\"public\"", (isset($_REQUEST['public']) ? ' checked="checked"' : ''), " /><label for=\"public\">PUBLIC</label>\n"; - // Only show groups if there are groups! - if ($groups->recordCount() > 0) { - echo "<br /><select name=\"groupname[]\" multiple=\"multiple\" size=\"", min(6, $groups->recordCount()), "\">\n"; - while (!$groups->EOF) { - $gname = htmlspecialchars($groups->fields['groname']); - echo "<option value=\"{$gname}\"", - in_array($groups->fields['groname'], $_REQUEST['groupname']) ? ' selected="selected"' : '', ">{$gname}</option>\n"; - $groups->moveNext(); - } - echo "</select>\n"; - } - echo "</td></tr>\n"; - echo "<tr><th class=\"data left required\">{$lang['strprivileges']}</th>\n"; - echo "<td class=\"data1\">\n"; - foreach ($data->privlist[$_REQUEST['subject']] as $v) { - $v = htmlspecialchars($v); - echo "<input type=\"checkbox\" id=\"privilege[$v]\" name=\"privilege[$v]\"", - isset($_REQUEST['privilege'][$v]) ? ' checked="checked"' : '', " /><label for=\"privilege[$v]\">{$v}</label><br />\n"; - } - echo "</td></tr>\n"; - // Grant option - if ($data->hasGrantOption()) { - echo "<tr><th class=\"data left\">{$lang['stroptions']}</th>\n"; - echo "<td class=\"data1\">\n"; - if ($mode == 'grant') { - echo "<input type=\"checkbox\" id=\"grantoption\" name=\"grantoption\"", - isset($_REQUEST['grantoption']) ? ' checked="checked"' : '', " /><label for=\"grantoption\">GRANT OPTION</label>\n"; - } elseif ($mode == 'revoke') { - echo "<input type=\"checkbox\" id=\"grantoption\" name=\"grantoption\"", - isset($_REQUEST['grantoption']) ? ' checked="checked"' : '', " /><label for=\"grantoption\">GRANT OPTION FOR</label><br />\n"; - echo "<input type=\"checkbox\" id=\"cascade\" name=\"cascade\"", - isset($_REQUEST['cascade']) ? ' checked="checked"' : '', " /><label for=\"cascade\">CASCADE</label><br />\n"; - } - echo "</td></tr>\n"; - } - echo "</table>\n"; - - echo "<p><input type=\"hidden\" name=\"action\" value=\"save\" />\n"; - echo "<input type=\"hidden\" name=\"mode\" value=\"", htmlspecialchars($mode), "\" />\n"; - echo "<input type=\"hidden\" name=\"subject\" value=\"", htmlspecialchars($_REQUEST['subject']), "\" />\n"; - if (isset($_REQUEST[$_REQUEST['subject'] . '_oid'])) { - echo "<input type=\"hidden\" name=\"", htmlspecialchars($_REQUEST['subject'] . '_oid'), - "\" value=\"", htmlspecialchars($_REQUEST[$_REQUEST['subject'] . '_oid']), "\" />\n"; - } - - echo "<input type=\"hidden\" name=\"", htmlspecialchars($_REQUEST['subject']), - "\" value=\"", htmlspecialchars($_REQUEST[$_REQUEST['subject']]), "\" />\n"; - if ($_REQUEST['subject'] == 'column') { - echo "<input type=\"hidden\" name=\"table\" value=\"", - htmlspecialchars($_REQUEST['table']), "\" />\n"; - } - - echo $misc->form; - if ($mode == 'grant') { - echo "<input type=\"submit\" name=\"grant\" value=\"{$lang['strgrant']}\" />\n"; - } elseif ($mode == 'revoke') { - echo "<input type=\"submit\" name=\"revoke\" value=\"{$lang['strrevoke']}\" />\n"; - } - - echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>"; - echo "</form>\n"; - } else { - // Determine whether object should be ref'd by name or oid. - if (isset($_REQUEST[$_REQUEST['subject'] . '_oid'])) { - $object = $_REQUEST[$_REQUEST['subject'] . '_oid']; - } else { - $object = $_REQUEST[$_REQUEST['subject']]; - } - - if (isset($_REQUEST['table'])) { - $table = $_REQUEST['table']; - } else { - $table = null; - } - - $status = $data->setPrivileges(($mode == 'grant') ? 'GRANT' : 'REVOKE', $_REQUEST['subject'], $object, - isset($_REQUEST['public']), $_REQUEST['username'], $_REQUEST['groupname'], array_keys($_REQUEST['privilege']), - isset($_REQUEST['grantoption']), isset($_REQUEST['cascade']), $table); - - if ($status == 0) { - doDefault($lang['strgranted']); - } elseif ($status == -3 || $status == -4) { - doAlter(true, $_REQUEST['mode'], $lang['strgrantbad']); - } else { - doAlter(true, $_REQUEST['mode'], $lang['strgrantfailed']); - } - - } -} - -/** - * Show permissions on a database, namespace, relation, language or function - */ -function doDefault($msg = '') { - global $data, $misc, $database; - global $lang; - - $misc->printTrail($_REQUEST['subject']); - - # @@@FIXME: This switch is just a temporary solution, - # need a better way, maybe every type of object should - # have a tab bar??? - switch ($_REQUEST['subject']) { - case 'server': - case 'database': - case 'schema': - case 'table': - case 'column': - case 'view': - $misc->printTabs($_REQUEST['subject'], 'privileges'); - break; - default: - $misc->printTitle($lang['strprivileges'], 'pg.privilege'); - } - $misc->printMsg($msg); - - // Determine whether object should be ref'd by name or oid. - if (isset($_REQUEST[$_REQUEST['subject'] . '_oid'])) { - $object = $_REQUEST[$_REQUEST['subject'] . '_oid']; - } else { - $object = $_REQUEST[$_REQUEST['subject']]; - } - - // Get the privileges on the object, given its type - if ($_REQUEST['subject'] == 'column') { - $privileges = $data->getPrivileges($object, 'column', $_REQUEST['table']); - } else { - $privileges = $data->getPrivileges($object, $_REQUEST['subject']); - } - - if (sizeof($privileges) > 0) { - echo "<table>\n"; - if ($data->hasRoles()) { - echo "<tr><th class=\"data\">{$lang['strrole']}</th>"; - } else { - echo "<tr><th class=\"data\">{$lang['strtype']}</th><th class=\"data\">{$lang['struser']}/{$lang['strgroup']}</th>"; - } - - foreach ($data->privlist[$_REQUEST['subject']] as $v2) { - // Skip over ALL PRIVILEGES - if ($v2 == 'ALL PRIVILEGES') { - continue; - } - - echo "<th class=\"data\">{$v2}</th>\n"; - } - if ($data->hasGrantOption()) { - echo "<th class=\"data\">{$lang['strgrantor']}</th>"; - } - echo "</tr>\n"; - - // Loop over privileges, outputting them - $i = 0; - foreach ($privileges as $v) { - $id = (($i % 2) == 0 ? '1' : '2'); - echo "<tr class=\"data{$id}\">\n"; - if (!$data->hasRoles()) { - echo "<td>", $misc->printVal($v[0]), "</td>\n"; - } - - echo "<td>", $misc->printVal($v[1]), "</td>\n"; - foreach ($data->privlist[$_REQUEST['subject']] as $v2) { - // Skip over ALL PRIVILEGES - if ($v2 == 'ALL PRIVILEGES') { - continue; - } - - echo "<td>"; - if (in_array($v2, $v[2])) { - echo $lang['stryes']; - } else { - echo $lang['strno']; - } - - // If we have grant option for this, end mark - if ($data->hasGrantOption() && in_array($v2, $v[4])) { - echo $lang['strasterisk']; - } - - echo "</td>\n"; - } - if ($data->hasGrantOption()) { - echo "<td>", $misc->printVal($v[3]), "</td>\n"; - } - echo "</tr>\n"; - $i++; - } - - echo "</table>"; - } else { - echo "<p>{$lang['strnoprivileges']}</p>\n"; - } - - // Links for granting to a user or group - switch ($_REQUEST['subject']) { - case 'table': - case 'view': - case 'sequence': - case 'function': - case 'tablespace': - $alllabel = "showall{$_REQUEST['subject']}s"; - $allurl = "{$_REQUEST['subject']}s.php"; - $alltxt = $lang["strshowall{$_REQUEST['subject']}s"]; - break; - case 'schema': - $alllabel = "showallschemas"; - $allurl = "schemas.php"; - $alltxt = $lang["strshowallschemas"]; - break; - case 'database': - $alllabel = "showalldatabases"; - $allurl = 'all_db.php'; - $alltxt = $lang['strshowalldatabases']; - break; - } - - $subject = $_REQUEST['subject']; - $object = $_REQUEST[$_REQUEST['subject']]; - - if ($_REQUEST['subject'] == 'function') { - $objectoid = $_REQUEST[$_REQUEST['subject'] . '_oid']; - $urlvars = [ - 'action' => 'alter', - 'server' => $_REQUEST['server'], - 'database' => $_REQUEST['database'], - 'schema' => $_REQUEST['schema'], - $subject => $object, - "{$subject}_oid" => $objectoid, - 'subject' => $subject, - ]; - } else if ($_REQUEST['subject'] == 'column') { - $urlvars = [ - 'action' => 'alter', - 'server' => $_REQUEST['server'], - 'database' => $_REQUEST['database'], - 'schema' => $_REQUEST['schema'], - $subject => $object, - 'subject' => $subject, - ]; - - if (isset($_REQUEST['table'])) { - $urlvars['table'] = $_REQUEST['table']; - } else { - $urlvars['view'] = $_REQUEST['view']; - } - - } else { - $urlvars = [ - 'action' => 'alter', - 'server' => $_REQUEST['server'], - 'database' => $_REQUEST['database'], - $subject => $object, - 'subject' => $subject, - ]; - if (isset($_REQUEST['schema'])) { - $urlvars['schema'] = $_REQUEST['schema']; - } - } - - $navlinks = [ - 'grant' => [ - 'attr' => [ - 'href' => [ - 'url' => 'privileges.php', - 'urlvars' => array_merge($urlvars, ['mode' => 'grant']), - ], - ], - 'content' => $lang['strgrant'], - ], - 'revoke' => [ - 'attr' => [ - 'href' => [ - 'url' => 'privileges.php', - 'urlvars' => array_merge($urlvars, ['mode' => 'revoke']), - ], - ], - 'content' => $lang['strrevoke'], - ], - ]; - - if (isset($allurl)) { - $navlinks[$alllabel] = [ - 'attr' => [ - 'href' => [ - 'url' => $allurl, - 'urlvars' => [ - 'server' => $_REQUEST['server'], - 'database' => $_REQUEST['database'], - ], - ], - ], - 'content' => $alltxt, - ]; - if (isset($_REQUEST['schema'])) { - $navlinks[$alllabel]['attr']['href']['urlvars']['schema'] = $_REQUEST['schema']; - } - } - - $misc->printNavLinks($navlinks, 'privileges-privileges', get_defined_vars()); -} - -$misc->printHeader($lang['strprivileges']); -$misc->printBody(); - -switch ($action) { - case 'save': - if (isset($_REQUEST['cancel'])) { - doDefault(); - } else { - doAlter(false, $_REQUEST['mode']); - } - - break; - case 'alter': - doAlter(true, $_REQUEST['mode']); - break; - default: - doDefault(); - break; -} - -$misc->printFooter(); +$privilege_controller->render();
\ No newline at end of file diff --git a/src/views/roles.php b/src/views/roles.php index 92c222da..f4ce2860 100644 --- a/src/views/roles.php +++ b/src/views/roles.php @@ -9,828 +9,6 @@ // Include application functions require_once '../lib.inc.php'; -$action = (isset($_REQUEST['action'])) ? $_REQUEST['action'] : ''; -if (!isset($msg)) { - $msg = ''; -} +$roles_controller = new \PHPPgAdmin\Controller\RolesController($container); -/** - * Displays a screen for create a new role - */ -function doCreate($msg = '') { - global $data, $misc, $username; - global $lang; - - if (!isset($_POST['formRolename'])) { - $_POST['formRolename'] = ''; - } - - if (!isset($_POST['formPassword'])) { - $_POST['formPassword'] = ''; - } - - if (!isset($_POST['formConfirm'])) { - $_POST['formConfirm'] = ''; - } - - if (!isset($_POST['formConnLimit'])) { - $_POST['formConnLimit'] = ''; - } - - if (!isset($_POST['formExpires'])) { - $_POST['formExpires'] = ''; - } - - if (!isset($_POST['memberof'])) { - $_POST['memberof'] = []; - } - - if (!isset($_POST['members'])) { - $_POST['members'] = []; - } - - if (!isset($_POST['adminmembers'])) { - $_POST['adminmembers'] = []; - } - - $misc->printTrail('role'); - $misc->printTitle($lang['strcreaterole'], 'pg.role.create'); - $misc->printMsg($msg); - - echo "<form action=\"/src/views/roles.php\" method=\"post\">\n"; - echo "<table>\n"; - echo "\t<tr>\n\t\t<th class=\"data left required\" style=\"width: 130px\">{$lang['strname']}</th>\n"; - echo "\t\t<td class=\"data1\"><input size=\"15\" maxlength=\"{$data->_maxNameLen}\" name=\"formRolename\" value=\"", htmlspecialchars($_POST['formRolename']), "\" /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strpassword']}</th>\n"; - echo "\t\t<td class=\"data1\"><input size=\"15\" type=\"password\" name=\"formPassword\" value=\"", htmlspecialchars($_POST['formPassword']), "\" /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strconfirm']}</th>\n"; - echo "\t\t<td class=\"data1\"><input size=\"15\" type=\"password\" name=\"formConfirm\" value=\"", htmlspecialchars($_POST['formConfirm']), "\" /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formSuper\">{$lang['strsuper']}</label></th>\n"; - echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formSuper\" name=\"formSuper\"", - (isset($_POST['formSuper'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formCreateDB\">{$lang['strcreatedb']}</label></th>\n"; - echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formCreateDB\" name=\"formCreateDB\"", - (isset($_POST['formCreateDB'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formCreateRole\">{$lang['strcancreaterole']}</label></th>\n"; - echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formCreateRole\" name=\"formCreateRole\"", - (isset($_POST['formCreateRole'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formInherits\">{$lang['strinheritsprivs']}</label></th>\n"; - echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formInherits\" name=\"formInherits\"", - (isset($_POST['formInherits'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formCanLogin\">{$lang['strcanlogin']}</label></th>\n"; - echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formCanLogin\" name=\"formCanLogin\"", - (isset($_POST['formCanLogin'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strconnlimit']}</th>\n"; - echo "\t\t<td class=\"data1\"><input size=\"4\" name=\"formConnLimit\" value=\"", htmlspecialchars($_POST['formConnLimit']), "\" /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strexpires']}</th>\n"; - echo "\t\t<td class=\"data1\"><input size=\"23\" name=\"formExpires\" value=\"", htmlspecialchars($_POST['formExpires']), "\" /></td>\n\t</tr>\n"; - - $roles = $data->getRoles(); - if ($roles->recordCount() > 0) { - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strmemberof']}</th>\n"; - echo "\t\t<td class=\"data\">\n"; - echo "\t\t\t<select name=\"memberof[]\" multiple=\"multiple\" size=\"", min(20, $roles->recordCount()), "\">\n"; - while (!$roles->EOF) { - $rolename = $roles->fields['rolname']; - echo "\t\t\t\t<option value=\"{$rolename}\"", - (in_array($rolename, $_POST['memberof']) ? ' selected="selected"' : ''), ">", $misc->printVal($rolename), "</option>\n"; - $roles->moveNext(); - } - echo "\t\t\t</select>\n"; - echo "\t\t</td>\n\t</tr>\n"; - - $roles->moveFirst(); - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strmembers']}</th>\n"; - echo "\t\t<td class=\"data\">\n"; - echo "\t\t\t<select name=\"members[]\" multiple=\"multiple\" size=\"", min(20, $roles->recordCount()), "\">\n"; - while (!$roles->EOF) { - $rolename = $roles->fields['rolname']; - echo "\t\t\t\t<option value=\"{$rolename}\"", - (in_array($rolename, $_POST['members']) ? ' selected="selected"' : ''), ">", $misc->printVal($rolename), "</option>\n"; - $roles->moveNext(); - } - echo "\t\t\t</select>\n"; - echo "\t\t</td>\n\t</tr>\n"; - - $roles->moveFirst(); - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['stradminmembers']}</th>\n"; - echo "\t\t<td class=\"data\">\n"; - echo "\t\t\t<select name=\"adminmembers[]\" multiple=\"multiple\" size=\"", min(20, $roles->recordCount()), "\">\n"; - while (!$roles->EOF) { - $rolename = $roles->fields['rolname']; - echo "\t\t\t\t<option value=\"{$rolename}\"", - (in_array($rolename, $_POST['adminmembers']) ? ' selected="selected"' : ''), ">", $misc->printVal($rolename), "</option>\n"; - $roles->moveNext(); - } - echo "\t\t\t</select>\n"; - echo "\t\t</td>\n\t</tr>\n"; - } - - echo "</table>\n"; - echo "<p><input type=\"hidden\" name=\"action\" value=\"save_create\" />\n"; - echo $misc->form; - echo "<input type=\"submit\" name=\"create\" value=\"{$lang['strcreate']}\" />\n"; - echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; - echo "</form>\n"; -} - -/** - * Actually creates the new role in the database - */ -function doSaveCreate() { - global $data, $lang; - - if (!isset($_POST['memberof'])) { - $_POST['memberof'] = []; - } - - if (!isset($_POST['members'])) { - $_POST['members'] = []; - } - - if (!isset($_POST['adminmembers'])) { - $_POST['adminmembers'] = []; - } - - // Check data - if ($_POST['formRolename'] == '') { - doCreate($lang['strroleneedsname']); - } else if ($_POST['formPassword'] != $_POST['formConfirm']) { - doCreate($lang['strpasswordconfirm']); - } else { - $status = $data->createRole($_POST['formRolename'], $_POST['formPassword'], isset($_POST['formSuper']), - isset($_POST['formCreateDB']), isset($_POST['formCreateRole']), isset($_POST['formInherits']), - isset($_POST['formCanLogin']), $_POST['formConnLimit'], $_POST['formExpires'], $_POST['memberof'], $_POST['members'], - $_POST['adminmembers']); - if ($status == 0) { - doDefault($lang['strrolecreated']); - } else { - doCreate($lang['strrolecreatedbad']); - } - - } -} - -/** - * Function to allow alter a role - */ -function doAlter($msg = '') { - global $data, $misc; - global $lang; - - $misc->printTrail('role'); - $misc->printTitle($lang['stralter'], 'pg.role.alter'); - $misc->printMsg($msg); - - $roledata = $data->getRole($_REQUEST['rolename']); - - if ($roledata->recordCount() > 0) { - $server_info = $misc->getServerInfo(); - $canRename = $data->hasUserRename() && ($_REQUEST['rolename'] != $server_info['username']); - $roledata->fields['rolsuper'] = $data->phpBool($roledata->fields['rolsuper']); - $roledata->fields['rolcreatedb'] = $data->phpBool($roledata->fields['rolcreatedb']); - $roledata->fields['rolcreaterole'] = $data->phpBool($roledata->fields['rolcreaterole']); - $roledata->fields['rolinherit'] = $data->phpBool($roledata->fields['rolinherit']); - $roledata->fields['rolcanlogin'] = $data->phpBool($roledata->fields['rolcanlogin']); - - if (!isset($_POST['formExpires'])) { - if ($canRename) { - $_POST['formNewRoleName'] = $roledata->fields['rolname']; - } - - if ($roledata->fields['rolsuper']) { - $_POST['formSuper'] = ''; - } - - if ($roledata->fields['rolcreatedb']) { - $_POST['formCreateDB'] = ''; - } - - if ($roledata->fields['rolcreaterole']) { - $_POST['formCreateRole'] = ''; - } - - if ($roledata->fields['rolinherit']) { - $_POST['formInherits'] = ''; - } - - if ($roledata->fields['rolcanlogin']) { - $_POST['formCanLogin'] = ''; - } - - $_POST['formConnLimit'] = $roledata->fields['rolconnlimit'] == '-1' ? '' : $roledata->fields['rolconnlimit']; - $_POST['formExpires'] = $roledata->fields['rolvaliduntil'] == 'infinity' ? '' : $roledata->fields['rolvaliduntil']; - $_POST['formPassword'] = ''; - } - - echo "<form action=\"/src/views/roles.php\" method=\"post\">\n"; - echo "<table>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\" style=\"width: 130px\">{$lang['strname']}</th>\n"; - echo "\t\t<td class=\"data1\">", ($canRename ? "<input name=\"formNewRoleName\" size=\"15\" maxlength=\"{$data->_maxNameLen}\" value=\"" . htmlspecialchars($_POST['formNewRoleName']) . "\" />" : $misc->printVal($roledata->fields['rolname'])), "</td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strpassword']}</th>\n"; - echo "\t\t<td class=\"data1\"><input type=\"password\" size=\"15\" name=\"formPassword\" value=\"", htmlspecialchars($_POST['formPassword']), "\" /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strconfirm']}</th>\n"; - echo "\t\t<td class=\"data1\"><input type=\"password\" size=\"15\" name=\"formConfirm\" value=\"\" /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formSuper\">{$lang['strsuper']}</label></th>\n"; - echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formSuper\" name=\"formSuper\"", - (isset($_POST['formSuper'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formCreateDB\">{$lang['strcreatedb']}</label></th>\n"; - echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formCreateDB\" name=\"formCreateDB\"", - (isset($_POST['formCreateDB'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formCreateRole\">{$lang['strcancreaterole']}</label></th>\n"; - echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formCreateRole\" name=\"formCreateRole\"", - (isset($_POST['formCreateRole'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formInherits\">{$lang['strinheritsprivs']}</label></th>\n"; - echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formInherits\" name=\"formInherits\"", - (isset($_POST['formInherits'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\"><label for=\"formCanLogin\">{$lang['strcanlogin']}</label></th>\n"; - echo "\t\t<td class=\"data1\"><input type=\"checkbox\" id=\"formCanLogin\" name=\"formCanLogin\"", - (isset($_POST['formCanLogin'])) ? ' checked="checked"' : '', " /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strconnlimit']}</th>\n"; - echo "\t\t<td class=\"data1\"><input size=\"4\" name=\"formConnLimit\" value=\"", htmlspecialchars($_POST['formConnLimit']), "\" /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strexpires']}</th>\n"; - echo "\t\t<td class=\"data1\"><input size=\"23\" name=\"formExpires\" value=\"", htmlspecialchars($_POST['formExpires']), "\" /></td>\n\t</tr>\n"; - - if (!isset($_POST['memberof'])) { - $memberof = $data->getMemberOf($_REQUEST['rolename']); - if ($memberof->recordCount() > 0) { - $i = 0; - while (!$memberof->EOF) { - $_POST['memberof'][$i++] = $memberof->fields['rolname']; - $memberof->moveNext(); - } - } else { - $_POST['memberof'] = []; - } - - $memberofold = implode(',', $_POST['memberof']); - } - if (!isset($_POST['members'])) { - $members = $data->getMembers($_REQUEST['rolename']); - if ($members->recordCount() > 0) { - $i = 0; - while (!$members->EOF) { - $_POST['members'][$i++] = $members->fields['rolname']; - $members->moveNext(); - } - } else { - $_POST['members'] = []; - } - - $membersold = implode(',', $_POST['members']); - } - if (!isset($_POST['adminmembers'])) { - $adminmembers = $data->getMembers($_REQUEST['rolename'], 't'); - if ($adminmembers->recordCount() > 0) { - $i = 0; - while (!$adminmembers->EOF) { - $_POST['adminmembers'][$i++] = $adminmembers->fields['rolname']; - $adminmembers->moveNext(); - } - } else { - $_POST['adminmembers'] = []; - } - - $adminmembersold = implode(',', $_POST['adminmembers']); - } - - $roles = $data->getRoles($_REQUEST['rolename']); - if ($roles->recordCount() > 0) { - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strmemberof']}</th>\n"; - echo "\t\t<td class=\"data\">\n"; - echo "\t\t\t<select name=\"memberof[]\" multiple=\"multiple\" size=\"", min(20, $roles->recordCount()), "\">\n"; - while (!$roles->EOF) { - $rolename = $roles->fields['rolname']; - echo "\t\t\t\t<option value=\"{$rolename}\"", - (in_array($rolename, $_POST['memberof']) ? ' selected="selected"' : ''), ">", $misc->printVal($rolename), "</option>\n"; - $roles->moveNext(); - } - echo "\t\t\t</select>\n"; - echo "\t\t</td>\n\t</tr>\n"; - - $roles->moveFirst(); - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strmembers']}</th>\n"; - echo "\t\t<td class=\"data\">\n"; - echo "\t\t\t<select name=\"members[]\" multiple=\"multiple\" size=\"", min(20, $roles->recordCount()), "\">\n"; - while (!$roles->EOF) { - $rolename = $roles->fields['rolname']; - echo "\t\t\t\t<option value=\"{$rolename}\"", - (in_array($rolename, $_POST['members']) ? ' selected="selected"' : ''), ">", $misc->printVal($rolename), "</option>\n"; - $roles->moveNext(); - } - echo "\t\t\t</select>\n"; - echo "\t\t</td>\n\t</tr>\n"; - - $roles->moveFirst(); - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['stradminmembers']}</th>\n"; - echo "\t\t<td class=\"data\">\n"; - echo "\t\t\t<select name=\"adminmembers[]\" multiple=\"multiple\" size=\"", min(20, $roles->recordCount()), "\">\n"; - while (!$roles->EOF) { - $rolename = $roles->fields['rolname']; - echo "\t\t\t\t<option value=\"{$rolename}\"", - (in_array($rolename, $_POST['adminmembers']) ? ' selected="selected"' : ''), ">", $misc->printVal($rolename), "</option>\n"; - $roles->moveNext(); - } - echo "\t\t\t</select>\n"; - echo "\t\t</td>\n\t</tr>\n"; - } - echo "</table>\n"; - - echo "<p><input type=\"hidden\" name=\"action\" value=\"save_alter\" />\n"; - echo "<input type=\"hidden\" name=\"rolename\" value=\"", htmlspecialchars($_REQUEST['rolename']), "\" />\n"; - echo "<input type=\"hidden\" name=\"memberofold\" value=\"", isset($_POST['memberofold']) ? $_POST['memberofold'] : htmlspecialchars($memberofold), "\" />\n"; - echo "<input type=\"hidden\" name=\"membersold\" value=\"", isset($_POST['membersold']) ? $_POST['membersold'] : htmlspecialchars($membersold), "\" />\n"; - echo "<input type=\"hidden\" name=\"adminmembersold\" value=\"", isset($_POST['adminmembersold']) ? $_POST['adminmembersold'] : htmlspecialchars($adminmembersold), "\" />\n"; - echo $misc->form; - echo "<input type=\"submit\" name=\"alter\" value=\"{$lang['stralter']}\" />\n"; - echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; - echo "</form>\n"; - } else { - echo "<p>{$lang['strnodata']}</p>\n"; - } - -} - -/** - * Function to save after editing a role - */ -function doSaveAlter() { - global $data, $lang; - - if (!isset($_POST['memberof'])) { - $_POST['memberof'] = []; - } - - if (!isset($_POST['members'])) { - $_POST['members'] = []; - } - - if (!isset($_POST['adminmembers'])) { - $_POST['adminmembers'] = []; - } - - // Check name and password - if (isset($_POST['formNewRoleName']) && $_POST['formNewRoleName'] == '') { - doAlter($lang['strroleneedsname']); - } else if ($_POST['formPassword'] != $_POST['formConfirm']) { - doAlter($lang['strpasswordconfirm']); - } else { - if (isset($_POST['formNewRoleName'])) { - $status = $data->setRenameRole($_POST['rolename'], $_POST['formPassword'], isset($_POST['formSuper']), isset($_POST['formCreateDB']), isset($_POST['formCreateRole']), isset($_POST['formInherits']), isset($_POST['formCanLogin']), $_POST['formConnLimit'], $_POST['formExpires'], $_POST['memberof'], $_POST['members'], $_POST['adminmembers'], $_POST['memberofold'], $_POST['membersold'], $_POST['adminmembersold'], $_POST['formNewRoleName']); - } else { - $status = $data->setRole($_POST['rolename'], $_POST['formPassword'], isset($_POST['formSuper']), isset($_POST['formCreateDB']), isset($_POST['formCreateRole']), isset($_POST['formInherits']), isset($_POST['formCanLogin']), $_POST['formConnLimit'], $_POST['formExpires'], $_POST['memberof'], $_POST['members'], $_POST['adminmembers'], $_POST['memberofold'], $_POST['membersold'], $_POST['adminmembersold']); - } - - if ($status == 0) { - doDefault($lang['strrolealtered']); - } else { - doAlter($lang['strrolealteredbad']); - } - - } -} - -/** - * Show confirmation of drop a role and perform actual drop - */ -function doDrop($confirm) { - global $data, $misc; - global $lang; - - if ($confirm) { - $misc->printTrail('role'); - $misc->printTitle($lang['strdroprole'], 'pg.role.drop'); - - echo "<p>", sprintf($lang['strconfdroprole'], $misc->printVal($_REQUEST['rolename'])), "</p>\n"; - - echo "<form action=\"/src/views/roles.php\" method=\"post\">\n"; - echo "<p><input type=\"hidden\" name=\"action\" value=\"drop\" />\n"; - echo "<input type=\"hidden\" name=\"rolename\" value=\"", htmlspecialchars($_REQUEST['rolename']), "\" />\n"; - echo $misc->form; - echo "<input type=\"submit\" name=\"drop\" value=\"{$lang['strdrop']}\" />\n"; - echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; - echo "</form>\n"; - } else { - $status = $data->dropRole($_REQUEST['rolename']); - if ($status == 0) { - doDefault($lang['strroledropped']); - } else { - doDefault($lang['strroledroppedbad']); - } - - } -} - -/** - * Show the properties of a role - */ -function doProperties($msg = '') { - global $data, $misc; - global $lang; - - $misc->printTrail('role'); - $misc->printTitle($lang['strproperties'], 'pg.role'); - $misc->printMsg($msg); - - $roledata = $data->getRole($_REQUEST['rolename']); - if ($roledata->recordCount() > 0) { - $roledata->fields['rolsuper'] = $data->phpBool($roledata->fields['rolsuper']); - $roledata->fields['rolcreatedb'] = $data->phpBool($roledata->fields['rolcreatedb']); - $roledata->fields['rolcreaterole'] = $data->phpBool($roledata->fields['rolcreaterole']); - $roledata->fields['rolinherit'] = $data->phpBool($roledata->fields['rolinherit']); - $roledata->fields['rolcanlogin'] = $data->phpBool($roledata->fields['rolcanlogin']); - - echo "<table>\n"; - echo "\t<tr>\n\t\t<th class=\"data\" style=\"width: 130px\">Description</th>\n"; - echo "\t\t<th class=\"data\" style=\"width: 120\">Value</th>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<td class=\"data1\">{$lang['strname']}</td>\n"; - echo "\t\t<td class=\"data1\">", htmlspecialchars($_REQUEST['rolename']), "</td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<td class=\"data2\">{$lang['strsuper']}</td>\n"; - echo "\t\t<td class=\"data2\">", (($roledata->fields['rolsuper']) ? $lang['stryes'] : $lang['strno']), "</td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<td class=\"data1\">{$lang['strcreatedb']}</td>\n"; - echo "\t\t<td class=\"data1\">", (($roledata->fields['rolcreatedb']) ? $lang['stryes'] : $lang['strno']), "</td>\n"; - echo "\t<tr>\n\t\t<td class=\"data2\">{$lang['strcancreaterole']}</td>\n"; - echo "\t\t<td class=\"data2\">", (($roledata->fields['rolcreaterole']) ? $lang['stryes'] : $lang['strno']), "</td>\n"; - echo "\t<tr>\n\t\t<td class=\"data1\">{$lang['strinheritsprivs']}</td>\n"; - echo "\t\t<td class=\"data1\">", (($roledata->fields['rolinherit']) ? $lang['stryes'] : $lang['strno']), "</td>\n"; - echo "\t<tr>\n\t\t<td class=\"data2\">{$lang['strcanlogin']}</td>\n"; - echo "\t\t<td class=\"data2\">", (($roledata->fields['rolcanlogin']) ? $lang['stryes'] : $lang['strno']), "</td>\n"; - echo "\t<tr>\n\t\t<td class=\"data1\">{$lang['strconnlimit']}</td>\n"; - echo "\t\t<td class=\"data1\">", ($roledata->fields['rolconnlimit'] == '-1' ? $lang['strnolimit'] : $misc->printVal($roledata->fields['rolconnlimit'])), "</td>\n"; - echo "\t<tr>\n\t\t<td class=\"data2\">{$lang['strexpires']}</td>\n"; - echo "\t\t<td class=\"data2\">", ($roledata->fields['rolvaliduntil'] == 'infinity' || is_null($roledata->fields['rolvaliduntil']) ? $lang['strnever'] : $misc->printVal($roledata->fields['rolvaliduntil'])), "</td>\n"; - echo "\t<tr>\n\t\t<td class=\"data1\">{$lang['strsessiondefaults']}</td>\n"; - echo "\t\t<td class=\"data1\">", $misc->printVal($roledata->fields['rolconfig']), "</td>\n"; - echo "\t<tr>\n\t\t<td class=\"data2\">{$lang['strmemberof']}</td>\n"; - echo "\t\t<td class=\"data2\">"; - $memberof = $data->getMemberOf($_REQUEST['rolename']); - if ($memberof->recordCount() > 0) { - while (!$memberof->EOF) { - echo $misc->printVal($memberof->fields['rolname']), "<br />\n"; - $memberof->moveNext(); - } - } - echo "</td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<td class=\"data1\">{$lang['strmembers']}</td>\n"; - echo "\t\t<td class=\"data1\">"; - $members = $data->getMembers($_REQUEST['rolename']); - if ($members->recordCount() > 0) { - while (!$members->EOF) { - echo $misc->printVal($members->fields['rolname']), "<br />\n"; - $members->moveNext(); - } - } - echo "</td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<td class=\"data2\">{$lang['stradminmembers']}</td>\n"; - echo "\t\t<td class=\"data2\">"; - $adminmembers = $data->getMembers($_REQUEST['rolename'], 't'); - if ($adminmembers->recordCount() > 0) { - while (!$adminmembers->EOF) { - echo $misc->printVal($adminmembers->fields['rolname']), "<br />\n"; - $adminmembers->moveNext(); - } - } - echo "</td>\n\t</tr>\n"; - echo "</table>\n"; - } else { - echo "<p>{$lang['strnodata']}</p>\n"; - } - - $navlinks = [ - 'showall' => [ - 'attr' => [ - 'href' => [ - 'url' => 'roles.php', - 'urlvars' => [ - 'server' => $_REQUEST['server'], - ], - ], - ], - 'content' => $lang['strshowallroles'], - ], - 'alter' => [ - 'attr' => [ - 'href' => [ - 'url' => 'roles.php', - 'urlvars' => [ - 'action' => 'alter', - 'server' => $_REQUEST['server'], - 'rolename' => $_REQUEST['rolename'], - ], - ], - ], - 'content' => $lang['stralter'], - ], - 'drop' => [ - 'attr' => [ - 'href' => [ - 'url' => 'roles.php', - 'urlvars' => [ - 'action' => 'confirm_drop', - 'server' => $_REQUEST['server'], - 'rolename' => $_REQUEST['rolename'], - ], - ], - ], - 'content' => $lang['strdrop'], - ], - ]; - - $misc->printNavLinks($navlinks, 'roles-properties', get_defined_vars()); -} - -/** - * If a role is not a superuser role, then we have an 'account management' - * page for change his password, etc. We don't prevent them from - * messing with the URL to gain access to other role admin stuff, because - * the PostgreSQL permissions will prevent them changing anything anyway. - */ -function doAccount($msg = '') { - global $data, $misc; - global $lang; - - $server_info = $misc->getServerInfo(); - - $roledata = $data->getRole($server_info['username']); - $_REQUEST['rolename'] = $server_info['username']; - - $misc->printTrail('role'); - $misc->printTabs('server', 'account'); - $misc->printMsg($msg); - - if ($roledata->recordCount() > 0) { - $roledata->fields['rolsuper'] = $data->phpBool($roledata->fields['rolsuper']); - $roledata->fields['rolcreatedb'] = $data->phpBool($roledata->fields['rolcreatedb']); - $roledata->fields['rolcreaterole'] = $data->phpBool($roledata->fields['rolcreaterole']); - $roledata->fields['rolinherit'] = $data->phpBool($roledata->fields['rolinherit']); - echo "<table>\n"; - echo "\t<tr>\n\t\t<th class=\"data\">{$lang['strname']}</th>\n"; - echo "\t\t<th class=\"data\">{$lang['strsuper']}</th>\n"; - echo "\t\t<th class=\"data\">{$lang['strcreatedb']}</th>\n"; - echo "\t\t<th class=\"data\">{$lang['strcancreaterole']}</th>\n"; - echo "\t\t<th class=\"data\">{$lang['strinheritsprivs']}</th>\n"; - echo "\t\t<th class=\"data\">{$lang['strconnlimit']}</th>\n"; - echo "\t\t<th class=\"data\">{$lang['strexpires']}</th>\n"; - echo "\t\t<th class=\"data\">{$lang['strsessiondefaults']}</th>\n"; - echo "\t</tr>\n"; - echo "\t<tr>\n\t\t<td class=\"data1\">", $misc->printVal($roledata->fields['rolname']), "</td>\n"; - echo "\t\t<td class=\"data1\">", $misc->printVal($roledata->fields['rolsuper'], 'yesno'), "</td>\n"; - echo "\t\t<td class=\"data1\">", $misc->printVal($roledata->fields['rolcreatedb'], 'yesno'), "</td>\n"; - echo "\t\t<td class=\"data1\">", $misc->printVal($roledata->fields['rolcreaterole'], 'yesno'), "</td>\n"; - echo "\t\t<td class=\"data1\">", $misc->printVal($roledata->fields['rolinherit'], 'yesno'), "</td>\n"; - echo "\t\t<td class=\"data1\">", ($roledata->fields['rolconnlimit'] == '-1' ? $lang['strnolimit'] : $misc->printVal($roledata->fields['rolconnlimit'])), "</td>\n"; - echo "\t\t<td class=\"data1\">", ($roledata->fields['rolvaliduntil'] == 'infinity' || is_null($roledata->fields['rolvaliduntil']) ? $lang['strnever'] : $misc->printVal($roledata->fields['rolvaliduntil'])), "</td>\n"; - echo "\t\t<td class=\"data1\">", $misc->printVal($roledata->fields['rolconfig']), "</td>\n"; - echo "\t</tr>\n</table>\n"; - } else { - echo "<p>{$lang['strnodata']}</p>\n"; - } - - $misc->printNavLinks(['changepassword' => [ - 'attr' => [ - 'href' => [ - 'url' => 'roles.php', - 'urlvars' => [ - 'action' => 'confchangepassword', - 'server' => $_REQUEST['server'], - ], - ], - ], - 'content' => $lang['strchangepassword'], - ]], 'roles-account', get_defined_vars()); -} - -/** - * Show confirmation of change password and actually change password - */ -function doChangePassword($confirm, $msg = '') { - global $data, $misc; - global $lang, $conf; - - $server_info = $misc->getServerInfo(); - - if ($confirm) { - $_REQUEST['rolename'] = $server_info['username']; - $misc->printTrail('role'); - $misc->printTitle($lang['strchangepassword'], 'pg.role.alter'); - $misc->printMsg($msg); - - if (!isset($_POST['password'])) { - $_POST['password'] = ''; - } - - if (!isset($_POST['confirm'])) { - $_POST['confirm'] = ''; - } - - echo "<form action=\"/src/views/roles.php\" method=\"post\">\n"; - echo "<table>\n"; - echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strpassword']}</th>\n"; - echo "\t\t<td><input type=\"password\" name=\"password\" size=\"32\" value=\"", - htmlspecialchars($_POST['password']), "\" /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strconfirm']}</th>\n"; - echo "\t\t<td><input type=\"password\" name=\"confirm\" size=\"32\" value=\"\" /></td>\n\t</tr>\n"; - echo "</table>\n"; - echo "<p><input type=\"hidden\" name=\"action\" value=\"changepassword\" />\n"; - echo $misc->form; - echo "<input type=\"submit\" name=\"ok\" value=\"{$lang['strok']}\" />\n"; - echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n"; - echo "</p></form>\n"; - } else { - // Check that password is minimum length - if (strlen($_POST['password']) < $conf['min_password_length']) { - doChangePassword(true, $lang['strpasswordshort']); - } - - // Check that password matches confirmation password - elseif ($_POST['password'] != $_POST['confirm']) { - doChangePassword(true, $lang['strpasswordconfirm']); - } else { - $status = $data->changePassword($server_info['username'], $_POST['password']); - if ($status == 0) { - doAccount($lang['strpasswordchanged']); - } else { - doAccount($lang['strpasswordchangedbad']); - } - - } - } -} - -/** - * Show default list of roles in the database - */ -function doDefault($msg = '') { - global $data, $misc; - global $lang; - - function renderRoleConnLimit($val) { - global $lang; - return $val == '-1' ? $lang['strnolimit'] : htmlspecialchars($val); - } - - function renderRoleExpires($val) { - global $lang; - return $val == 'infinity' ? $lang['strnever'] : htmlspecialchars($val); - } - - $misc->printTrail('server'); - $misc->printTabs('server', 'roles'); - $misc->printMsg($msg); - - $roles = $data->getRoles(); - - $columns = [ - 'role' => [ - 'title' => $lang['strrole'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('rolname'), - 'url' => "/redirect/role?action=properties&{$misc->href}&", - 'vars' => ['rolename' => 'rolname'], - ], - 'superuser' => [ - 'title' => $lang['strsuper'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('rolsuper'), - 'type' => 'yesno', - ], - 'createdb' => [ - 'title' => $lang['strcreatedb'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('rolcreatedb'), - 'type' => 'yesno', - ], - 'createrole' => [ - 'title' => $lang['strcancreaterole'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('rolcreaterole'), - 'type' => 'yesno', - ], - 'inherits' => [ - 'title' => $lang['strinheritsprivs'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('rolinherit'), - 'type' => 'yesno', - ], - 'canloging' => [ - 'title' => $lang['strcanlogin'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('rolcanlogin'), - 'type' => 'yesno', - ], - 'connlimit' => [ - 'title' => $lang['strconnlimit'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('rolconnlimit'), - 'type' => 'callback', - 'params' => ['function' => 'renderRoleConnLimit'], - ], - 'expires' => [ - 'title' => $lang['strexpires'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('rolvaliduntil'), - 'type' => 'callback', - 'params' => ['function' => 'renderRoleExpires', 'null' => $lang['strnever']], - ], - 'actions' => [ - 'title' => $lang['stractions'], - ], - ]; - - $actions = [ - 'alter' => [ - 'content' => $lang['stralter'], - 'attr' => [ - 'href' => [ - 'url' => 'roles.php', - 'urlvars' => [ - 'action' => 'alter', - 'rolename' => \PHPPgAdmin\Decorators\Decorator::field('rolname'), - ], - ], - ], - ], - 'drop' => [ - 'content' => $lang['strdrop'], - 'attr' => [ - 'href' => [ - 'url' => 'roles.php', - 'urlvars' => [ - 'action' => 'confirm_drop', - 'rolename' => \PHPPgAdmin\Decorators\Decorator::field('rolname'), - ], - ], - ], - ], - ]; - - echo $misc->printTable($roles, $columns, $actions, 'roles-roles', $lang['strnoroles']); - - $navlinks = [ - 'create' => [ - 'attr' => [ - 'href' => [ - 'url' => 'roles.php', - 'urlvars' => [ - 'action' => 'create', - 'server' => $_REQUEST['server'], - ], - ], - ], - 'content' => $lang['strcreaterole'], - ], - ]; - $misc->printNavLinks($navlinks, 'roles-roles', get_defined_vars()); -} - -$misc->printHeader($lang['strroles']); -$misc->printBody(); - -switch ($action) { - case 'create': - doCreate(); - break; - case 'save_create': - if (isset($_POST['create'])) { - doSaveCreate(); - } else { - doDefault(); - } - - break; - case 'alter': - doAlter(); - break; - case 'save_alter': - if (isset($_POST['alter'])) { - doSaveAlter(); - } else { - doDefault(); - } - - break; - case 'confirm_drop': - doDrop(true); - break; - case 'drop': - if (isset($_POST['drop'])) { - doDrop(false); - } else { - doDefault(); - } - - break; - case 'properties': - doProperties(); - break; - case 'confchangepassword': - doChangePassword(true); - break; - case 'changepassword': - if (isset($_REQUEST['ok'])) { - doChangePassword(false); - } else { - doAccount(); - } - - break; - case 'account': - doAccount(); - break; - default: - doDefault(); -} - -$misc->printFooter(); +$roles_controller->render(); diff --git a/src/views/rules.php b/src/views/rules.php index 24186352..3557a050 100644 --- a/src/views/rules.php +++ b/src/views/rules.php @@ -10,37 +10,4 @@ require_once '../lib.inc.php'; $rule_controller = new \PHPPgAdmin\Controller\RuleController($container); - -// Different header if we're view rules or table rules -$misc->printHeader($_REQUEST[$_REQUEST['subject']] . ' - ' . $lang['strrules']); -$misc->printBody(); - -switch ($action) { - case 'create_rule': - $rule_controller->createRule(true); - break; - case 'save_create_rule': - if (isset($_POST['cancel'])) { - $rule_controller->doDefault(); - } else { - $rule_controller->createRule(false); - } - - break; - case 'drop': - if (isset($_POST['yes'])) { - $rule_controller->doDrop(false); - } else { - $rule_controller->doDefault(); - } - - break; - case 'confirm_drop': - $rule_controller->doDrop(true); - break; - default: - $rule_controller->doDefault(); - break; -} - -$misc->printFooter(); +$rule_controller->render();
\ No newline at end of file diff --git a/src/views/schemas.php b/src/views/schemas.php index 8bb271e8..2afb4f6e 100755 --- a/src/views/schemas.php +++ b/src/views/schemas.php @@ -11,44 +11,4 @@ require_once '../lib.inc.php'; $schema_controller = new \PHPPgAdmin\Controller\SchemaController($container); -$misc->printHeader($lang['strschemas']); -$misc->printBody(); - -if (isset($_POST['cancel'])) { - $action = ''; -} - -switch ($action) { - case 'create': - if (isset($_POST['create'])) { - $schema_controller->doSaveCreate(); - } else { - $schema_controller->doCreate(); - } - - break; - case 'alter': - if (isset($_POST['alter'])) { - $schema_controller->doSaveAlter(); - } else { - $schema_controller->doAlter(); - } - - break; - case 'drop': - if (isset($_POST['drop'])) { - $schema_controller->doDrop(false); - } else { - $schema_controller->doDrop(true); - } - - break; - case 'export': - $schema_controller->doExport(); - break; - default: - $schema_controller->doDefault(); - break; -} - -$misc->printFooter(); +$schema_controller->render(); diff --git a/src/views/sequences.php b/src/views/sequences.php index 63ef6d1a..342262c3 100644 --- a/src/views/sequences.php +++ b/src/views/sequences.php @@ -10,72 +10,4 @@ require_once '../lib.inc.php'; $sequence_controller = new \PHPPgAdmin\Controller\SequenceController($container); - -// Print header -$misc->printHeader($lang['strsequences']); -$misc->printBody(); - -switch ($action) { - case 'create': - $sequence_controller->doCreateSequence(); - break; - case 'save_create_sequence': - if (isset($_POST['create'])) { - $sequence_controller->doSaveCreateSequence(); - } else { - $sequence_controller->doDefault(); - } - - break; - case 'properties': - $sequence_controller->doProperties(); - break; - case 'drop': - if (isset($_POST['drop'])) { - $sequence_controller->doDrop(false); - } else { - $sequence_controller->doDefault(); - } - - break; - case 'confirm_drop': - $sequence_controller->doDrop(true); - break; - case 'restart': - $sequence_controller->doRestart(); - break; - case 'reset': - $sequence_controller->doReset(); - break; - case 'nextval': - $sequence_controller->doNextval(); - break; - case 'setval': - if (isset($_POST['setval'])) { - $sequence_controller->doSaveSetval(); - } else { - $sequence_controller->doDefault(); - } - - break; - case 'confirm_setval': - $sequence_controller->doSetval(); - break; - case 'alter': - if (isset($_POST['alter'])) { - $sequence_controller->doSaveAlter(); - } else { - $sequence_controller->doDefault(); - } - - break; - case 'confirm_alter': - $sequence_controller->doAlter(); - break; - default: - $sequence_controller->doDefault(); - break; -} - -// Print footer -$misc->printFooter(); +$sequence_controller->render();
\ No newline at end of file diff --git a/src/views/servers.php b/src/views/servers.php index fa027788..ef069ed9 100644 --- a/src/views/servers.php +++ b/src/views/servers.php @@ -6,106 +6,8 @@ * $Id: servers.php,v 1.12 2008/02/18 22:20:26 ioguix Exp $ */ -function doLogout($container) { +require_once '../lib.inc.php'; - $lang = $container->get('lang'); - $misc = $container->get('misc'); - $plugin_manager = $container->get('plugin_manager'); +$server_controller = new \PHPPgAdmin\Controller\ServerController($container); - $plugin_manager->do_hook('logout', $_REQUEST['logoutServer']); - - $server_info = $misc->getServerInfo($_REQUEST['logoutServer']); - $misc->setServerInfo(null, null, $_REQUEST['logoutServer']); - - unset($_SESSION['sharedUsername'], $_SESSION['sharedPassword']); - - $misc->setReloadBrowser(true); - - return sprintf($lang['strlogoutmsg'], $server_info['desc']); - -} - -function doDefault($container, $msg = '') { - - $lang = $container->get('lang'); - $conf = $container->get('conf'); - $misc = $container->get('misc'); - - $default_html = ''; - - $default_html .= $misc->printTabs('root', 'servers', false); - $default_html .= $misc->printMsg($msg, false); - - $group = isset($_GET['group']) ? $_GET['group'] : false; - - $groups = $misc->getServersGroups(true, $group); - - $columns = [ - 'group' => [ - 'title' => $lang['strgroup'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('desc'), - 'url' => 'servers.php?', - 'vars' => ['group' => 'id'], - ], - ]; - $actions = []; - - if (($group !== false) and (isset($conf['srv_groups'][$group])) and ($groups->recordCount() > 0)) { - $default_html .= $misc->printTitle(sprintf($lang['strgroupgroups'], htmlentities($conf['srv_groups'][$group]['desc'], ENT_QUOTES, 'UTF-8')), null, false); - } - - $default_html .= $misc->printTable($groups, $columns, $actions, 'servers-servers'); - - $servers = $misc->getServers(true, $group); - - function svPre(&$rowdata, $actions) { - $actions['logout']['disable'] = empty($rowdata->fields['username']); - return $actions; - } - - $columns = [ - 'server' => [ - 'title' => $lang['strserver'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('desc'), - 'url' => "/redirect/server?", - 'vars' => ['server' => 'id'], - ], - 'host' => [ - 'title' => $lang['strhost'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('host'), - ], - 'port' => [ - 'title' => $lang['strport'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('port'), - ], - 'username' => [ - 'title' => $lang['strusername'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('username'), - ], - 'actions' => [ - 'title' => $lang['stractions'], - ], - ]; - - $actions = [ - 'logout' => [ - 'content' => $lang['strlogout'], - 'attr' => [ - 'href' => [ - 'url' => '/src/views/servers/logout', - 'urlvars' => [ - 'logoutServer' => \PHPPgAdmin\Decorators\Decorator::field('id'), - ], - ], - ], - ], - ]; - - if (($group !== false) and isset($conf['srv_groups'][$group])) { - $default_html .= $misc->printTitle(sprintf($lang['strgroupservers'], htmlentities($conf['srv_groups'][$group]['desc'], ENT_QUOTES, 'UTF-8')), null, false); - $actions['logout']['attr']['href']['urlvars']['group'] = $group; - } - - $default_html .= $misc->printTable($servers, $columns, $actions, 'servers-servers', $lang['strnoobjects'], 'svPre'); - return $default_html; -} +$server_controller->render();
\ No newline at end of file diff --git a/src/views/sql.php b/src/views/sql.php index 33286356..098b03a0 100644 --- a/src/views/sql.php +++ b/src/views/sql.php @@ -9,263 +9,9 @@ * $Id: sql.php,v 1.43 2008/01/10 20:19:27 xzilla Exp $ */ -global $lang; - -// Prevent timeouts on large exports (non-safe mode only) -if (!ini_get('safe_mode')) { - set_time_limit(0); -} - // Include application functions require_once '../lib.inc.php'; -/** - * This is a callback function to display the result of each separate query - * @param ADORecordSet $rs The recordset returned by the script execetor - */ -function sqlCallback($query, $rs, $lineno) { - global $data, $misc, $lang, $_connection; - // Check if $rs is false, if so then there was a fatal error - if ($rs === false) { - echo htmlspecialchars($_FILES['script']['name']), ':', $lineno, ': ', nl2br(htmlspecialchars($_connection->getLastError())), "<br/>\n"; - } else { - // Print query results - switch (pg_result_status($rs)) { - case PGSQL_TUPLES_OK: - // If rows returned, then display the results - $num_fields = pg_numfields($rs); - echo "<p><table>\n<tr>"; - for ($k = 0; $k < $num_fields; $k++) { - echo "<th class=\"data\">", $misc->printVal(pg_fieldname($rs, $k)), "</th>"; - } - - $i = 0; - $row = pg_fetch_row($rs); - while ($row !== false) { - $id = (($i % 2) == 0 ? '1' : '2'); - echo "<tr class=\"data{$id}\">\n"; - foreach ($row as $k => $v) { - echo "<td style=\"white-space:nowrap;\">", $misc->printVal($v, pg_fieldtype($rs, $k), ['null' => true]), "</td>"; - } - echo "</tr>\n"; - $row = pg_fetch_row($rs); - $i++; - } - ; - echo "</table><br/>\n"; - echo $i, " {$lang['strrows']}</p>\n"; - break; - case PGSQL_COMMAND_OK: - // If we have the command completion tag - if (version_compare(phpversion(), '4.3', '>=')) { - echo htmlspecialchars(pg_result_status($rs, PGSQL_STATUS_STRING)), "<br/>\n"; - } - // Otherwise if any rows have been affected - elseif ($data->conn->Affected_Rows() > 0) { - echo $data->conn->Affected_Rows(), " {$lang['strrowsaff']}<br/>\n"; - } - // Otherwise output nothing... - break; - case PGSQL_EMPTY_QUERY: - break; - default: - break; - } - } -} - -// We need to store the query in a session for editing purposes -// We avoid GPC vars to avoid truncating long queries -if (isset($_REQUEST['subject']) && $_REQUEST['subject'] == 'history') { - // Or maybe we came from the history popup - $_SESSION['sqlquery'] = $_SESSION['history'][$_REQUEST['server']][$_REQUEST['database']][$_GET['queryid']]['query']; -} elseif (isset($_POST['query'])) { - // Or maybe we came from an sql form - $_SESSION['sqlquery'] = $_POST['query']; -} else { - echo "could not find the query!!"; -} - -// Pagination maybe set by a get link that has it as FALSE, -// if that's the case, unset the variable. - -if (isset($_REQUEST['paginate']) && $_REQUEST['paginate'] == 'f') { - unset($_REQUEST['paginate']); - unset($_POST['paginate']); - unset($_GET['paginate']); -} -// Check to see if pagination has been specified. In that case, send to display -// script for pagination -/* if a file is given or the request is an explain, do not paginate */ -if (isset($_REQUEST['paginate']) && !(isset($_FILES['script']) && $_FILES['script']['size'] > 0) - && (preg_match('/^\s*explain/i', $_SESSION['sqlquery']) == 0)) { - include './display.php'; - exit; -} - -$subject = isset($_REQUEST['subject']) ? $_REQUEST['subject'] : ''; -$misc->printHeader($lang['strqueryresults']); -$misc->printBody(); -$misc->printTrail('database'); -$misc->printTitle($lang['strqueryresults']); - -// Set the schema search path -if (isset($_REQUEST['search_path'])) { - if ($data->setSearchPath(array_map('trim', explode(',', $_REQUEST['search_path']))) != 0) { - $misc->printFooter(); - exit; - } -} - -// May as well try to time the query -if (function_exists('microtime')) { - list($usec, $sec) = explode(' ', microtime()); - $start_time = ((float) $usec + (float) $sec); -} else { - $start_time = null; -} - -// Execute the query. If it's a script upload, special handling is necessary -if (isset($_FILES['script']) && $_FILES['script']['size'] > 0) { - $data->executeScript('script', 'sqlCallback'); -} else { - // Set fetch mode to NUM so that duplicate field names are properly returned - $data->conn->setFetchMode(ADODB_FETCH_NUM); - $rs = $data->conn->Execute($_SESSION['sqlquery']); - - // $rs will only be an object if there is no error - if (is_object($rs)) { - // Request was run, saving it in history - if (!isset($_REQUEST['nohistory'])) { - $misc->saveScriptHistory($_SESSION['sqlquery']); - } - - // Now, depending on what happened do various things - - // First, if rows returned, then display the results - if ($rs->recordCount() > 0) { - echo "<table>\n<tr>"; - foreach ($rs->fields as $k => $v) { - $finfo = $rs->fetchField($k); - echo "<th class=\"data\">", $misc->printVal($finfo->name), "</th>"; - } - echo "</tr>\n"; - $i = 0; - while (!$rs->EOF) { - $id = (($i % 2) == 0 ? '1' : '2'); - echo "<tr class=\"data{$id}\">\n"; - foreach ($rs->fields as $k => $v) { - $finfo = $rs->fetchField($k); - echo "<td style=\"white-space:nowrap;\">", $misc->printVal($v, $finfo->type, ['null' => true]), "</td>"; - } - echo "</tr>\n"; - $rs->moveNext(); - $i++; - } - echo "</table>\n"; - echo "<p>", $rs->recordCount(), " {$lang['strrows']}</p>\n"; - } - // Otherwise if any rows have been affected - elseif ($data->conn->Affected_Rows() > 0) { - echo "<p>", $data->conn->Affected_Rows(), " {$lang['strrowsaff']}</p>\n"; - } - // Otherwise nodata to print - else { - echo '<p>', $lang['strnodata'], "</p>\n"; - } - - } -} - -// May as well try to time the query -if ($start_time !== null) { - list($usec, $sec) = explode(' ', microtime()); - $end_time = ((float) $usec + (float) $sec); - // Get duration in milliseconds, round to 3dp's - $duration = number_format(($end_time - $start_time) * 1000, 3); -} else { - $duration = null; -} - -// Reload the browser as we may have made schema changes -$_reload_browser = true; - -// Display duration if we know it -if ($duration !== null) { - echo "<p>", sprintf($lang['strruntime'], $duration), "</p>\n"; -} - -echo "<p>{$lang['strsqlexecuted']}</p>\n"; - -$navlinks = []; -$fields = [ - 'server' => $_REQUEST['server'], - 'database' => $_REQUEST['database'], -]; - -if (isset($_REQUEST['schema'])) { - $fields['schema'] = $_REQUEST['schema']; -} - -// Return -if (isset($_REQUEST['return'])) { - $urlvars = $misc->getSubjectParams($_REQUEST['return']); - $navlinks['back'] = [ - 'attr' => [ - 'href' => [ - 'url' => $urlvars['url'], - 'urlvars' => $urlvars['params'], - ], - ], - 'content' => $lang['strback'], - ]; -} - -// Edit -$navlinks['alter'] = [ - 'attr' => [ - 'href' => [ - 'url' => 'database.php', - 'urlvars' => array_merge($fields, [ - 'action' => 'sql', - ]), - ], - ], - 'content' => $lang['streditsql'], -]; - -// Create view and download -if (isset($_SESSION['sqlquery']) && isset($rs) && is_object($rs) && $rs->recordCount() > 0) { - // Report views don't set a schema, so we need to disable create view in that case - if (isset($_REQUEST['schema'])) { - $navlinks['createview'] = [ - 'attr' => [ - 'href' => [ - 'url' => 'views.php', - 'urlvars' => array_merge($fields, [ - 'action' => 'create', - ]), - ], - ], - 'content' => $lang['strcreateview'], - ]; - } - - if (isset($_REQUEST['search_path'])) { - $fields['search_path'] = $_REQUEST['search_path']; - } - - $navlinks['download'] = [ - 'attr' => [ - 'href' => [ - 'url' => 'dataexport.php', - 'urlvars' => $fields, - ], - ], - 'content' => $lang['strdownload'], - ]; -} - -$misc->printNavLinks($navlinks, 'sql-form', get_defined_vars()); +$sqlquery_controller = new \PHPPgAdmin\Controller\SQLQueryController($container); -$misc->printFooter(); +$sqlquery_controller->render(); diff --git a/src/views/sqledit.php b/src/views/sqledit.php index 2fa2854e..3689c207 100644 --- a/src/views/sqledit.php +++ b/src/views/sqledit.php @@ -8,148 +8,8 @@ // Include application functions //require_once '../lib.inc.php'; +require_once '../lib.inc.php'; -/** - * Private function to display server and list of databases - */ -function _printConnection($container, $action) { - - $lang = $container->get('lang'); - $conf = $container->get('conf'); - $misc = $container->get('misc'); - - $data = $misc->getDatabaseAccessor(); - - // The javascript action on the select box reloads the - // popup whenever the server or database is changed. - // This ensures that the correct page encoding is used. - $onchange = "onchange=\"location.href='/sqledit/" . - urlencode($action) . "?server=' + encodeURI(server.options[server.selectedIndex].value) + '&database=' + encodeURI(database.options[database.selectedIndex].value) + "; - - // The exact URL to reload to is different between SQL and Find mode, however. - if ($action == 'find') { - $onchange .= "'&term=' + encodeURI(term.value) + '&filter=' + encodeURI(filter.value) + '&'\""; - } else { - $onchange .= "'&query=' + encodeURI(query.value) + '&search_path=' + encodeURI(search_path.value) + (paginate.checked ? '&paginate=on' : '') + '&'\""; - } - - return $misc->printConnection($onchange, false); -} - -/** - * Searches for a named database object - */ -function doFind($container) { - $lang = $container->get('lang'); - $conf = $container->get('conf'); - $misc = $container->get('misc'); - - $data = $misc->getDatabaseAccessor(); - - if (!isset($_REQUEST['term'])) { - $_REQUEST['term'] = ''; - } - - if (!isset($_REQUEST['filter'])) { - $_REQUEST['filter'] = ''; - } - - $default_html = $misc->printTabs($misc->getNavTabs('popup'), 'find', false); - - $default_html .= "<form action=\"database.php\" method=\"post\" target=\"detail\">\n"; - $default_html .= _printConnection($container, 'find'); - $default_html .= "<p><input class=\"focusme\" name=\"term\" value=\"" . htmlspecialchars($_REQUEST['term']) . "\" size=\"32\" maxlength=\"{$data->_maxNameLen}\" />\n"; - - // Output list of filters. This is complex due to all the 'has' and 'conf' feature possibilities - $default_html .= "<select name=\"filter\">\n"; - $default_html .= "\t<option value=\"\"" . ($_REQUEST['filter'] == '' ? ' selected="selected" ' : '') . ">{$lang['strallobjects']}</option>\n"; - $default_html .= "\t<option value=\"SCHEMA\"" . ($_REQUEST['filter'] == 'SCHEMA' ? ' selected="selected" ' : '') . ">{$lang['strschemas']}</option>\n"; - $default_html .= "\t<option value=\"TABLE\"" . ($_REQUEST['filter'] == 'TABLE' ? ' selected="selected" ' : '') . ">{$lang['strtables']}</option>\n"; - $default_html .= "\t<option value=\"VIEW\"" . ($_REQUEST['filter'] == 'VIEW' ? ' selected="selected" ' : '') . ">{$lang['strviews']}</option>\n"; - $default_html .= "\t<option value=\"SEQUENCE\"" . ($_REQUEST['filter'] == 'SEQUENCE' ? ' selected="selected" ' : '') . ">{$lang['strsequences']}</option>\n"; - $default_html .= "\t<option value=\"COLUMN\"" . ($_REQUEST['filter'] == 'COLUMN' ? ' selected="selected" ' : '') . ">{$lang['strcolumns']}</option>\n"; - $default_html .= "\t<option value=\"RULE\"" . ($_REQUEST['filter'] == 'RULE' ? ' selected="selected" ' : '') . ">{$lang['strrules']}</option>\n"; - $default_html .= "\t<option value=\"INDEX\"" . ($_REQUEST['filter'] == 'INDEX' ? ' selected="selected" ' : '') . ">{$lang['strindexes']}</option>\n"; - $default_html .= "\t<option value=\"TRIGGER\"" . ($_REQUEST['filter'] == 'TRIGGER' ? ' selected="selected" ' : '') . ">{$lang['strtriggers']}</option>\n"; - $default_html .= "\t<option value=\"CONSTRAINT\"" . ($_REQUEST['filter'] == 'CONSTRAINT' ? ' selected="selected" ' : '') . ">{$lang['strconstraints']}</option>\n"; - $default_html .= "\t<option value=\"FUNCTION\"" . ($_REQUEST['filter'] == 'FUNCTION' ? ' selected="selected" ' : '') . ">{$lang['strfunctions']}</option>\n"; - $default_html .= "\t<option value=\"DOMAIN\"" . ($_REQUEST['filter'] == 'DOMAIN' ? ' selected="selected" ' : '') . ">{$lang['strdomains']}</option>\n"; - if ($conf['show_advanced']) { - $default_html .= "\t<option value=\"AGGREGATE\"" . ($_REQUEST['filter'] == 'AGGREGATE' ? ' selected="selected" ' : '') . ">{$lang['straggregates']}</option>\n"; - $default_html .= "\t<option value=\"TYPE\"" . ($_REQUEST['filter'] == 'TYPE' ? ' selected="selected" ' : '') . ">{$lang['strtypes']}</option>\n"; - $default_html .= "\t<option value=\"OPERATOR\"" . ($_REQUEST['filter'] == 'OPERATOR' ? ' selected="selected" ' : '') . ">{$lang['stroperators']}</option>\n"; - $default_html .= "\t<option value=\"OPCLASS\"" . ($_REQUEST['filter'] == 'OPCLASS' ? ' selected="selected" ' : '') . ">{$lang['stropclasses']}</option>\n"; - $default_html .= "\t<option value=\"CONVERSION\"" . ($_REQUEST['filter'] == 'CONVERSION' ? ' selected="selected" ' : '') . ">{$lang['strconversions']}</option>\n"; - $default_html .= "\t<option value=\"LANGUAGE\"" . ($_REQUEST['filter'] == 'LANGUAGE' ? ' selected="selected" ' : '') . ">{$lang['strlanguages']}</option>\n"; - } - $default_html .= "</select>\n"; - - $default_html .= "<input type=\"submit\" value=\"{$lang['strfind']}\" />\n"; - $default_html .= "<input type=\"hidden\" name=\"action\" value=\"find\" /></p>\n"; - $default_html .= "</form>\n"; - - // Default focus - //$misc->setFocus('forms[0].term'); - return $default_html; -} - -/** - * Allow execution of arbitrary SQL statements on a database - */ -function doDefault($container) { - - $lang = $container->get('lang'); - $misc = $container->get('misc'); - $data = $misc->getDatabaseAccessor(); - - if (!isset($_SESSION['sqlquery'])) { - $_SESSION['sqlquery'] = ''; - } - - $default_html = $misc->printTabs($misc->getNavTabs('popup'), 'sql', false); - - $default_html .= '<form action="/src/views/sql.php" method="post" enctype="multipart/form-data" class="sqlform" id="sqlform" target="detail">'; - $default_html .= "\n"; - $default_html .= _printConnection($container, 'sql'); - - $default_html .= "\n"; - - if (!isset($_REQUEST['search_path'])) { - $_REQUEST['search_path'] = implode(',', $data->getSearchPath()); - } - $search_path = htmlspecialchars($_REQUEST['search_path']); - $sqlquery = htmlspecialchars($_SESSION['sqlquery']); - - $default_html .= ' <div class="searchpath">'; - $default_html .= "<label>"; - $default_html .= $misc->printHelp($lang['strsearchpath'], 'pg.schema.search_path', false); - - $default_html .= ": <input type=\"text\" name=\"search_path\" size=\"50\" value=\"" . $search_path . "\" />"; - $default_html .= "</label>\n"; - $default_html .= "</div>\n"; - - $default_html .= '<div id="queryedition" style="padding:1%;width:98%;float:left;">'; - $default_html .= "\n<textarea style=\"width:98%;\" rows=\"10\" cols=\"50\" name=\"query\" id=\"query\">" . $sqlquery . "</textarea>\n"; - $default_html .= "</div>\n"; - - // Check that file uploads are enabled - if (ini_get('file_uploads')) { - // Don't show upload option if max size of uploads is zero - $max_size = $misc->inisizeToBytes(ini_get('upload_max_filesize')); - if (is_double($max_size) && $max_size > 0) { - $default_html .= "<p><input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"{$max_size}\" />\n"; - $default_html .= "<label for=\"script\">{$lang['struploadscript']}</label> <input id=\"script\" name=\"script\" type=\"file\" /></p>\n"; - } - } - $checked = (isset($_REQUEST['paginate']) ? ' checked="checked"' : ''); - $default_html .= "<p><label for=\"paginate\"><input type=\"checkbox\" id=\"paginate\" name=\"paginate\"" . $checked . " /> {$lang['strpaginate']}</label></p>\n"; - - $default_html .= "<p><input type=\"submit\" name=\"execute\" accesskey=\"r\" value=\"{$lang['strexecute']}\" />\n"; - $default_html .= "<input type=\"reset\" accesskey=\"q\" value=\"{$lang['strreset']}\" /></p>\n"; - $default_html .= "</form>\n"; - - // Default focus - //$misc->setFocus('forms[0].query'); - return $default_html; +$sqledit_controller = new \PHPPgAdmin\Controller\SQLEditController($container); -} +$sqledit_controller->render(); diff --git a/src/views/tables.php b/src/views/tables.php index 2e21f5b7..8043bd8f 100644 --- a/src/views/tables.php +++ b/src/views/tables.php @@ -10,80 +10,4 @@ require_once '../lib.inc.php'; $table_controller = new \PHPPgAdmin\Controller\TableController($container); - -$misc->printHeader($lang['strtables']); -$misc->printBody(); - -switch ($action) { - case 'create': - if (isset($_POST['cancel'])) { - $table_controller->doDefault(); - } else { - $table_controller->doCreate(); - } - - break; - case 'createlike': - $table_controller->doCreateLike(false); - break; - case 'confcreatelike': - if (isset($_POST['cancel'])) { - $table_controller->doDefault(); - } else { - $table_controller->doCreateLike(true); - } - - break; - case 'selectrows': - if (!isset($_POST['cancel'])) { - $table_controller->doSelectRows(false); - } else { - $table_controller->doDefault(); - } - - break; - case 'confselectrows': - $table_controller->doSelectRows(true); - break; - case 'insertrow': - if (!isset($_POST['cancel'])) { - $table_controller->doInsertRow(false); - } else { - $table_controller->doDefault(); - } - - break; - case 'confinsertrow': - $table_controller->doInsertRow(true); - break; - case 'empty': - if (isset($_POST['empty'])) { - $table_controller->doEmpty(false); - } else { - $table_controller->doDefault(); - } - - break; - case 'confirm_empty': - $table_controller->doEmpty(true); - break; - case 'drop': - if (isset($_POST['drop'])) { - $table_controller->doDrop(false); - } else { - $table_controller->doDefault(); - } - - break; - case 'confirm_drop': - $table_controller->doDrop(true); - break; - default: - if ($table_controller->adminActions($action, 'table') === false) { - $table_controller->doDefault(); - } - - break; -} - -$misc->printFooter(); +$table_controller->render();
\ No newline at end of file diff --git a/src/views/tablespaces.php b/src/views/tablespaces.php index 0ec0af61..3fb9e3cb 100755 --- a/src/views/tablespaces.php +++ b/src/views/tablespaces.php @@ -9,353 +9,6 @@ // Include application functions require_once '../lib.inc.php'; -$action = (isset($_REQUEST['action'])) ? $_REQUEST['action'] : ''; -if (!isset($msg)) { - $msg = ''; -} +$tablespaces_controller = new \PHPPgAdmin\Controller\TableSpacesController($container); -/** - * Function to allow altering of a tablespace - */ -function doAlter($msg = '') { - global $data, $misc; - global $lang; - - $misc->printTrail('tablespace'); - $misc->printTitle($lang['stralter'], 'pg.tablespace.alter'); - $misc->printMsg($msg); - - // Fetch tablespace info - $tablespace = $data->getTablespace($_REQUEST['tablespace']); - // Fetch all users - $users = $data->getUsers(); - - if ($tablespace->recordCount() > 0) { - - if (!isset($_POST['name'])) { - $_POST['name'] = $tablespace->fields['spcname']; - } - - if (!isset($_POST['owner'])) { - $_POST['owner'] = $tablespace->fields['spcowner']; - } - - if (!isset($_POST['comment'])) { - $_POST['comment'] = ($data->hasSharedComments()) ? $tablespace->fields['spccomment'] : ''; - } - - echo "<form action=\"/src/views/tablespaces.php\" method=\"post\">\n"; - echo $misc->form; - echo "<table>\n"; - echo "<tr><th class=\"data left required\">{$lang['strname']}</th>\n"; - echo "<td class=\"data1\">"; - echo "<input name=\"name\" size=\"32\" maxlength=\"{$data->_maxNameLen}\" value=\"", - htmlspecialchars($_POST['name']), "\" /></td></tr>\n"; - echo "<tr><th class=\"data left required\">{$lang['strowner']}</th>\n"; - echo "<td class=\"data1\"><select name=\"owner\">"; - while (!$users->EOF) { - $uname = $users->fields['usename']; - echo "<option value=\"", htmlspecialchars($uname), "\"", - ($uname == $_POST['owner']) ? ' selected="selected"' : '', ">", htmlspecialchars($uname), "</option>\n"; - $users->moveNext(); - } - echo "</select></td></tr>\n"; - if ($data->hasSharedComments()) { - echo "<tr><th class=\"data left\">{$lang['strcomment']}</th>\n"; - echo "<td class=\"data1\">"; - echo "<textarea rows=\"3\" cols=\"32\" name=\"comment\">", - htmlspecialchars($_POST['comment']), "</textarea></td></tr>\n"; - } - echo "</table>\n"; - echo "<p><input type=\"hidden\" name=\"action\" value=\"save_edit\" />\n"; - echo "<input type=\"hidden\" name=\"tablespace\" value=\"", htmlspecialchars($_REQUEST['tablespace']), "\" />\n"; - echo "<input type=\"submit\" name=\"alter\" value=\"{$lang['stralter']}\" />\n"; - echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; - echo "</form>\n"; - } else { - echo "<p>{$lang['strnodata']}</p>\n"; - } - -} - -/** - * Function to save after altering a tablespace - */ -function doSaveAlter() { - global $data, $lang; - - // Check data - if (trim($_POST['name']) == '') { - doAlter($lang['strtablespaceneedsname']); - } else { - $status = $data->alterTablespace($_POST['tablespace'], $_POST['name'], $_POST['owner'], $_POST['comment']); - if ($status == 0) { - // If tablespace has been renamed, need to change to the new name - if ($_POST['tablespace'] != $_POST['name']) { - // Jump them to the new table name - $_REQUEST['tablespace'] = $_POST['name']; - } - doDefault($lang['strtablespacealtered']); - } else { - doAlter($lang['strtablespacealteredbad']); - } - - } -} - -/** - * Show confirmation of drop and perform actual drop - */ -function doDrop($confirm) { - global $data, $misc; - global $lang; - - if ($confirm) { - $misc->printTrail('tablespace'); - $misc->printTitle($lang['strdrop'], 'pg.tablespace.drop'); - - echo "<p>", sprintf($lang['strconfdroptablespace'], $misc->printVal($_REQUEST['tablespace'])), "</p>\n"; - - echo "<form action=\"/src/views/tablespaces.php\" method=\"post\">\n"; - echo $misc->form; - echo "<input type=\"hidden\" name=\"action\" value=\"drop\" />\n"; - echo "<input type=\"hidden\" name=\"tablespace\" value=\"", htmlspecialchars($_REQUEST['tablespace']), "\" />\n"; - echo "<input type=\"submit\" name=\"drop\" value=\"{$lang['strdrop']}\" />\n"; - echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n"; - echo "</form>\n"; - } else { - $status = $data->droptablespace($_REQUEST['tablespace']); - if ($status == 0) { - doDefault($lang['strtablespacedropped']); - } else { - doDefault($lang['strtablespacedroppedbad']); - } - - } -} - -/** - * Displays a screen where they can enter a new tablespace - */ -function doCreate($msg = '') { - global $data, $misc, $spcname; - global $lang; - - $server_info = $misc->getServerInfo(); - - if (!isset($_POST['formSpcname'])) { - $_POST['formSpcname'] = ''; - } - - if (!isset($_POST['formOwner'])) { - $_POST['formOwner'] = $server_info['username']; - } - - if (!isset($_POST['formLoc'])) { - $_POST['formLoc'] = ''; - } - - if (!isset($_POST['formComment'])) { - $_POST['formComment'] = ''; - } - - // Fetch all users - $users = $data->getUsers(); - - $misc->printTrail('server'); - $misc->printTitle($lang['strcreatetablespace'], 'pg.tablespace.create'); - $misc->printMsg($msg); - - echo "<form action=\"/src/views/tablespaces.php\" method=\"post\">\n"; - echo $misc->form; - echo "<table>\n"; - echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strname']}</th>\n"; - echo "\t\t<td class=\"data1\"><input size=\"32\" name=\"formSpcname\" maxlength=\"{$data->_maxNameLen}\" value=\"", htmlspecialchars($_POST['formSpcname']), "\" /></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strowner']}</th>\n"; - echo "\t\t<td class=\"data1\"><select name=\"formOwner\">\n"; - while (!$users->EOF) { - $uname = $users->fields['usename']; - echo "\t\t\t<option value=\"", htmlspecialchars($uname), "\"", - ($uname == $_POST['formOwner']) ? ' selected="selected"' : '', ">", htmlspecialchars($uname), "</option>\n"; - $users->moveNext(); - } - echo "\t\t</select></td>\n\t</tr>\n"; - echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strlocation']}</th>\n"; - echo "\t\t<td class=\"data1\"><input size=\"32\" name=\"formLoc\" value=\"", htmlspecialchars($_POST['formLoc']), "\" /></td>\n\t</tr>\n"; - // Comments (if available) - if ($data->hasSharedComments()) { - echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strcomment']}</th>\n"; - echo "\t\t<td><textarea name=\"formComment\" rows=\"3\" cols=\"32\">", - htmlspecialchars($_POST['formComment']), "</textarea></td>\n\t</tr>\n"; - } - echo "</table>\n"; - echo "<p><input type=\"hidden\" name=\"action\" value=\"save_create\" />\n"; - echo "<input type=\"submit\" value=\"{$lang['strcreate']}\" />\n"; - echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" /></p>\n"; - echo "</form>\n"; -} - -/** - * Actually creates the new tablespace in the cluster - */ -function doSaveCreate() { - global $data; - global $lang; - - // Check data - if (trim($_POST['formSpcname']) == '') { - doCreate($lang['strtablespaceneedsname']); - } elseif (trim($_POST['formLoc']) == '') { - doCreate($lang['strtablespaceneedsloc']); - } else { - // Default comment to blank if it isn't set - if (!isset($_POST['formComment'])) { - $_POST['formComment'] = null; - } - - $status = $data->createTablespace($_POST['formSpcname'], $_POST['formOwner'], $_POST['formLoc'], $_POST['formComment']); - if ($status == 0) { - doDefault($lang['strtablespacecreated']); - } else { - doCreate($lang['strtablespacecreatedbad']); - } - - } -} - -/** - * Show default list of tablespaces in the cluster - */ -function doDefault($msg = '') { - global $data, $misc; - global $lang; - - $misc->printTrail('server'); - $misc->printTabs('server', 'tablespaces'); - $misc->printMsg($msg); - - $tablespaces = $data->getTablespaces(); - - $columns = [ - 'database' => [ - 'title' => $lang['strname'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('spcname'), - ], - 'owner' => [ - 'title' => $lang['strowner'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('spcowner'), - ], - 'location' => [ - 'title' => $lang['strlocation'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('spclocation'), - ], - 'actions' => [ - 'title' => $lang['stractions'], - ], - ]; - - if ($data->hasSharedComments()) { - $columns['comment'] = [ - 'title' => $lang['strcomment'], - 'field' => \PHPPgAdmin\Decorators\Decorator::field('spccomment'), - ]; - } - - $actions = [ - 'alter' => [ - 'content' => $lang['stralter'], - 'attr' => [ - 'href' => [ - 'url' => 'tablespaces.php', - 'urlvars' => [ - 'action' => 'edit', - 'tablespace' => \PHPPgAdmin\Decorators\Decorator::field('spcname'), - ], - ], - ], - ], - 'drop' => [ - 'content' => $lang['strdrop'], - 'attr' => [ - 'href' => [ - 'url' => 'tablespaces.php', - 'urlvars' => [ - 'action' => 'confirm_drop', - 'tablespace' => \PHPPgAdmin\Decorators\Decorator::field('spcname'), - ], - ], - ], - ], - 'privileges' => [ - 'content' => $lang['strprivileges'], - 'attr' => [ - 'href' => [ - 'url' => 'privileges.php', - 'urlvars' => [ - 'subject' => 'tablespace', - 'tablespace' => \PHPPgAdmin\Decorators\Decorator::field('spcname'), - ], - ], - ], - ], - ]; - - echo $misc->printTable($tablespaces, $columns, $actions, 'tablespaces-tablespaces', $lang['strnotablespaces']); - - $misc->printNavLinks(['create' => [ - 'attr' => [ - 'href' => [ - 'url' => 'tablespaces.php', - 'urlvars' => [ - 'action' => 'create', - 'server' => $_REQUEST['server'], - ], - ], - ], - 'content' => $lang['strcreatetablespace'], - ]], 'tablespaces-tablespaces', get_defined_vars()); -} - -$misc->printHeader($lang['strtablespaces']); -$misc->printBody(); - -switch ($action) { - case 'save_create': - if (isset($_REQUEST['cancel'])) { - doDefault(); - } else { - doSaveCreate(); - } - - break; - case 'create': - doCreate(); - break; - case 'drop': - if (isset($_REQUEST['cancel'])) { - doDefault(); - } else { - doDrop(false); - } - - break; - case 'confirm_drop': - doDrop(true); - break; - case 'save_edit': - if (isset($_REQUEST['cancel'])) { - doDefault(); - } else { - doSaveAlter(); - } - - break; - case 'edit': - doAlter(); - break; - default: - doDefault(); - break; -} - -$misc->printFooter(); +$tablespaces_controller->render(); diff --git a/src/views/tblproperties.php b/src/views/tblproperties.php index 0aa6fd54..8072d748 100644 --- a/src/views/tblproperties.php +++ b/src/views/tblproperties.php @@ -11,57 +11,4 @@ require_once '../lib.inc.php'; $tableproperty_controller = new \PHPPgAdmin\Controller\TablePropertyController($container); -$misc->printHeader($lang['strtables'] . ' - ' . $_REQUEST['table']); -$misc->printBody(); - -switch ($action) { - case 'alter': - if (isset($_POST['alter'])) { - $tableproperty_controller->doSaveAlter(); - } else { - $tableproperty_controller->doDefault(); - } - - break; - case 'confirm_alter': - $tableproperty_controller->doAlter(); - break; - case 'import': - $tableproperty_controller->doImport(); - break; - case 'export': - $tableproperty_controller->doExport(); - break; - case 'add_column': - if (isset($_POST['cancel'])) { - $tableproperty_controller->doDefault(); - } else { - $tableproperty_controller->doAddColumn(); - } - - break; - case 'properties': - if (isset($_POST['cancel'])) { - $tableproperty_controller->doDefault(); - } else { - $tableproperty_controller->doProperties(); - } - - break; - case 'drop': - if (isset($_POST['drop'])) { - $tableproperty_controller->doDrop(false); - } else { - $tableproperty_controller->doDefault(); - } - - break; - case 'confirm_drop': - $tableproperty_controller->doDrop(true); - break; - default: - $tableproperty_controller->doDefault(); - break; -} - -$misc->printFooter(); +$tableproperty_controller->render();
\ No newline at end of file diff --git a/src/views/triggers.php b/src/views/triggers.php index 53b916e2..3c756b95 100644 --- a/src/views/triggers.php +++ b/src/views/triggers.php @@ -10,69 +10,4 @@ require_once '../lib.inc.php'; $trigger_controller = new \PHPPgAdmin\Controller\TriggerController($container); - -$misc->printHeader($lang['strtables'] . ' - ' . $_REQUEST['table'] . ' - ' . $lang['strtriggers']); -$misc->printBody(); - -switch ($action) { - case 'alter': - if (isset($_POST['alter'])) { - $trigger_controller->doSaveAlter(); - } else { - $trigger_controller->doDefault(); - } - - break; - case 'confirm_alter': - $trigger_controller->doAlter(); - break; - case 'confirm_enable': - $trigger_controller->doEnable(true); - break; - case 'confirm_disable': - $trigger_controller->doDisable(true); - break; - case 'save_create': - if (isset($_POST['cancel'])) { - $trigger_controller->doDefault(); - } else { - $trigger_controller->doSaveCreate(); - } - - break; - case 'create': - $trigger_controller->doCreate(); - break; - case 'drop': - if (isset($_POST['yes'])) { - $trigger_controller->doDrop(false); - } else { - $trigger_controller->doDefault(); - } - - break; - case 'confirm_drop': - $trigger_controller->doDrop(true); - break; - case 'enable': - if (isset($_POST['yes'])) { - $trigger_controller->doEnable(false); - } else { - $trigger_controller->doDefault(); - } - - break; - case 'disable': - if (isset($_POST['yes'])) { - $trigger_controller->doDisable(false); - } else { - $trigger_controller->doDefault(); - } - - break; - default: - $trigger_controller->doDefault(); - break; -} - -$misc->printFooter(); +$trigger_controller->render(); diff --git a/src/views/types.php b/src/views/types.php index 81e24dba..21ddba16 100644 --- a/src/views/types.php +++ b/src/views/types.php @@ -10,55 +10,4 @@ require_once '../lib.inc.php'; $type_controller = new \PHPPgAdmin\Controller\TypeController($container); - -$misc->printHeader($lang['strtypes']); -$misc->printBody(); - -switch ($action) { - case 'create_comp': - if (isset($_POST['cancel'])) { - $type_controller->doDefault(); - } else { - $type_controller->doCreateComposite(); - } - - break; - case 'create_enum': - if (isset($_POST['cancel'])) { - $type_controller->doDefault(); - } else { - $type_controller->doCreateEnum(); - } - - break; - case 'save_create': - if (isset($_POST['cancel'])) { - $type_controller->doDefault(); - } else { - $type_controller->doSaveCreate(); - } - - break; - case 'create': - $type_controller->doCreate(); - break; - case 'drop': - if (isset($_POST['cancel'])) { - $type_controller->doDefault(); - } else { - $type_controller->doDrop(false); - } - - break; - case 'confirm_drop': - $type_controller->doDrop(true); - break; - case 'properties': - $type_controller->doProperties(); - break; - default: - $type_controller->doDefault(); - break; -} - -$misc->printFooter(); +$type_controller->render(); diff --git a/src/views/viewproperties.php b/src/views/viewproperties.php index 4148854c..f0680ee7 100755 --- a/src/views/viewproperties.php +++ b/src/views/viewproperties.php @@ -10,61 +10,4 @@ require_once '../lib.inc.php'; $viewproperty_controller = new \PHPPgAdmin\Controller\ViewPropertyController($container); - -$misc->printHeader($lang['strviews'] . ' - ' . $_REQUEST['view']); -$misc->printBody(); - -switch ($action) { - case 'save_edit': - if (isset($_POST['cancel'])) { - $viewproperty_controller->doDefinition(); - } else { - $viewproperty_controller->doSaveEdit(); - } - - break; - case 'edit': - $viewproperty_controller->doEdit(); - break; - case 'export': - $viewproperty_controller->doExport(); - break; - case 'definition': - $viewproperty_controller->doDefinition(); - break; - case 'properties': - if (isset($_POST['cancel'])) { - $viewproperty_controller->doDefault(); - } else { - $viewproperty_controller->doProperties(); - } - - break; - case 'alter': - if (isset($_POST['alter'])) { - $viewproperty_controller->doAlter(false); - } else { - $viewproperty_controller->doDefault(); - } - - break; - case 'confirm_alter': - doAlter(true); - break; - case 'drop': - if (isset($_POST['drop'])) { - $viewproperty_controller->doDrop(false); - } else { - $viewproperty_controller->doDefault(); - } - - break; - case 'confirm_drop': - $viewproperty_controller->doDrop(true); - break; - default: - $viewproperty_controller->doDefault(); - break; -} - -$misc->printFooter(); +$viewproperty_controller->render(); diff --git a/src/views/views.php b/src/views/views.php index e2d62965..9c2cec16 100644 --- a/src/views/views.php +++ b/src/views/views.php @@ -11,65 +11,4 @@ require_once '../lib.inc.php'; $view_controller = new \PHPPgAdmin\Controller\ViewController($container); -$misc->printHeader($lang['strviews']); -$misc->printBody(); - -switch ($action) { - case 'selectrows': - if (!isset($_REQUEST['cancel'])) { - $view_controller->doSelectRows(false); - } else { - $view_controller->doDefault(); - } - - break; - case 'confselectrows': - $view_controller->doSelectRows(true); - break; - case 'save_create_wiz': - if (isset($_REQUEST['cancel'])) { - $view_controller->doDefault(); - } else { - $view_controller->doSaveCreateWiz(); - } - - break; - case 'wiz_create': - doWizardCreate(); - break; - case 'set_params_create': - if (isset($_POST['cancel'])) { - $view_controller->doDefault(); - } else { - $view_controller->doSetParamsCreate(); - } - - break; - case 'save_create': - if (isset($_REQUEST['cancel'])) { - $view_controller->doDefault(); - } else { - $view_controller->doSaveCreate(); - } - - break; - case 'create': - doCreate(); - break; - case 'drop': - if (isset($_POST['drop'])) { - $view_controller->doDrop(false); - } else { - $view_controller->doDefault(); - } - - break; - case 'confirm_drop': - $view_controller->doDrop(true); - break; - default: - $view_controller->doDefault(); - break; -} - -$misc->printFooter(); +$view_controller->render();
\ No newline at end of file diff --git a/src/xhtml/HTMLController.php b/src/xhtml/HTMLController.php new file mode 100644 index 00000000..2a794fde --- /dev/null +++ b/src/xhtml/HTMLController.php @@ -0,0 +1,135 @@ +<?php + +namespace PHPPgAdmin\XHtml; +use \PHPPgAdmin\Decorators\Decorator; + +/** + * Base controller class + */ +class HTMLController { + private $container = null; + private $_connection = null; + private $_reload_browser = false; + private $app = null; + private $data = null; + private $database = null; + private $server_id = null; + public $appLangFiles = []; + public $appThemes = []; + public $appName = ''; + public $appVersion = ''; + public $form = ''; + public $href = ''; + public $lang = []; + public $action = ''; + public $_name = 'HTMLController'; + public $_title = 'base'; + private $table_controller = null; + private $trail_controller = null; + + /* Constructor */ + function __construct(\Slim\Container $container) { + $this->container = $container; + $this->lang = $container->get('lang'); + $this->conf = $container->get('conf'); + $this->view = $container->get('view'); + $this->plugin_manager = $container->get('plugin_manager'); + $this->appName = $container->get('settings')['appName']; + $this->appVersion = $container->get('settings')['appVersion']; + $this->appLangFiles = $container->get('appLangFiles'); + $this->misc = $container->get('misc'); + $this->appThemes = $container->get('appThemes'); + $this->action = $container->get('action'); + + //\PC::debug($this->_name, 'instanced controller'); + } + + public function getContainer() { + return $this->container; + } + + /** + * Returns URL given an action associative array. + * NOTE: this function does not html-escape, only url-escape + * @param $action An associative array of the follow properties: + * 'url' => The first part of the URL (before the ?) + * 'urlvars' => Associative array of (URL variable => field name) + * these are appended to the URL + * @param $fields Field data from which 'urlfield' and 'vars' are obtained. + */ + protected function getActionUrl(&$action, &$fields) { + $url = Decorator::get_sanitized_value($action['url'], $fields); + + if ($url === false) { + return ''; + } + + if (!empty($action['urlvars'])) { + $urlvars = Decorator::get_sanitized_value($action['urlvars'], $fields); + } else { + $urlvars = []; + } + + /* set server, database and schema parameter if not presents */ + if (isset($urlvars['subject'])) { + $subject = Decorator::get_sanitized_value($urlvars['subject'], $fields); + } else { + $subject = ''; + } + + if (isset($_REQUEST['server']) and !isset($urlvars['server']) and $subject != 'root') { + $urlvars['server'] = $_REQUEST['server']; + if (isset($_REQUEST['database']) and !isset($urlvars['database']) and $subject != 'server') { + $urlvars['database'] = $_REQUEST['database']; + if (isset($_REQUEST['schema']) and !isset($urlvars['schema']) and $subject != 'database') { + $urlvars['schema'] = $_REQUEST['schema']; + } + } + } + + $sep = '?'; + foreach ($urlvars as $var => $varfield) { + $url .= $sep . Decorator::value_url($var, $fields) . '=' . Decorator::value_url($varfield, $fields); + $sep = '&'; + } + //return '/src/views/' . $url; + return $url; + } + /** + * Display a link + * @param $link An associative array of link parameters to print + * link = array( + * 'attr' => array( // list of A tag attribute + * 'attrname' => attribute value + * ... + * ), + * 'content' => The link text + * 'fields' => (optionnal) the data from which content and attr's values are obtained + * ); + * the special attribute 'href' might be a string or an array. If href is an array it + * will be generated by getActionUrl. See getActionUrl comment for array format. + */ + function printLink($link, $do_print = true) { + \PC::debug($link, 'printLink'); + + if (!isset($link['fields'])) { + $link['fields'] = $_REQUEST; + } + + $tag = "<a "; + foreach ($link['attr'] as $attr => $value) { + if ($attr == 'href' and is_array($value)) { + $tag .= 'href="' . htmlentities($this->getActionUrl($value, $link['fields'])) . '" '; + } else { + $tag .= htmlentities($attr) . '="' . Decorator::get_sanitized_value($value, $link['fields'], 'html') . '" '; + } + } + $tag .= ">" . Decorator::get_sanitized_value($link['content'], $link['fields'], 'html') . "</a>\n"; + + if ($do_print) { + echo $tag; + } else { + return $tag; + } + } +}
\ No newline at end of file diff --git a/src/xhtml/HTMLNavbarController.php b/src/xhtml/HTMLNavbarController.php new file mode 100644 index 00000000..ab30dade --- /dev/null +++ b/src/xhtml/HTMLNavbarController.php @@ -0,0 +1,535 @@ +<?php + +namespace PHPPgAdmin\XHtml; + +/** + * Class to render tables. Formerly part of Misc.php + * + */ +class HTMLNavbarController extends HTMLController { + public $_name = 'HTMLNavbarController'; + + /** + * Display a bread crumb trail. + * @param $do_print true to echo, false to return html + */ + function printTrail($trail = [], $do_print = true) { + $lang = $this->lang; + $misc = $this->misc; + + $trail_html = $this->printTopbar(false); + + if (is_string($trail)) { + $trail = $this->getTrail($trail); + } + + $trail_html .= "<div class=\"trail\"><table><tr>"; + + foreach ($trail as $crumb) { + $trail_html .= "<td class=\"crumb\">"; + $crumblink = "<a"; + + if (isset($crumb['url'])) { + $crumblink .= " href=\"{$crumb['url']}\""; + } + + if (isset($crumb['title'])) { + $crumblink .= " title=\"{$crumb['title']}\""; + } + + $crumblink .= ">"; + + if (isset($crumb['title'])) { + $iconalt = $crumb['title']; + } else { + $iconalt = 'Database Root'; + } + + if (isset($crumb['icon']) && $icon = $misc->icon($crumb['icon'])) { + $crumblink .= "<span class=\"icon\"><img src=\"{$icon}\" alt=\"{$iconalt}\" /></span>"; + } + + $crumblink .= "<span class=\"label\">" . htmlspecialchars($crumb['text']) . "</span></a>"; + + if (isset($crumb['help'])) { + $trail_html .= $misc->printHelp($crumblink, $crumb['help'], false); + } else { + $trail_html .= $crumblink; + } + + $trail_html .= "{$lang['strseparator']}"; + $trail_html .= "</td>"; + } + + $trail_html .= "</tr></table></div>\n"; + if ($do_print) { + echo $trail_html; + } else { + return $trail_html; + } + } + + /** + * Display the navlinks + * + * @param $navlinks - An array with the the attributes and values that will be shown. See printLinksList for array format. + * @param $place - Place where the $navlinks are displayed. Like 'display-browse', where 'display' is the file (display.php) + * @param $env - Associative array of defined variables in the scope of the caller. + * Allows to give some environnement details to plugins. + * and 'browse' is the place inside that code (doBrowse). + * @param bool $do_print if true, print html, if false, return html + */ + function printNavLinks($navlinks, $place, $env = [], $do_print = true) { + $plugin_manager = $this->plugin_manager; + + // Navlinks hook's place + $plugin_functions_parameters = [ + 'navlinks' => &$navlinks, + 'place' => $place, + 'env' => $env, + ]; + $plugin_manager->do_hook('navlinks', $plugin_functions_parameters); + + if (count($navlinks) > 0) { + if ($do_print) { + $this->printLinksList($navlinks, 'navlink'); + } else { + return $this->printLinksList($navlinks, 'navlink', false); + } + + } + } + + /** + * Display navigation tabs + * @param $tabs The name of current section (Ex: intro, server, ...), or an array with tabs (Ex: sqledit.php doFind function) + * @param $activetab The name of the tab to be highlighted. + * @param $print if false, return html + */ + function printTabs($tabs, $activetab, $do_print = true) { + + $lang = $this->lang; + $misc = $this->misc; + $data = $misc->getDatabaseAccessor(); + + if (is_string($tabs)) { + $_SESSION['webdbLastTab'][$tabs] = $activetab; + $tabs = $misc->getNavTabs($tabs); + } + $tabs_html = ''; + if (count($tabs) > 0) { + + $tabs_html .= "<table class=\"tabs\"><tr>\n"; + + # FIXME: don't count hidden tabs + $width = (int) (100 / count($tabs)) . '%'; + foreach ($tabs as $tab_id => $tab) { + + $tabs[$tab_id]['active'] = $active = ($tab_id == $activetab) ? ' active' : ''; + + $tabs[$tab_id]['width'] = $width; + + if (!isset($tab['hide']) || $tab['hide'] !== true) { + + $tabs[$tab_id]['tablink'] = htmlentities($this->getActionUrl($tab, $_REQUEST)); + + $tablink = '<a href="' . $tabs[$tab_id]['tablink'] . '">'; + + if (isset($tab['icon']) && $icon = $misc->icon($tab['icon'])) { + $tabs[$tab_id]['iconurl'] = $icon; + $tablink .= "<span class=\"icon\"><img src=\"{$icon}\" alt=\"{$tab['title']}\" /></span>"; + } + + $tablink .= "<span class=\"label\">{$tab['title']}</span></a>"; + + $tabs_html .= "<td style=\"width: {$width}\" class=\"tab{$active}\">"; + + if (isset($tab['help'])) { + $tabs_html .= $misc->printHelp($tablink, $tab['help'], false); + } else { + $tabs_html .= $tablink; + } + + $tabs_html .= "</td>\n"; + } + } + $tabs_html .= "</tr></table>\n"; + } + + if ($do_print) { + echo $tabs_html; + } else { + return $tabs_html; + } + + } + + /** + * Get the URL for the last active tab of a particular tab bar. + */ + function getLastTabURL($section) { + $lang = $this->lang; + $misc = $this->misc; + + $tabs = $misc->getNavTabs($section); + + if (isset($_SESSION['webdbLastTab'][$section]) && isset($tabs[$_SESSION['webdbLastTab'][$section]])) { + $tab = $tabs[$_SESSION['webdbLastTab'][$section]]; + } else { + $tab = reset($tabs); + } + \PC::debug(['section' => $section, 'tabs' => $tabs, 'tab' => $tab], 'getLastTabURL'); + return isset($tab['url']) ? $tab : null; + } + + /** + * [printTopbar description] + * @param bool $do_print true to print, false to return html + * @return string + */ + private function printTopbar($do_print = true) { + + $lang = $this->lang; + $plugin_manager = $this->plugin_manager; + $misc = $this->misc; + $appName = $misc->appName; + $appVersion = $misc->appVersion; + $appLangFiles = $misc->appLangFiles; + + $server_info = $misc->getServerInfo(); + $reqvars = $misc->getRequestVars('table'); + + $topbar_html = "<div class=\"topbar\"><table style=\"width: 100%\"><tr><td>"; + + if ($server_info && isset($server_info['platform']) && isset($server_info['username'])) { + /* top left informations when connected */ + $topbar_html .= sprintf($lang['strtopbar'], + '<span class="platform">' . htmlspecialchars($server_info['platform']) . '</span>', + '<span class="host">' . htmlspecialchars((empty($server_info['host'])) ? 'localhost' : $server_info['host']) . '</span>', + '<span class="port">' . htmlspecialchars($server_info['port']) . '</span>', + '<span class="username">' . htmlspecialchars($server_info['username']) . '</span>'); + + $topbar_html .= "</td>"; + + /* top right informations when connected */ + + $toplinks = [ + 'sql' => [ + 'attr' => [ + 'href' => [ + 'url' => 'sqledit.php', + 'urlvars' => array_merge($reqvars, [ + 'action' => 'sql', + ]), + ], + 'target' => "sqledit", + 'id' => 'toplink_sql', + ], + 'content' => $lang['strsql'], + ], + 'history' => [ + 'attr' => [ + 'href' => [ + 'url' => 'history.php', + 'urlvars' => array_merge($reqvars, [ + 'action' => 'pophistory', + ]), + ], + 'id' => 'toplink_history', + ], + 'content' => $lang['strhistory'], + ], + 'find' => [ + 'attr' => [ + 'href' => [ + 'url' => 'sqledit.php', + 'urlvars' => array_merge($reqvars, [ + 'action' => 'find', + ]), + ], + 'target' => "sqledit", + 'id' => 'toplink_find', + ], + 'content' => $lang['strfind'], + ], + 'logout' => [ + 'attr' => [ + 'href' => [ + 'url' => 'servers.php', + 'urlvars' => [ + 'action' => 'logout', + 'logoutServer' => "{$server_info['host']}:{$server_info['port']}:{$server_info['sslmode']}", + ], + ], + 'id' => 'toplink_logout', + ], + 'content' => $lang['strlogout'], + ], + ]; + + // Toplink hook's place + $plugin_functions_parameters = [ + 'toplinks' => &$toplinks, + ]; + + $plugin_manager->do_hook('toplinks', $plugin_functions_parameters); + + $topbar_html .= "<td style=\"text-align: right\">"; + + $topbar_html .= $this->printLinksList($toplinks, 'toplink', [], false); + + $topbar_html .= "</td>"; + + $sql_window_id = htmlentities('sqledit:' . $this->server_id); + $history_window_id = htmlentities('history:' . $this->server_id); + + $topbar_html .= "<script type=\"text/javascript\"> + $('#toplink_sql').click(function() { + window.open($(this).attr('href'),'{$sql_window_id}','toolbar=no,width=700,height=500,resizable=yes,scrollbars=yes').focus(); + return false; + }); + + $('#toplink_history').click(function() { + window.open($(this).attr('href'),'{$history_window_id}','toolbar=no,width=700,height=500,resizable=yes,scrollbars=yes').focus(); + return false; + }); + + $('#toplink_find').click(function() { + window.open($(this).attr('href'),'{$sql_window_id}','toolbar=no,width=700,height=500,resizable=yes,scrollbars=yes').focus(); + return false; + }); + "; + + if (isset($_SESSION['sharedUsername'])) { + $topbar_html .= sprintf(" + $('#toplink_logout').click(function() { + return confirm('%s'); + });", str_replace("'", "\'", $lang['strconfdropcred'])); + } + + $topbar_html .= " + </script>"; + } else { + $topbar_html .= "<span class=\"appname\">{$appName}</span> <span class=\"version\">{$appVersion}</span>"; + } + /* + echo "<td style=\"text-align: right; width: 1%\">"; + + echo "<form method=\"get\"><select name=\"language\" onchange=\"this.form.submit()\">\n"; + $language = isset($_SESSION['webdbLanguage']) ? $_SESSION['webdbLanguage'] : 'english'; + foreach ($appLangFiles as $k => $v) { + echo "<option value=\"{$k}\"", + ($k == $language) ? ' selected="selected"' : '', + ">{$v}</option>\n"; + } + echo "</select>\n"; + echo "<noscript><input type=\"submit\" value=\"Set Language\"></noscript>\n"; + foreach ($_GET as $key => $val) { + if ($key == 'language') continue; + echo "<input type=\"hidden\" name=\"$key\" value=\"", htmlspecialchars($val), "\" />\n"; + } + echo "</form>\n"; + + echo "</td>"; + */ + $topbar_html .= "</tr></table></div>\n"; + + if ($do_print) { + echo $topbar_html; + } else { + return $topbar_html; + } + } + + /** + * Create a bread crumb trail of the object hierarchy. + * @param $object The type of object at the end of the trail. + */ + private function getTrail($subject = null) { + $lang = $this->lang; + $plugin_manager = $this->plugin_manager; + $misc = $this->misc; + $appName = $misc->appName; + + $data = $misc->getDatabaseAccessor(); + + $trail = []; + $vars = ''; + $done = false; + + $trail['root'] = [ + 'text' => $appName, + 'url' => '/redirect/root', + 'icon' => 'Introduction', + ]; + + if ($subject == 'root') { + $done = true; + } + + if (!$done) { + $server_info = $misc->getServerInfo(); + $trail['server'] = [ + 'title' => $lang['strserver'], + 'text' => $server_info['desc'], + 'url' => $misc->getHREFSubject('server'), + 'help' => 'pg.server', + 'icon' => 'Server', + ]; + } + if ($subject == 'server') { + $done = true; + } + + if (isset($_REQUEST['database']) && !$done) { + $trail['database'] = [ + 'title' => $lang['strdatabase'], + 'text' => $_REQUEST['database'], + 'url' => $misc->getHREFSubject('database'), + 'help' => 'pg.database', + 'icon' => 'Database', + ]; + } elseif (isset($_REQUEST['rolename']) && !$done) { + $trail['role'] = [ + 'title' => $lang['strrole'], + 'text' => $_REQUEST['rolename'], + 'url' => $misc->getHREFSubject('role'), + 'help' => 'pg.role', + 'icon' => 'Roles', + ]; + } + if ($subject == 'database' || $subject == 'role') { + $done = true; + } + + if (isset($_REQUEST['schema']) && !$done) { + $trail['schema'] = [ + 'title' => $lang['strschema'], + 'text' => $_REQUEST['schema'], + 'url' => $misc->getHREFSubject('schema'), + 'help' => 'pg.schema', + 'icon' => 'Schema', + ]; + } + if ($subject == 'schema') { + $done = true; + } + + if (isset($_REQUEST['table']) && !$done) { + $trail['table'] = [ + 'title' => $lang['strtable'], + 'text' => $_REQUEST['table'], + 'url' => $misc->getHREFSubject('table'), + 'help' => 'pg.table', + 'icon' => 'Table', + ]; + } elseif (isset($_REQUEST['view']) && !$done) { + $trail['view'] = [ + 'title' => $lang['strview'], + 'text' => $_REQUEST['view'], + 'url' => $misc->getHREFSubject('view'), + 'help' => 'pg.view', + 'icon' => 'View', + ]; + } elseif (isset($_REQUEST['ftscfg']) && !$done) { + $trail['ftscfg'] = [ + 'title' => $lang['strftsconfig'], + 'text' => $_REQUEST['ftscfg'], + 'url' => $misc->getHREFSubject('ftscfg'), + 'help' => 'pg.ftscfg.example', + 'icon' => 'Fts', + ]; + } + if ($subject == 'table' || $subject == 'view' || $subject == 'ftscfg') { + $done = true; + } + + if (!$done && !is_null($subject)) { + switch ($subject) { + case 'function': + $trail[$subject] = [ + 'title' => $lang['str' . $subject], + 'text' => $_REQUEST[$subject], + 'url' => $misc->getHREFSubject('function'), + 'help' => 'pg.function', + 'icon' => 'Function', + ]; + break; + case 'aggregate': + $trail[$subject] = [ + 'title' => $lang['straggregate'], + 'text' => $_REQUEST['aggrname'], + 'url' => $misc->getHREFSubject('aggregate'), + 'help' => 'pg.aggregate', + 'icon' => 'Aggregate', + ]; + break; + case 'column': + $trail['column'] = [ + 'title' => $lang['strcolumn'], + 'text' => $_REQUEST['column'], + 'icon' => 'Column', + 'url' => $misc->getHREFSubject('column'), + ]; + break; + default: + if (isset($_REQUEST[$subject])) { + switch ($subject) { + case 'domain':$icon = 'Domain'; + break; + case 'sequence':$icon = 'Sequence'; + break; + case 'type':$icon = 'Type'; + break; + case 'operator':$icon = 'Operator'; + break; + default:$icon = null; + break; + } + $trail[$subject] = [ + 'title' => $lang['str' . $subject], + 'text' => $_REQUEST[$subject], + 'help' => 'pg.' . $subject, + 'icon' => $icon, + ]; + } + } + } + + // Trail hook's place + $plugin_functions_parameters = [ + 'trail' => &$trail, + 'section' => $subject, + ]; + + $plugin_manager->do_hook('trail', $plugin_functions_parameters); + + return $trail; + } + + /** + * Display a list of links + * @param $links An associative array of links to print. See printLink function for + * the links array format. + * @param $class An optional class or list of classes seprated by a space + * WARNING: This field is NOT escaped! No user should be able to inject something here, use with care. + * @param boolean $do_print true to echo, false to return + */ + private function printLinksList($links, $class = '', $do_print = true) { + $misc = $this->misc; + \PC::debug($links, 'printLinksList'); + $list_html = "<ul class=\"{$class}\">\n"; + foreach ($links as $link) { + $list_html .= "\t<li>"; + $list_html .= $this->printLink($link, false); + $list_html .= "</li>\n"; + } + $list_html .= "</ul>\n"; + if ($do_print) { + echo $list_html; + } else { + return $list_html; + } + } + +} diff --git a/src/xhtml/HTMLTableController.php b/src/xhtml/HTMLTableController.php new file mode 100644 index 00000000..d2035efd --- /dev/null +++ b/src/xhtml/HTMLTableController.php @@ -0,0 +1,339 @@ +<?php + +namespace PHPPgAdmin\XHtml; +use \PHPPgAdmin\Decorators\Decorator; + +/** + * Class to render tables. Formerly part of Misc.php + * + */ +class HTMLTableController extends HTMLController { + public $_name = 'HTMLTableController'; + private $ma = []; + + /** + * Display a table of data. + * @param $tabledata A set of data to be formatted, as returned by $data->getDatabases() etc. + * @param $columns An associative array of columns to be displayed: + * $columns = array( + * column_id => array( + * 'title' => Column heading, + * 'class' => The class to apply on the column cells, + * 'field' => Field name for $tabledata->fields[...], + * 'help' => Help page for this column, + * ), ... + * ); + * @param $actions Actions that can be performed on each object: + * $actions = array( + * * multi action support + * * parameters are serialized for each entries and given in $_REQUEST['ma'] + * 'multiactions' => array( + * 'keycols' => Associative array of (URL variable => field name), // fields included in the form + * 'url' => URL submission, + * 'default' => Default selected action in the form. If null, an empty action is added & selected + * ), + * * actions * + * action_id => array( + * 'title' => Action heading, + * 'url' => Static part of URL. Often we rely + * relative urls, usually the page itself (not '' !), or just a query string, + * 'vars' => Associative array of (URL variable => field name), + * 'multiaction' => Name of the action to execute. + * Add this action to the multi action form + * ), ... + * ); + * @param $place Place where the $actions are displayed. Like 'display-browse', where 'display' + * is the entrypoint (/src/views/display.php) and 'browse' is the action used inside its controller (in this case, doBrowse). + * @param $nodata (optional) Message to display if data set is empty. + * @param $pre_fn (optional) callback closure for each row. It will be passed two params: $rowdata and $actions, + * it may be used to derive new fields or modify actions. + * It can return an array of actions specific to the row, or if nothing is returned then the standard actions are used. + * (see TablePropertyController and ConstraintController for examples) + * The function must not must not store urls because they are relative and won't work out of context. + */ + public function printTable(&$tabledata, &$columns, &$actions, $place, $nodata = null, $pre_fn = null) { + + $data = $this->data; + $misc = $this->misc; + $lang = $this->lang; + $plugin_manager = $this->plugin_manager; + + // Action buttons hook's place + $plugin_functions_parameters = [ + 'actionbuttons' => &$actions, + 'place' => $place, + ]; + $plugin_manager->do_hook('actionbuttons', $plugin_functions_parameters); + + if ($this->has_ma = isset($actions['multiactions'])) { + $this->ma = $actions['multiactions']; + } + $tablehtml = ''; + + unset($actions['multiactions']); + + if ($tabledata->recordCount() > 0) { + + // Remove the 'comment' column if they have been disabled + if (!$this->conf['show_comments']) { + unset($columns['comment']); + } + + if (isset($columns['comment'])) { + // Uncomment this for clipped comments. + // TODO: This should be a user option. + //$columns['comment']['params']['clip'] = true; + } + + if ($this->has_ma) { + $tablehtml .= "<script src=\"/js/multiactionform.js\" type=\"text/javascript\"></script>\n"; + $tablehtml .= "<form id=\"multi_form\" action=\"{$this->ma['url']}\" method=\"post\" enctype=\"multipart/form-data\">\n"; + if (isset($this->ma['vars'])) { + foreach ($this->ma['vars'] as $k => $v) { + $tablehtml .= "<input type=\"hidden\" name=\"$k\" value=\"$v\" />"; + } + } + + } + + $tablehtml .= '<table width="auto" class="' . $place . '">' . "\n"; + + $tablehtml .= $this->getThead($columns, $actions); + + $tablehtml .= $this->getTbody($columns, $actions, $tabledata, $pre_fn); + + $tablehtml .= $this->getTfooter($columns, $actions); + + $tablehtml .= "</table>\n"; + + // Multi action table footer w/ options & [un]check'em all + if ($this->has_ma) { + // if default is not set or doesn't exist, set it to null + if (!isset($this->ma['default']) || !isset($actions[$this->ma['default']])) { + $this->ma['default'] = null; + } + + $tablehtml .= "<br />\n"; + $tablehtml .= "<table>\n"; + $tablehtml .= "<tr>\n"; + $tablehtml .= "<th class=\"data\" style=\"text-align: left\" colspan=\"3\">{$lang['stractionsonmultiplelines']}</th>\n"; + $tablehtml .= "</tr>\n"; + $tablehtml .= "<tr class=\"row1\">\n"; + $tablehtml .= "<td>"; + $tablehtml .= "<a href=\"#\" onclick=\"javascript:checkAll(true);\">{$lang['strselectall']}</a> / "; + $tablehtml .= "<a href=\"#\" onclick=\"javascript:checkAll(false);\">{$lang['strunselectall']}</a></td>\n"; + $tablehtml .= "<td> ---> </td>\n"; + $tablehtml .= "<td>\n"; + $tablehtml .= "\t<select name=\"action\">\n"; + if ($this->ma['default'] == null) { + $tablehtml .= "\t\t<option value=\"\">--</option>\n"; + } + + foreach ($actions as $k => $a) { + if (isset($a['multiaction'])) { + $selected = $this->ma['default'] == $k ? ' selected="selected" ' : ''; + $tablehtml .= "\t\t"; + $tablehtml .= '<option value="' . $a['multiaction'] . '" ' . $selected . ' rel="' . $k . '">' . $a['content'] . '</option>'; + $tablehtml .= "\n"; + } + } + + $tablehtml .= "\t</select>\n"; + $tablehtml .= "<input type=\"submit\" value=\"{$lang['strexecute']}\" />\n"; + $tablehtml .= $this->getForm(); + $tablehtml .= "</td>\n"; + $tablehtml .= "</tr>\n"; + $tablehtml .= "</table>\n"; + $tablehtml .= '</form>'; + }; + + } else { + if (!is_null($nodata)) { + $tablehtml .= "<p>{$nodata}</p>\n"; + } + + } + return $tablehtml; + } + + private function getTbody($columns, $actions, $tabledata, $pre_fn) { + // Display table rows + $i = 0; + $tbody_html = '<tbody>'; + while (!$tabledata->EOF) { + $id = ($i % 2) + 1; + + unset($alt_actions); + if (!is_null($pre_fn)) { + $alt_actions = $pre_fn($tabledata, $actions); + } + + if (!isset($alt_actions)) { + $alt_actions = &$actions; + } + + $tbody_html .= "<tr class=\"data{$id}\">\n"; + if ($this->has_ma) { + $a = []; + foreach ($this->ma['keycols'] as $k => $v) { + $a[$k] = $tabledata->fields[$v]; + } + //\Kint::dump($a); + $tbody_html .= "<td>"; + $tbody_html .= "<input type=\"checkbox\" name=\"ma[]\" value=\"" . htmlentities(serialize($a), ENT_COMPAT, 'UTF-8') . "\" />"; + $tbody_html .= "</td>\n"; + } + + foreach ($columns as $column_id => $column) { + + // Apply default values for missing parameters + if (isset($column['url']) && !isset($column['vars'])) { + $column['vars'] = []; + } + + switch ($column_id) { + case 'actions': + foreach ($alt_actions as $action) { + if (isset($action['disable']) && $action['disable'] === true) { + $tbody_html .= "<td></td>\n"; + } else { + $tbody_html .= "<td class=\"opbutton{$id} {$class}\">"; + $action['fields'] = $tabledata->fields; + $tbody_html .= $this->printLink($action, false); + $tbody_html .= "</td>\n"; + } + } + break; + case 'comment': + $tbody_html .= "<td class='comment_cell'>"; + $val = Decorator::get_sanitized_value($column['field'], $tabledata->fields); + if (!is_null($val)) { + $tbody_html .= htmlentities($val); + } + $tbody_html .= "</td>"; + break; + default: + $tbody_html .= "<td{$class}>"; + $val = Decorator::get_sanitized_value($column['field'], $tabledata->fields); + if (!is_null($val)) { + if (isset($column['url'])) { + $tbody_html .= "<a href=\"{$column['url']}"; + $tbody_html .= $this->printUrlVars($column['vars'], $tabledata->fields, false); + $tbody_html .= "\">"; + } + $type = isset($column['type']) ? $column['type'] : null; + $params = isset($column['params']) ? $column['params'] : []; + $tbody_html .= $this->misc->printVal($val, $type, $params); + if (isset($column['url'])) { + $tbody_html .= "</a>"; + } + + } + + $tbody_html .= "</td>\n"; + break; + } + } + $tbody_html .= "</tr>\n"; + + $tabledata->moveNext(); + $i++; + } + + $tbody_html .= '</tbody>'; + return $tbody_html; + } + + private function getThead($columns, $actions) { + $thead_html = "<thead><tr>\n"; + + // Handle cases where no class has been passed + if (isset($column['class'])) { + $class = $column['class'] !== '' ? " class=\"{$column['class']}\"" : ''; + } else { + $class = ''; + } + + // Display column headings + if ($this->has_ma) { + $thead_html .= "<th></th>"; + } + + foreach ($columns as $column_id => $column) { + switch ($column_id) { + case 'actions': + if (sizeof($actions) > 0) { + $thead_html .= '<th class="data" colspan="' . count($actions) . '">' . $column['title'] . '</th>' . "\n"; + } + + break; + default: + $thead_html .= '<th class="data' . $class . '">'; + if (isset($column['help'])) { + $thead_html .= $this->misc->printHelp($column['title'], $column['help'], false); + } else { + $thead_html .= $column['title']; + } + + $thead_html .= "</th>\n"; + break; + } + } + $thead_html .= "</tr></thead>\n"; + + return $thead_html; + } + + private function getTfooter($columns, $actions) { + $tfoot_html = "<tfoot><tr>\n"; + + // Handle cases where no class has been passed + if (isset($column['class'])) { + $class = $column['class'] !== '' ? " class=\"{$column['class']}\"" : ''; + } else { + $class = ''; + } + + // Display column headings + if ($this->has_ma) { + $tfoot_html .= "<td></td>"; + } + + foreach ($columns as $column_id => $column) { + switch ($column_id) { + case 'actions': + if (sizeof($actions) > 0) { + $tfoot_html .= "<td class=\"data\" colspan=\"" . count($actions) . "\"></td>\n"; + } + + break; + default: + $tfoot_html .= "<td class=\"data{$class}\"></td>\n"; + break; + } + } + $tfoot_html .= "</tr></tfoot>\n"; + + return $tfoot_html; + } + + private function getForm() { + if (!$this->form) { + $this->form = $this->misc->setForm(); + } + return $this->form; + } + + private function printUrlVars(&$vars, &$fields, $do_print = true) { + $url_vars_html = ''; + foreach ($vars as $var => $varfield) { + $url_vars_html .= "{$var}=" . urlencode($fields[$varfield]) . "&"; + } + if ($do_print) { + echo $url_vars_html; + } else { + return $url_vars_html; + } + } + +} diff --git a/src/classes/xhtml/XHTML_Button.php b/src/xhtml/XHTML_Button.php index 91f49646..91f49646 100644 --- a/src/classes/xhtml/XHTML_Button.php +++ b/src/xhtml/XHTML_Button.php diff --git a/src/classes/xhtml/XHTML_Option.php b/src/xhtml/XHTML_Option.php index b61b7077..b61b7077 100644 --- a/src/classes/xhtml/XHTML_Option.php +++ b/src/xhtml/XHTML_Option.php diff --git a/src/classes/xhtml/XHTML_Select.php b/src/xhtml/XHTML_Select.php index b477abe1..b477abe1 100644 --- a/src/classes/xhtml/XHTML_Select.php +++ b/src/xhtml/XHTML_Select.php diff --git a/src/classes/xhtml/XHtmlElement.php b/src/xhtml/XHtmlElement.php index a2ff382f..a2ff382f 100644 --- a/src/classes/xhtml/XHtmlElement.php +++ b/src/xhtml/XHtmlElement.php diff --git a/src/classes/xhtml/XHtmlSimpleElement.php b/src/xhtml/XHtmlSimpleElement.php index dbe2cc32..dbe2cc32 100644 --- a/src/classes/xhtml/XHtmlSimpleElement.php +++ b/src/xhtml/XHtmlSimpleElement.php diff --git a/templates/browser.twig b/templates/browser.twig index 44510bbf..da2782be 100644 --- a/templates/browser.twig +++ b/templates/browser.twig @@ -22,7 +22,7 @@ <div dir="ltr"> <div class="logo"> - <a href="/src/views/intro" target="detail">{{appName}}</a> + <a href="/src/views/intro.php" target="detail">{{appName}}</a> </div> <div class="refreshTree"> <a href="/tree/browser" target="browser"> @@ -65,7 +65,8 @@ return false; }; */ - var tree = new WebFXLoadTree("{{strservers}}", "/tree/servers" /* src for sub branches */ , "/src/views/servers" /* action */ ); + //var tree = new WebFXLoadTree("{{strservers}}", "/tree/servers" /* src for sub branches */ , "/src/views/servers.php" /* action */ ); + var tree = new WebFXLoadTree("{{strservers}}", "/src/views/servers.php?action=tree" /* src for sub branches */ , "servers.php" /* action */ ); tree.write(); tree.setExpanded(true); </script> diff --git a/templates/datatables_header.twig b/templates/datatables_header.twig new file mode 100644 index 00000000..07487d5d --- /dev/null +++ b/templates/datatables_header.twig @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html xml:lang="{{applocale}}" lang="{{applocale}}" {{dir}}> + + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <link rel="stylesheet" href="/src/themes/{{theme}}/global.css" type="text/css" id="csstheme" /> + + <link rel="stylesheet" href="/src/themes/datatables.min.css" type="text/css" /> + + <link rel="shortcut icon" href="/images/themes/{{theme}}/Favicon.ico" type="image/vnd.microsoft.icon" /> + <link rel="icon" type="image/png" href="/images/themes/{{theme}}/Introduction.png" /> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> + + <script src="/js/datatables.min.js"></script> + + <title>{{appName}}</title> + + <script type="text/javascript"> + $(document).ready(function() { + if (window.parent.frames.length > 1) { + $('#csstheme', window.parent.frames[0].document).attr('href', '/src/themes/{{theme}}/global.css'); + } + }); + </script> diff --git a/templates/header.twig b/templates/header.twig index 3d7e31cc..053466b1 100644 --- a/templates/header.twig +++ b/templates/header.twig @@ -4,14 +4,11 @@ <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link rel="stylesheet" href="/src/themes/{{theme}}/global.css" type="text/css" id="csstheme" /> - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css" type="text/css" id="csstheme" /> - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.css" type="text/css" id="csstheme" /> + <link rel="shortcut icon" href="/images/themes/{{theme}}/Favicon.ico" type="image/vnd.microsoft.icon" /> <link rel="icon" type="image/png" href="/images/themes/{{theme}}/Introduction.png" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.full.min.js"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/i18n/es.js"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js"></script> + <title>{{appName}}</title> <script type="text/javascript"> diff --git a/templates/home.twig b/templates/home.twig index ae3f8997..a6446bd7 100644 --- a/templates/home.twig +++ b/templates/home.twig @@ -5,20 +5,18 @@ {% include 'home_header.twig' %} </head> - <frameset cols="{{cols}}"> - {% if rtl == true %} - <frame src="/src/views/intro.php" name="detail" id="detail" frameborder="0" /> - <frame src="/tree/browser" name="browser" id="browser" frameborder="0" /> {% else %} - <frame src="/tree/browser" name="browser" id="browser" frameborder="0" /> - <frame src="/src/views/intro.php" name="detail" id="detail" frameborder="0" /> {% endif %} + <body class="flexbox_body"> + <div class="flexbox_wrapper"> - <noframes> + <iframe src="/src/views/browser.php" name="browser" id="browser" class="browser_container" frameborder="0" /> + <p>Your browser does not support iframes.</p> + </iframe> - <body>{{strnoframes}} - <br /> - <a href="/src/views/intro">{{strnoframeslink}}</a> - </body> - </noframes> - </frameset> + <iframe src="/src/views/intro.php" name="detail" id="detail" class="detail_container" frameborder="0" /> + <p>Your browser does not support iframes.</p> + </iframe> + + </div> + </body> </html> diff --git a/templates/home_header.twig b/templates/home_header.twig index 07197f85..a0e9f074 100644 --- a/templates/home_header.twig +++ b/templates/home_header.twig @@ -1,13 +1,16 @@ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> -<link rel="stylesheet" href="/src/themes/default/global.css" type="text/css" id="csstheme" /> -<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css" type="text/css" id="csstheme" /> -<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.css" type="text/css" id="csstheme" /> +<link rel="stylesheet" href="/src/themes/global.css" type="text/css" id="csstheme" /> + <link rel="shortcut icon" href="/images/themes/default/Favicon.ico" type="image/vnd.microsoft.icon" /> <link rel="icon" type="image/png" href="/images/themes/default/Introduction.png" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> {# +<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css" type="text/css" id="csstheme" /> +<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.css" type="text/css" id="csstheme" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.full.min.js"></script> -<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/i18n/es.js"></script>#} +<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/i18n/es.js"></script> +<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js"></script> +#} <link rel="stylesheet" href="/js/codemirror/codemirror.css" /> @@ -22,6 +25,4 @@ <script src="/js/codemirror/addon/fold/foldgutter.js"></script> <script src="/js/codemirror/addon/fold/indent-fold.js"></script> -<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js"></script> - <title>phpPgAdmin - {{title}}</title> diff --git a/templates/home_rtl.twig b/templates/home_rtl.twig new file mode 100644 index 00000000..462ab9c6 --- /dev/null +++ b/templates/home_rtl.twig @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html lang="en"> + + <head> + {% include 'home_header.twig' %} + </head> + + <frameset cols="{{cols}}"> + + <frame src="/src/views/intro.php" name="detail" id="detail" frameborder="0" /> + <frame src="/tree/browser" name="browser" id="browser" frameborder="0" /> + + <noframes> + + <body>{{strnoframes}} + <br /> + <a href="/src/views/intro.php">{{strnoframeslink}}</a> + </body> + </noframes> + </frameset> + +</html> diff --git a/templates/select2_header.twig b/templates/select2_header.twig new file mode 100644 index 00000000..3d7e31cc --- /dev/null +++ b/templates/select2_header.twig @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html xml:lang="{{applocale}}" lang="{{applocale}}" {{dir}}> + + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <link rel="stylesheet" href="/src/themes/{{theme}}/global.css" type="text/css" id="csstheme" /> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css" type="text/css" id="csstheme" /> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.css" type="text/css" id="csstheme" /> + <link rel="shortcut icon" href="/images/themes/{{theme}}/Favicon.ico" type="image/vnd.microsoft.icon" /> + <link rel="icon" type="image/png" href="/images/themes/{{theme}}/Introduction.png" /> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.full.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/i18n/es.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js"></script> + <title>{{appName}}</title> + + <script type="text/javascript"> + $(document).ready(function() { + if (window.parent.frames.length > 1) { + $('#csstheme', window.parent.frames[0].document).attr('href', '/src/themes/{{theme}}/global.css'); + } + }); + </script> diff --git a/templates/sqledit.twig b/templates/sqledit.twig index 4f6b0995..2d466ce7 100644 --- a/templates/sqledit.twig +++ b/templates/sqledit.twig @@ -20,17 +20,16 @@ jQuery(document).ready(function() { gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], hintOptions: { tables: { - medios: { - id: null, - name: null, - categoria: null - }, - posts: { - id: null, - name: null, - id_medio: null, - created_date: null - } + "medios": ['id', 'name', 'categoria', 'table_name', 'category', 'url'], + "posts": ['id', 'name', 'id_medio', 'created_date'], + "comments": ['id', 'id_usuario', 'id_post', 'id_medio', 'created_time', 'message'], + "users": ['id', 'nombre', 'url'], + "reactions": ['id', 'id_usuario', 'id_post', 'id_medio', 'type'], + "public.medios": ['id', 'name', 'categoria', 'table_name', 'category', 'url'], + "public.posts": ['id', 'name', 'id_medio', 'created_date'], + "public.comments": ['id', 'id_usuario', 'id_post', 'id_medio', 'created_time', 'message'], + "public.users": ['id', 'nombre', 'url'], + "public.reactions": ['id', 'id_usuario', 'id_post', 'id_medio', 'type'] } } }); diff --git a/templates/sqledit_header.twig b/templates/sqledit_header.twig index 1f95893f..728a7c98 100644 --- a/templates/sqledit_header.twig +++ b/templates/sqledit_header.twig @@ -4,20 +4,16 @@ <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link rel="stylesheet" href="/src/themes/default/global.css" type="text/css" id="csstheme" /> - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css" type="text/css" id="csstheme" /> - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.css" type="text/css" id="csstheme" /> <link rel="shortcut icon" href="/images/themes/default/Favicon.ico" type="image/vnd.microsoft.icon" /> <link rel="icon" type="image/png" href="/images/themes/default/Introduction.png" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> - {# - <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.full.min.js"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/i18n/es.js"></script>#} - <link rel="stylesheet" href="/js/codemirror/codemirror.css" /> + <link href="/js/codemirror/addon/hint/show-hint.css" rel="stylesheet" /> + <link href="/js/codemirror/codemirror.css" rel="stylesheet" /> <script src="/js/codemirror/codemirror.js"></script> <script src="/js/codemirror/mode/sql/sql.js"></script> - <link rel="stylesheet" href="/js/codemirror/addon/hint/show-hint.css" /> + <script src="/js/codemirror/addon/hint/show-hint.js"></script> <script src="/js/codemirror/addon/hint/sql-hint.js"></script> @@ -26,8 +22,6 @@ <script src="/js/codemirror/addon/fold/foldgutter.js"></script> <script src="/js/codemirror/addon/fold/indent-fold.js"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js"></script> - <title>phpPgAdmin - {{title}}</title> </head> diff --git a/templates/table_list_footer.twig b/templates/table_list_footer.twig new file mode 100644 index 00000000..377e27ac --- /dev/null +++ b/templates/table_list_footer.twig @@ -0,0 +1,7 @@ +<script> +$(document).ready(function() { + $('.{{table_class}}').DataTable({ + "pageLength": 100 + }); +}); +</script> |