├── screenshot.png ├── forms └── import.php ├── conf ├── cli.php └── config.php ├── handlers ├── index.php ├── shell.php ├── info.php ├── export-json.php ├── drop.php ├── shell │ ├── export.php │ └── query.php ├── export.php ├── import-json.php ├── import.php ├── delete.php ├── browse.php ├── add.php └── edit.php ├── elefant.json ├── composer.json ├── views ├── shell.html ├── import.html ├── info.html └── index.html ├── README.md ├── js └── dbman.js └── lib └── DBMan.php /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LUX/dbman/master/screenshot.png -------------------------------------------------------------------------------- /forms/import.php: -------------------------------------------------------------------------------- 1 | ; 12 | -------------------------------------------------------------------------------- /conf/cli.php: -------------------------------------------------------------------------------- 1 | ; -------------------------------------------------------------------------------- /conf/config.php: -------------------------------------------------------------------------------- 1 | ; -------------------------------------------------------------------------------- /handlers/index.php: -------------------------------------------------------------------------------- 1 | layout = 'admin'; 4 | 5 | if (! User::require_admin ()) { 6 | header ('Location: /admin'); 7 | exit; 8 | } 9 | 10 | $f = new Form ('get', $this); 11 | $csrf_token = $f->generate_csrf_token (); 12 | 13 | $page->title = 'DB Manager'; 14 | 15 | $tables = DBMan::list_tables (); 16 | 17 | echo $tpl->render ('dbman/index', array ('tables' => $tables, 'csrf_token' => $csrf_token)); 18 | 19 | ?> -------------------------------------------------------------------------------- /elefant.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "app", 3 | "folder": "dbman", 4 | "name": "DB Manager", 5 | "version": "0.9.0", 6 | "website": "http://www.elefantcms.com/", 7 | "repository": "git://github.com/jbroadway/dbman.git", 8 | "author": { 9 | "name": "Johnny Broadway", 10 | "email": "johnny@johnnybroadway.com" 11 | }, 12 | "requires": { 13 | "php": "5.3.2", 14 | "elefant": "1.0.1" 15 | } 16 | } -------------------------------------------------------------------------------- /handlers/shell.php: -------------------------------------------------------------------------------- 1 | layout = 'admin'; 4 | 5 | $this->require_admin (); 6 | 7 | $f = new Form ('post', $this); 8 | $csrf_token = $f->generate_csrf_token (); 9 | 10 | 11 | $page->title = __ ('SQL Shell'); 12 | 13 | $page->add_script ('/apps/dbman/js/dbman.js?v=2'); 14 | $page->add_script (I18n::export ( 15 | 'Error', 16 | 'Query executed.', 17 | 'Please wait...', 18 | 'results', 19 | 'Export' 20 | )); 21 | echo $tpl->render ('dbman/shell', array ('query' => $_POST['query'], 'csrf_token' => $csrf_token)); 22 | 23 | ?> -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elefant/app-dbman", 3 | "type": "elefant-app", 4 | "description": "DB Manager app for the Elefant CMS", 5 | "keywords": ["db", "database", "admin", "elefant", "cms", "app"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Johnny Broadway", 10 | "email": "johnny@johnnybroadway.com", 11 | "homepage": "http://www.johnnybroadway.com/" 12 | } 13 | ], 14 | "repositories": [ 15 | {"type": "git", "url": "http://github.com/jbroadway/elefant_installer"} 16 | ], 17 | "require": { 18 | "elefant/app-installer": "*" 19 | } 20 | } -------------------------------------------------------------------------------- /handlers/info.php: -------------------------------------------------------------------------------- 1 | layout = 'admin'; 4 | 5 | if (! User::require_admin ()) { 6 | header ('Location: /admin'); 7 | exit; 8 | } 9 | 10 | $f = new Form ('get', $this); 11 | if (! $f->verify_csrf ()) { 12 | header ('Location: /admin'); 13 | exit; 14 | } 15 | 16 | if (! preg_match ('/^[a-zA-Z0-9_]+$/', $_GET['table'])) { 17 | header ('Location: /admin'); 18 | exit; 19 | } 20 | 21 | $page->title = i18n_get ('Table Info') . ': ' . $_GET['table']; 22 | 23 | $columns = DBMan::table_info ($_GET['table']); 24 | 25 | echo $tpl->render ('dbman/info', array ('table' => $_GET['table'], 'columns' => $columns)); 26 | 27 | ?> -------------------------------------------------------------------------------- /handlers/export-json.php: -------------------------------------------------------------------------------- 1 | cli) { 9 | die ('Must be run from the command line.'); 10 | } 11 | 12 | $page->layout = false; 13 | 14 | set_time_limit (0); 15 | 16 | if (count ($_SERVER['argv']) > 2) { 17 | $tables = $_SERVER['argv']; 18 | array_shift ($tables); 19 | array_shift ($tables); 20 | } else { 21 | $tables = DBMan::list_tables (); 22 | } 23 | $export = array (); 24 | 25 | foreach ($tables as $table) { 26 | $export[$table] = DB::fetch ('select * from ' . $table); 27 | } 28 | 29 | echo json_encode ($export); 30 | -------------------------------------------------------------------------------- /views/shell.html: -------------------------------------------------------------------------------- 1 | {! admin/util/codemirror?field_id=query&mode=sql !} 2 | 3 |

« {"Back"}

4 | 5 |
6 |


7 | 8 |

9 |
10 | 11 |
12 | 13 | 22 | 23 | 31 | -------------------------------------------------------------------------------- /handlers/drop.php: -------------------------------------------------------------------------------- 1 | layout = 'admin'; 4 | 5 | if (! User::require_admin ()) { 6 | header ('Location: /admin'); 7 | exit; 8 | } 9 | 10 | $f = new Form ('get', $this); 11 | if (! $f->verify_csrf ()) { 12 | header ('Location: /admin'); 13 | exit; 14 | } 15 | 16 | if (! preg_match ('/^[a-zA-Z0-9_]+$/', $_GET['table'])) { 17 | header ('Location: /admin'); 18 | exit; 19 | } 20 | 21 | if (! db_execute ('drop table `' . $_GET['table'] . '`')) { 22 | $page->title = i18n_get ('Error'); 23 | printf ("

« %s

\n", i18n_get ('Back')); 24 | echo '

' . db_error () . '

'; 25 | return; 26 | } 27 | 28 | $this->add_notification (i18n_get ('Table Dropped') . ': ' . $_GET['table']); 29 | $this->redirect ('/dbman/index'); 30 | 31 | ?> -------------------------------------------------------------------------------- /views/import.html: -------------------------------------------------------------------------------- 1 |

« {"Back"}

2 | 3 |
4 | 5 |

6 | {"CSV file"}: {"Please upload a CSV file."}
7 | 8 |

9 | 10 |

11 | {"Import into table"}: {"Please choose a table to import into."}
12 | 18 |

19 | 20 |

21 | 22 |

23 | 24 |
25 | -------------------------------------------------------------------------------- /views/info.html: -------------------------------------------------------------------------------- 1 |

« {"Back"}

2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% foreach columns %} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% end %} 25 |
{"Column"}{"Type"}{"Length"}{"Null"}{"Key"}{"Default"}{"Extra"}
{{ loop_value->name }} {{ loop_value->type }} {{ loop_value->length }} {{ loop_value->notnull }} {{ loop_value->key }} {{ loop_value->default }} {{ loop_value->extra }} 
26 |

27 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 |

2 | {"SQL Shell"} 3 |  |  4 | {"CSV Importer"} 5 |

6 | 7 |

8 | 9 | 10 | 11 | 12 | 13 | 14 | {% foreach tables %} 15 | 16 | 17 | 18 | 19 | 20 | {% end %} 21 |
{"Table"}{"Count"} 
{{ loop_value }}{{ loop_value|DBMan::count }}{"Info"} | {"Export"} | {"Drop"}
22 |

23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a simple database manager app for the [Elefant CMS](http://github.com/jbroadway/elefant). 2 | To install, unzip it into your apps folder, and you'll see "DB Manager" appear in the Tools menu. 3 | 4 | > Note: This app should **not** be used on a production website, as it provides a level 5 | > of direct access to your database that is not intended for live website editing. 6 | 7 | ### Features include: 8 | 9 | * List/describe tables 10 | * Browse rows 11 | * Add/edit/delete rows 12 | * Input validation 13 | * Import/export as CSV 14 | * SQL shell with CSV export 15 | * Drop tables 16 | * jQuery UI date/time selectors 17 | * Command line import/export as JSON 18 | * Tables with up to 3-column primary keys 19 | * Configurable joins between tables or to PHP callbacks 20 | 21 | > Note: Command line JSON importer will clear the database table contents. 22 | 23 | ### Screenshot 24 | 25 | ![DB Manager screenshot](https://github.com/jbroadway/dbman/raw/master/screenshot.png) 26 | -------------------------------------------------------------------------------- /handlers/shell/export.php: -------------------------------------------------------------------------------- 1 | require_admin (); 4 | 5 | $f = new Form ('post', $this); 6 | if (! $f->verify_csrf ()) { 7 | header ('Location: /admin'); 8 | exit; 9 | } 10 | 11 | if (! isset ($_POST['query'])) { 12 | header ('Location: /admin'); 13 | exit; 14 | } 15 | 16 | $page->layout = false; 17 | header ('Cache-control: private'); 18 | header ('Content-Type: text/plain'); 19 | header ('Content-Disposition: attachment; filename=query-export-' . gmdate ('Y-m-d') . '.csv'); 20 | 21 | $res = DB::fetch ($_POST['query']); 22 | echo preg_replace ('/[\r\n]+/', ' ', $_POST['query']) . "\n"; 23 | if (count ($res) > 0) { 24 | echo join (',', array_keys ((array) $res[0])) . "\n"; 25 | } 26 | 27 | foreach ($res as $row) { 28 | $sep = ''; 29 | foreach ((array) $row as $k => $v) { 30 | $v = str_replace ('"', '""', $v); 31 | if (strpos ($v, ',') !== false) { 32 | $v = '"' . $v . '"'; 33 | } 34 | $v = str_replace (array ("\n", "\r"), array ('\\n', '\\r'), $v); 35 | echo $sep . $v; 36 | $sep = ','; 37 | } 38 | echo "\n"; 39 | } 40 | 41 | ?> -------------------------------------------------------------------------------- /handlers/export.php: -------------------------------------------------------------------------------- 1 | verify_csrf ()) { 10 | header ('Location: /admin'); 11 | exit; 12 | } 13 | 14 | if (! isset ($_GET['table'])) { 15 | header ('Location: /admin'); 16 | exit; 17 | } 18 | 19 | if (! preg_match ('/^[a-zA-Z0-9_]+$/', $_GET['table'])) { 20 | header ('Location: /admin'); 21 | exit; 22 | } 23 | 24 | $page->layout = false; 25 | header ('Cache-control: private'); 26 | header ('Content-Type: text/plain'); 27 | header ('Content-Disposition: attachment; filename=' . $_GET['table'] . '-' . gmdate ('Y-m-d') . '.csv'); 28 | 29 | $res = db_fetch_array ('select * from `' . $_GET['table'] . '`'); 30 | if (count ($res) > 0) { 31 | echo join (',', array_keys ((array) $res[0])) . "\n"; 32 | } 33 | 34 | foreach ($res as $row) { 35 | $sep = ''; 36 | foreach ((array) $row as $k => $v) { 37 | $v = str_replace ('"', '""', $v); 38 | if (strpos ($v, ',') !== false) { 39 | $v = '"' . $v . '"'; 40 | } 41 | $v = str_replace (array ("\n", "\r"), array ('\\n', '\\r'), $v); 42 | echo $sep . $v; 43 | $sep = ','; 44 | } 45 | echo "\n"; 46 | } 47 | 48 | ?> -------------------------------------------------------------------------------- /handlers/shell/query.php: -------------------------------------------------------------------------------- 1 | require_admin (); 4 | 5 | $f = new Form ('post', $this); 6 | if (! $f->verify_csrf ()) { 7 | header ('Location: /admin'); 8 | exit; 9 | } 10 | 11 | $page->layout = false; 12 | 13 | header ('Content-Type: application/json'); 14 | 15 | if (! isset ($_POST['query']) || empty ($_POST['query'])) { 16 | echo json_encode (array ( 17 | 'success' => false, 18 | 'error' => __ ('No query specified') 19 | )); 20 | return; 21 | } 22 | 23 | $queries = explode (';', $_POST['query']); 24 | $res = array (); 25 | 26 | foreach ($queries as $query) { 27 | $query = trim ($query); 28 | if ($query === '') { 29 | continue; 30 | } 31 | 32 | $exec = preg_match ('/^(alter|create|insert|update|delete|drop) /i', $query); 33 | if ($exec) { 34 | $cur = array ( 35 | 'sql' => Template::sanitize ($query), 36 | 'headers' => array (), 37 | 'results' => DB::execute ($query), 38 | 'error' => false, 39 | 'exec' => $exec 40 | ); 41 | } else { 42 | $cur = array ( 43 | 'sql' => Template::sanitize ($query), 44 | 'headers' => array (), 45 | 'results' => DB::fetch ($query), 46 | 'error' => false, 47 | 'exec' => $exec 48 | ); 49 | } 50 | 51 | if ($cur['results'] === false) { 52 | $cur['error'] = DB::error (); 53 | } elseif (count ($cur['results']) > 0) { 54 | $cur['headers'] = array_keys ((array) $cur['results'][0]); 55 | } 56 | 57 | $res[] = $cur; 58 | } 59 | 60 | echo json_encode (array ( 61 | 'success' => true, 62 | 'data' => $res 63 | )); 64 | 65 | ?> -------------------------------------------------------------------------------- /handlers/import-json.php: -------------------------------------------------------------------------------- 1 | cli) { 12 | die ('Must be run from the command line.'); 13 | } 14 | 15 | $page->layout = false; 16 | 17 | if (! isset ($_SERVER['argv'][2])) { 18 | Cli::out ('Usage: ./elefant import-db ', 'info'); 19 | die; 20 | } 21 | 22 | $file = $_SERVER['argv'][2]; 23 | if (! file_exists ($file)) { 24 | Cli::out ('** Error: File not found: ' . $file, 'error'); 25 | die; 26 | } 27 | 28 | set_time_limit (0); 29 | 30 | $import = json_decode (file_get_contents ($file)); 31 | $count = 0; 32 | 33 | DB::beginTransaction (); 34 | 35 | foreach ($import as $table => $rows) { 36 | if (count ($rows) > 0) { 37 | DB::execute ('delete from ' . $table); 38 | foreach ($rows as $n => $row) { 39 | $row = (array) $row; 40 | $keys = Model::backticks (array_keys ($row)); 41 | $sql = 'insert into ' . $table . ' (' . join (', ', $keys) . ') values ('; 42 | $vals = array_values ($row); 43 | 44 | $sep = ''; 45 | foreach ($vals as $k => $val) { 46 | $sql .= $sep . '?'; 47 | $sep = ', '; 48 | $vals[$k] = ($val === null) ? '' : $val; 49 | } 50 | $sql .= ')'; 51 | 52 | if (! DB::execute ($sql, $vals)) { 53 | Cli::out ('** Error: ' . DB::error (), 'error'); 54 | DB::rollback (); 55 | return; 56 | } 57 | 58 | $count++; 59 | } 60 | } 61 | } 62 | 63 | DB::commit (); 64 | 65 | Cli::out ($count . ' commands executed.', 'success'); 66 | -------------------------------------------------------------------------------- /handlers/import.php: -------------------------------------------------------------------------------- 1 | layout = 'admin'; 4 | 5 | $this->require_admin (); 6 | 7 | $page->title = __ ('Import CSV Data'); 8 | 9 | $form = new Form ('post', $this); 10 | 11 | $form->data = array ( 12 | 'tables' => DBMan::list_tables () 13 | ); 14 | 15 | ini_set ('auto_detect_line_endings', true); 16 | set_time_limit (0); 17 | 18 | echo $form->handle (function ($form) use ($page, $tpl) { 19 | $count = 0; 20 | 21 | $key = DBMan::primary_key ($_POST['table']); 22 | $headers = false; 23 | 24 | if (($f = fopen ($_FILES['data']['tmp_name'], 'r')) !== false) { 25 | while (($row = fgetcsv ($f, 0, ',')) !== false) { 26 | // optionally skip first line as header 27 | if ($headers === false) { 28 | $headers = $row; 29 | continue; 30 | } 31 | 32 | $data = array_combine ($headers, $row); 33 | $obj = new Model ($data); 34 | $obj->table = $_POST['table']; 35 | $obj->key = $key; 36 | 37 | if (! $obj->put ()) { 38 | fclose ($f); 39 | unlink ($_FILES['data']['tmp_name']); 40 | printf ('

%s: %s

', __ ('Import failed'), $obj->error); 41 | printf ('

« %s

', __ ('Back')); 42 | return; 43 | } 44 | 45 | $count++; 46 | } 47 | fclose ($f); 48 | unlink ($_FILES['data']['tmp_name']); 49 | printf ('

%d %s

', $count, __ ('imported.')); 50 | printf ('

%s »

', $_POST['table'], __ ('Continue')); 51 | } else { 52 | printf ('

%s

', __ ('Import failed.')); 53 | printf ('

« %s

', __ ('Back')); 54 | } 55 | }); 56 | 57 | ?> -------------------------------------------------------------------------------- /handlers/delete.php: -------------------------------------------------------------------------------- 1 | require_admin (); 4 | 5 | $f = new Form ('post', $this); 6 | if (! $f->verify_csrf ()) { 7 | header ('Location: /admin'); 8 | exit; 9 | } 10 | 11 | if (! isset ($_POST['table'])) { 12 | header ('Location: /admin'); 13 | exit; 14 | } 15 | 16 | if (! preg_match ('/^[a-zA-Z0-9_]+$/', $_POST['table'])) { 17 | header ('Location: /admin'); 18 | exit; 19 | } 20 | 21 | $page->layout = 'admin'; 22 | 23 | $pkey = DBMan::primary_key ($_POST['table']); 24 | 25 | if (is_array ($pkey)) { 26 | if (count ($pkey) == 2) { 27 | $sql = sprintf ( 28 | 'delete from `%s` where (`%s` = ? and `%s` = ?)', 29 | $_POST['table'], 30 | $pkey[0], 31 | $pkey[1] 32 | ); 33 | } elseif (count ($pkey) == 3) { 34 | $sql = sprintf ( 35 | 'delete from `%s` where (`%s` = ? and `%s` = ? and `%s` = ?)', 36 | $_POST['table'], 37 | $pkey[0], 38 | $pkey[1], 39 | $pkey[2] 40 | ); 41 | } 42 | } else { 43 | $sql = sprintf ( 44 | 'delete from `%s` where %s = ?', 45 | $_POST['table'], 46 | DBMan::primary_key ($_POST['table']) 47 | ); 48 | } 49 | 50 | if (is_array ($_POST['key'])) { 51 | foreach ($_POST['key'] as $key) { 52 | $key = is_array ($pkey) ? explode ('|', $key) : $key; 53 | 54 | if (! DB::execute ($sql, $key)) { 55 | $this->add_notification (__ ('An Error Occurred') . ': ' . DB::error ()); 56 | $this->redirect ('/dbman/browse?table=' . urlencode ($_POST['table'])); 57 | } 58 | } 59 | $this->add_notification (count ($_POST['key']) . ' ' . __ ('items deleted.')); 60 | $this->redirect ('/dbman/browse?table=' . urlencode ($_POST['table'])); 61 | } else { 62 | if (DB::execute ($sql, $_POST['key'])) { 63 | $this->add_notification (__ ('Item deleted.')); 64 | $this->redirect ('/dbman/browse?table=' . urlencode ($_POST['table'])); 65 | } 66 | } 67 | 68 | $page->title = __ ('An Error Occurred'); 69 | printf ("

%s

\n

« %s

\n", DB::error (), Template::sanitize ($_POST['table']), __ ('Back')); 70 | 71 | ?> -------------------------------------------------------------------------------- /js/dbman.js: -------------------------------------------------------------------------------- 1 | var dbman = (function ($) { 2 | var self = {}; 3 | 4 | /** 5 | * Escape a value for output. 6 | */ 7 | self.esc = function (html) { 8 | return String(html) 9 | .replace (/&/g, '&') 10 | .replace (//g, '>') 12 | .replace (/"/g, '"') 13 | .replace (/'/g, '''); 14 | }; 15 | 16 | /** 17 | * Turns a link into a POST submission with the `data-*` properties 18 | * as parameters. Usage: 19 | * 20 | * {"Post me"} 25 | */ 26 | self.post = function (el) { 27 | if (window.event) { 28 | window.event.preventDefault (); 29 | } 30 | 31 | var $el = $(el), 32 | params = $el.data (), 33 | url = $el.attr ('href'), 34 | $form = $('
') 35 | .attr ('method', 'post') 36 | .attr ('action', url); 37 | 38 | $.each (params, function (name, value) { 39 | $('') 40 | .attr ('name', name) 41 | .attr ('value', value) 42 | .appendTo ($form); 43 | }); 44 | 45 | $form.appendTo ('body'); 46 | $form.submit (); 47 | return false; 48 | }; 49 | 50 | /** 51 | * Makes an AJAX call for the results of an SQL query. 52 | */ 53 | self.query = function (query, csrf_token) { 54 | var params = {query: query, _token_: csrf_token}; 55 | console.log (params); 56 | if (! query) { 57 | return; 58 | } 59 | 60 | $('#results').html ($.i18n ('Please wait...')); 61 | 62 | $.post ('/dbman/shell/query', params, function (res) { 63 | if (! res.success) { 64 | $.add_notification (res.error); 65 | return; 66 | } 67 | 68 | var results = $('#results').html (''); 69 | 70 | for (var q in res.data) { 71 | var sql = res.data[q].sql, 72 | headers = res.data[q].headers, 73 | rows = res.data[q].results; 74 | 75 | results.append ('
' + dbman.esc (sql) + '
'); 76 | 77 | if (res.data[q].error) { 78 | results.append ('

' + $.i18n ('Error') + ': ' + res.data[q].error); 79 | continue; 80 | } 81 | 82 | if (res.data[q].exec) { 83 | results.append ('

' + $.i18n ('Query executed.') + '

'); 84 | continue; 85 | } 86 | 87 | results.append ( 88 | '

' + 89 | res.data[q].results.length + ' ' + $.i18n ('results') + ' (' + 90 | '' + $.i18n ('Export') + 92 | '):' + 93 | '

' 94 | ); 95 | 96 | var table = '

'; 97 | 98 | for (var h in res.data[q].headers) { 99 | table += ''; 100 | } 101 | table += ''; 102 | 103 | for (var i = 0; i < res.data[q].results.length; i++) { 104 | table += ''; 105 | for (var k in res.data[q].results[i]) { 106 | table += ''; 107 | } 108 | table += ''; 109 | } 110 | 111 | table += '
' + dbman.esc (res.data[q].headers[h]) + '
' + dbman.esc (res.data[q].results[i][k]) + '

'; 112 | 113 | results.append (table); 114 | } 115 | }); 116 | 117 | return false; 118 | }; 119 | 120 | /** 121 | * Delete the selected items. 122 | */ 123 | self.delete = function () { 124 | if (! confirm ($.i18n ('Are you sure you want to delete these items?'))) { 125 | return false; 126 | } 127 | 128 | $('#delete-form')[0].submit (); 129 | return false; 130 | }; 131 | 132 | return self; 133 | })(jQuery); -------------------------------------------------------------------------------- /handlers/browse.php: -------------------------------------------------------------------------------- 1 | layout = 'admin'; 4 | 5 | $this->require_admin (); 6 | 7 | $f = new Form ('get', $this); 8 | $csrf_token = $f->generate_csrf_token (); 9 | 10 | if (! isset ($_GET['table'])) { 11 | header ('Location: /dbman/index'); 12 | exit; 13 | } 14 | 15 | $limit = 20; 16 | $num = (isset ($_GET['num'])) ? $_GET['num'] : 1; 17 | $_GET['offset'] = ($num - 1) * $limit; 18 | 19 | $page->title = __ ('Table') . ': ' . Template::sanitize ($_GET['table']); 20 | 21 | $this->run ('admin/util/fontawesome'); 22 | $page->add_script ('/apps/dbman/js/dbman.js'); 23 | $page->add_script (I18n::export ( 24 | 'Are you sure you want to delete these items?' 25 | )); 26 | 27 | $pkey = DBMan::primary_key ($_GET['table']); 28 | $count = DB::shift ('select count(*) from `' . $_GET['table'] . '`'); 29 | $res = DB::fetch ('select * from `' . $_GET['table'] . '` limit ' . $limit . ' offset ' . $_GET['offset']); 30 | $more = ($count > $_GET['offset'] + $limit); 31 | $prev = $_GET['offset'] - $limit; 32 | $next = $_GET['offset'] + $limit; 33 | 34 | if (count ($res) > 0) { 35 | $headers = array_keys ((array) $res[0]); 36 | } else { 37 | $headers = array (); 38 | } 39 | 40 | printf ( 41 | "

« %s | %s | %s

\n", 42 | __ ('Back'), 43 | Template::sanitize ($_GET['table']), 44 | __ ('Add Item'), 45 | Template::sanitize ($_GET['table']), 46 | $csrf_token, 47 | __ ('Table Info') 48 | ); 49 | 50 | echo "\n"; 51 | 52 | echo '

' . $count . ' ' . __ ('results') . ":

\n"; 53 | 54 | if ($count > $limit) { 55 | echo '
' . $this->run ('navigation/pager', array ( 56 | 'style' => 'numbers', 57 | 'url' => '/dbman/browse?table=' . $_GET['table'] . '&num=%d', 58 | 'total' => $count, 59 | 'count' => count ($res), 60 | 'limit' => $limit 61 | )) . '
'; 62 | } 63 | 64 | echo "\n"; 65 | echo "\n"; 66 | echo "\n"; 67 | echo "\n"; 68 | foreach ($headers as $header) { 69 | printf ("\n", $header); 70 | } 71 | echo "\n"; 72 | foreach ($res as $row) { 73 | echo "\n"; 74 | foreach ((array) $row as $k => $v) { 75 | if (strlen ($v) > 48) { 76 | printf ( 77 | "\n", 78 | Template::sanitize ($v), 79 | Template::sanitize (substr ($v, 0, 45)) 80 | ); 81 | } else { 82 | printf ("\n", Template::sanitize ($v)); 83 | } 84 | } 85 | printf ( 86 | "\n", 87 | Template::sanitize ($_GET['table']), 88 | DBMan::pkey_value ($row, $pkey), 89 | $csrf_token, 90 | __ ('Edit'), 91 | DBMan::pkey_value ($row, $pkey) 92 | ); 93 | echo "\n"; 94 | } 95 | echo "
%s 
%s...%s%s |
\n"; 96 | echo "
\n"; 97 | 98 | if ($count > $limit) { 99 | echo $this->run ('navigation/pager', array ( 100 | 'style' => 'numbers', 101 | 'url' => '/dbman/browse?table=' . urlencode ($_GET['table']) . '&num=%d', 102 | 'total' => $count, 103 | 'count' => count ($res), 104 | 'limit' => $limit 105 | )); 106 | } 107 | 108 | ?> -------------------------------------------------------------------------------- /lib/DBMan.php: -------------------------------------------------------------------------------- 1 | type); 38 | $info = (object) array ( 39 | 'name' => $row->name, 40 | 'type' => $type['type'], 41 | 'length' => $type['length'], 42 | 'notnull' => ($row->notnull == 1) ? 'No' : 'Yes', 43 | 'key' => ($row->pk == 1) ? 'Primary' : '', 44 | 'default' => trim ($row->dflt_value, '"'), 45 | 'extra' => '', 46 | 'original' => $row 47 | ); 48 | 49 | if (isset ($joins[$table][$info->name])) { 50 | $info->type = 'select'; 51 | $info->values = DBMan::select_values ($table, $info, $row, $joins); 52 | } 53 | $out[] = $info; 54 | } 55 | break; 56 | 57 | case 'mysql': 58 | $res = db_fetch_array ('describe `' . $table . '`'); 59 | foreach ($res as $row) { 60 | $type = DBMan::parse_type ($row->Type); 61 | $info = (object) array ( 62 | 'name' => $row->Field, 63 | 'type' => $type['type'], 64 | 'length' => $type['length'], 65 | 'notnull' => ($row->Null == 'NO') ? 'No' : 'Yes', 66 | 'key' => ($row->Key == 'PRI') ? 'Primary' : (! empty ($row->Key)) ? 'Secondary' : '', 67 | 'default' => $row->Default, 68 | 'extra' => $row->Extra, 69 | 'original' => $row 70 | ); 71 | 72 | if ($info->type === 'enum') { 73 | $info->values = DBMan::enum_values ($row->Type); 74 | } 75 | 76 | if (isset ($joins[$table][$info->name])) { 77 | $info->type = 'select'; 78 | $info->values = DBMan::select_values ($table, $info, $row, $joins); 79 | } 80 | $out[] = $info; 81 | } 82 | break; 83 | } 84 | return $out; 85 | } 86 | 87 | /** 88 | * Return the primary key field of a table. Note that this currently 89 | * only supports tables with single-field primary keys. 90 | */ 91 | public static function primary_key ($table) { 92 | switch (DBMan::driver ()) { 93 | case 'sqlite': 94 | $res = db_fetch_array ('pragma table_info(' . $table . ')'); 95 | $rows = []; 96 | 97 | foreach ($res as $row) { 98 | if ($row->pk == 1) { 99 | $rows[] = $row->name; 100 | } 101 | } 102 | 103 | if (count ($rows) == 1) { 104 | return $rows[0]; 105 | } elseif (count ($rows) > 1) { 106 | return $rows; 107 | } 108 | 109 | break; 110 | 111 | case 'mysql': 112 | $res = db_fetch_array ('describe `' . $table . '`'); 113 | $rows = []; 114 | 115 | foreach ($res as $row) { 116 | if ($row->Key == 'PRI') { 117 | $rows[] = $row->Field; 118 | } 119 | } 120 | 121 | if (count ($rows) == 1) { 122 | return $rows[0]; 123 | } elseif (count ($rows) > 1) { 124 | return $rows; 125 | } 126 | 127 | break; 128 | } 129 | return false; 130 | } 131 | 132 | /** 133 | * Takes the primary_key() info and a database row and returns the 134 | * primary key value for it. If it's a single-column primary key, 135 | * the value is the column value. If it's a multi-column primary key, 136 | * the values are joined by a pipe character. 137 | */ 138 | public static function pkey_value ($row, $pkey) { 139 | if (is_array ($pkey)) { 140 | $sep = ''; 141 | $val = ''; 142 | foreach ($pkey as $key) { 143 | $val .= $sep . $row->{$key}; 144 | $sep = '|'; 145 | } 146 | return $val; 147 | } 148 | return $row->{$pkey}; 149 | } 150 | 151 | /** 152 | * Number of rows in a table. 153 | */ 154 | public static function count ($table) { 155 | return db_shift ('select count(*) from ' . $table); 156 | } 157 | 158 | /** 159 | * Parse a type string from a database column and return an 160 | * array with type, length, and other. For example: 161 | * 162 | * text -> type:'text', length:'', other:'' 163 | * 164 | * int(11) -> type:'int', length:'11', other:'' 165 | * 166 | * enum("yes","no") -> type:'enum', length:'', other:'"yes","no"' 167 | */ 168 | public static function parse_type ($type) { 169 | if (strpos ($type, '(') !== false) { 170 | list ($type, $length) = explode ('(', $type); 171 | $length = trim ($length, ')'); 172 | if (is_numeric ($length)) { 173 | return array ('type' => $type, 'length' => $length, 'other' => ''); 174 | } else { 175 | return array ('type' => $type, 'length' => '', 'other' => $length); 176 | } 177 | } 178 | return array ('type' => $type, 'length' => '', 'other' => ''); 179 | } 180 | 181 | /** 182 | * Retrieve the values for an enum type. 183 | */ 184 | public static function enum_values ($type) { 185 | $type = substr ($type, 6, -2); 186 | return explode ("','", stripslashes ($type)); 187 | } 188 | 189 | /** 190 | * Retrieve the values for a select type (fields in the [Joins] config block). 191 | */ 192 | public static function select_values ($table, $info, $row, $joins) { 193 | $default = is_numeric ($info->default) ? (int) $info->default : $info->default; 194 | $values = []; 195 | 196 | if (preg_match ('/^`.*`$/', $joins[$table][$info->name])) { 197 | $method = substr ($joins[$table][$info->name], 1, -1); 198 | $values = call_user_func ($method); 199 | 200 | } else { 201 | list ($other_table, $key_field, $value_field) = explode ('.', $joins[$table][$info->name]); 202 | $values = DB::pairs ('select `' . $key_field . '`, `' . $value_field . '` from `' . $other_table . '` order by `' . $value_field . '` asc'); 203 | } 204 | 205 | if (! isset ($values[$default])) { 206 | $values = [$default => __ ('- default value -')] + $values; 207 | } 208 | 209 | //info ($default, true); 210 | //info ($values); 211 | 212 | return $values; 213 | } 214 | 215 | /** 216 | * Get form rules for a field. 217 | */ 218 | public static function get_rules ($field) { 219 | $rules = array (); 220 | 221 | // skip auto-incrementing fields 222 | if (DBMan::is_auto_incrementing ($field)) { 223 | return $rules; 224 | } 225 | 226 | // ensure non-nullable fields aren't empty 227 | $empty_ok = array ('char', 'varchar', 'text', 'tinytext', 'mediumtext', 'longtext', 'blob', 'tinyblob', 'mediumblob', 'longblob', 'select'); 228 | if ($field->notnull == 'No' && ! in_array ($field->type, $empty_ok)) { 229 | $rules['not empty'] = 1; 230 | } else { 231 | $rules['skip_if_empty'] = 1; 232 | } 233 | 234 | if (in_array ($field->type, array ('int', 'integer', 'float'))) { 235 | $rules['type'] = 'numeric'; 236 | } 237 | 238 | if ($field->length != '') { 239 | $rules['length'] = $field->length . '-'; 240 | } 241 | return $rules; 242 | } 243 | 244 | /** 245 | * Determine whether the specified field is auto-incrementing. 246 | */ 247 | public static function is_auto_incrementing ($field) { 248 | // skip auto-incrementing fields 249 | if (DBMan::driver () == 'sqlite' && $field->type == 'integer' && $field->key == 'Primary') { 250 | return true; 251 | } 252 | if (strtolower ($field->extra) == 'auto_increment') { 253 | return true; 254 | } 255 | return false; 256 | } 257 | } 258 | 259 | ?> -------------------------------------------------------------------------------- /handlers/add.php: -------------------------------------------------------------------------------- 1 | layout = 'admin'; 14 | $page->title = i18n_get ('Add') . ' ' . $_GET['table']; 15 | 16 | // get the field details of the table so we can dynamically generate the form 17 | $fields = DBMan::table_info ($_GET['table']); 18 | 19 | $f = new Form ('post'); 20 | 21 | // generate rules for required fields 22 | foreach ($fields as $field) { 23 | $f->rules[$field->name] = DBMan::get_rules ($field); 24 | } 25 | 26 | if ($f->submit ()) { 27 | unset ($_POST['_token_']); 28 | 29 | // add item 30 | $obj = new Model ($_POST); 31 | $obj->table = $_GET['table']; 32 | 33 | if ($obj->put ()) { 34 | $this->add_notification (i18n_get ('Item added.')); 35 | $this->redirect ('/dbman/browse?table=' . $_GET['table']); 36 | } 37 | $page->title = i18n_get ('An Error Occurred'); 38 | printf ("

%s

\n

« %s

\n", $obj->error, $_GET['table'], i18n_get ('Back')); 39 | return; 40 | } 41 | 42 | // generate the form 43 | $o = new StdClass; 44 | 45 | // set default values 46 | foreach ($fields as $field) { 47 | if (! empty ($field->default)) { 48 | $o->{$field->name} = $field->default; 49 | } 50 | } 51 | 52 | $o = $f->merge_values ($o); 53 | $o->failed = $f->failed; 54 | echo "
\n"; 55 | 56 | $timepicker_loaded = false; 57 | 58 | // generate the form fields 59 | foreach ($fields as $field) { 60 | // disable auto-incrementing fields 61 | if (DBMan::is_auto_incrementing ($field)) { 62 | printf ( 63 | '

%s:
%s

' . "\n", 64 | $field->name, 65 | $field->name, 66 | i18n_get ('Auto-incrementing field') 67 | ); 68 | continue; 69 | } 70 | 71 | if (isset ($f->rules[$field->name]['type']) && $f->rules[$field->name]['type'] == 'numeric') { 72 | $rule = ' ' . i18n_getf ('You must enter a number for %s', $field->name) . ''; 73 | } elseif (isset ($f->rules[$field->name]['length'])) { 74 | $rule = ' ' . i18n_getf ('You must enter a value for %s no longer than %s', $field->name, $field->length) . ''; 75 | } elseif (isset ($f->rules[$field->name]['not empty'])) { 76 | $rule = ' ' . i18n_getf ('You must enter a value for %s', $field->name) . ''; 77 | } else { 78 | $rule = ''; 79 | } 80 | 81 | switch ($field->type) { 82 | case 'text': 83 | case 'mediumtext': 84 | printf ( 85 | '

%s:
%s

' . "\n", 86 | $field->name, 87 | $field->name, 88 | $field->name, 89 | Template::quotes ($o->{$field->name}), 90 | $rule 91 | ); 92 | break; 93 | case 'date': 94 | if (! $timepicker_loaded) { 95 | $page->add_script ('/js/jquery-ui/jquery-ui.css'); 96 | $page->add_script ('/js/jquery-ui/jquery-ui.min.js'); 97 | $page->add_script ( 98 | '' 106 | ); 107 | $page->add_script ('/apps/blog/js/jquery.timepicker.js'); 108 | $timepicker_loaded = true; 109 | } 110 | printf ( 111 | '

%s:
%s

' . "\n", 112 | $field->name, 113 | $field->name, 114 | $field->name, 115 | Template::quotes ($o->{$field->name}), 116 | $rule 117 | ); 118 | printf ( 119 | "\n", 120 | $field->name 121 | ); 122 | break; 123 | case 'time': 124 | if (! $timepicker_loaded) { 125 | $page->add_script ('/js/jquery-ui/jquery-ui.css'); 126 | $page->add_script ('/js/jquery-ui/jquery-ui.min.js'); 127 | $page->add_script ( 128 | '' 136 | ); 137 | $page->add_script ('/apps/blog/js/jquery.timepicker.js'); 138 | $timepicker_loaded = true; 139 | } 140 | printf ( 141 | '

%s:
%s

' . "\n", 142 | $field->name, 143 | $field->name, 144 | $field->name, 145 | Template::quotes ($o->{$field->name}), 146 | $rule 147 | ); 148 | printf ( 149 | "\n", 150 | $field->name 151 | ); 152 | break; 153 | case 'datetime': 154 | if (! $timepicker_loaded) { 155 | $page->add_script ('/js/jquery-ui/jquery-ui.css'); 156 | $page->add_script ('/js/jquery-ui/jquery-ui.min.js'); 157 | $page->add_script ( 158 | '' 166 | ); 167 | $page->add_script ('/apps/blog/js/jquery.timepicker.js'); 168 | $timepicker_loaded = true; 169 | } 170 | printf ( 171 | '

%s:
%s

' . "\n", 172 | $field->name, 173 | $field->name, 174 | $field->name, 175 | Template::quotes ($o->{$field->name}), 176 | $rule 177 | ); 178 | printf ( 179 | "\n", 180 | $field->name 181 | ); 182 | break; 183 | case 'enum': 184 | printf ( 185 | '

%s:
%s

' . "\n", 203 | $rule 204 | ); 205 | break; 206 | case 'select': 207 | printf ( 208 | '

%s:
%s

' . "\n", 227 | $rule 228 | ); 229 | break; 230 | default: 231 | printf ( 232 | '

%s:
%s

' . "\n", 233 | $field->name, 234 | $field->name, 235 | $field->name, 236 | Template::quotes ($o->{$field->name}), 237 | $rule 238 | ); 239 | break; 240 | } 241 | } 242 | echo "

\n"; 243 | 244 | // display any notices for failed fields 245 | if (count ($o->failed) > 0) { 246 | echo "\n"; 251 | } 252 | 253 | ?> -------------------------------------------------------------------------------- /handlers/edit.php: -------------------------------------------------------------------------------- 1 | layout = 'admin'; 19 | $page->title = i18n_get ('Editing Item') . ': ' . $_GET['table'] . '/' . str_replace ('|', '+', $_GET['key']); 20 | 21 | // get the field details of the table so we can dynamically generate the form 22 | $fields = DBMan::table_info ($_GET['table']); 23 | $pkey = DBMan::primary_key ($_GET['table']); 24 | 25 | $f = new Form ('post'); 26 | 27 | // generate rules for required fields 28 | foreach ($fields as $field) { 29 | $f->rules[$field->name] = DBMan::get_rules ($field); 30 | } 31 | 32 | if ($f->submit ()) { 33 | unset ($_POST['_token_']); 34 | 35 | // update item 36 | $pkey = DBMan::primary_key ($_GET['table']); 37 | $sql = 'update `' . $_GET['table'] . '` set '; 38 | $params = array (); 39 | $sep = ''; 40 | 41 | foreach ($_POST as $k => $v) { 42 | if ($k == $pkey) { 43 | continue; 44 | } 45 | $sql .= $sep . '`' . $k . '` = ?'; 46 | $params[] = $v; 47 | $sep = ', '; 48 | } 49 | 50 | if (is_array ($pkey)) { 51 | if (count ($pkey) == 2) { 52 | $sql .= ' where (`' . $pkey[0] . '` = ? and `' . $pkey[1] . '` = ?)'; 53 | $keys = explode ('|', $_GET['key']); 54 | $params[] = $keys[0]; 55 | $params[] = $keys[1]; 56 | } elseif (count ($pkey) == 3) { 57 | $sql .= ' where (`' . $pkey[0] . '` = ? and `' . $pkey[1] . '` = ? and `' . $pkey[2] . '` = ?)'; 58 | $keys = explode ('|', $_GET['key']); 59 | $params[] = $keys[0]; 60 | $params[] = $keys[1]; 61 | $params[] = $keys[2]; 62 | } 63 | } else { 64 | $sql .= ' where `' . $pkey . '` = ?'; 65 | $params[] = $_GET['key']; 66 | } 67 | 68 | if (! db_execute ($sql, $params)) { 69 | $page->title = i18n_get ('An Error Occurred'); 70 | printf ("

%s

\n

« %s

\n", db_error (), $_GET['table'], i18n_get ('Back')); 71 | return; 72 | } 73 | 74 | $this->add_notification (i18n_get ('Item updated.')); 75 | $this->redirect ('/dbman/browse?table=' . $_GET['table']); 76 | } 77 | 78 | // get the initial object from the database 79 | if (is_array ($pkey)) { 80 | if (count ($pkey) == 2) { 81 | $o = db_single ( 82 | sprintf ( 83 | 'select * from `%s` where (`%s` = ? and `%s` = ?)', 84 | $_GET['table'], 85 | $pkey[0], 86 | $pkey[1] 87 | ), 88 | explode ('|', $_GET['key']) 89 | ); 90 | } elseif (count ($pkey) == 3) { 91 | $o = db_single ( 92 | sprintf ( 93 | 'select * from `%s` where (`%s` = ? and `%s` = ? and `%s` = ?)', 94 | $_GET['table'], 95 | $pkey[0], 96 | $pkey[1], 97 | $pkey[2] 98 | ), 99 | explode ('|', $_GET['key']) 100 | ); 101 | } 102 | } else { 103 | $o = db_single ( 104 | sprintf ( 105 | 'select * from `%s` where %s = ?', 106 | $_GET['table'], 107 | DBMan::primary_key ($_GET['table']) 108 | ), 109 | $_GET['key'] 110 | ); 111 | } 112 | 113 | // generate the form 114 | $o = $f->merge_values ($o); 115 | $o->failed = $f->failed; 116 | echo "
\n"; 117 | 118 | $timepicker_loaded = false; 119 | 120 | // generate the form fields 121 | foreach ($fields as $field) { 122 | // disable auto-incrementing fields 123 | if (DBMan::is_auto_incrementing ($field)) { 124 | printf ( 125 | '' . "\n", 126 | $field->name, 127 | $o->{$field->name} 128 | ); 129 | continue; 130 | } 131 | 132 | if (isset ($f->rules[$field->name]['type']) && $f->rules[$field->name]['type'] == 'numeric') { 133 | $rule = ' ' . i18n_getf ('You must enter a number for %s', $field->name) . ''; 134 | } elseif (isset ($f->rules[$field->name]['length'])) { 135 | $rule = ' ' . i18n_getf ('You must enter a value for %s no longer than %s', $field->name, $field->length) . ''; 136 | } elseif (isset ($f->rules[$field->name]['not empty'])) { 137 | $rule = ' ' . i18n_getf ('You must enter a value for %s', $field->name) . ''; 138 | } else { 139 | $rule = ''; 140 | } 141 | 142 | switch ($field->type) { 143 | case 'text': 144 | case 'mediumtext': 145 | printf ( 146 | '

%s:
%s

' . "\n", 147 | $field->name, 148 | $field->name, 149 | Template::quotes ($o->{$field->name}), 150 | $rule 151 | ); 152 | break; 153 | case 'date': 154 | if (! $timepicker_loaded) { 155 | $page->add_script ('/js/jquery-ui/jquery-ui.css'); 156 | $page->add_script ('/js/jquery-ui/jquery-ui.min.js'); 157 | $page->add_script ( 158 | '' 166 | ); 167 | $page->add_script ('/apps/blog/js/jquery.timepicker.js'); 168 | $timepicker_loaded = true; 169 | } 170 | printf ( 171 | '

%s:
%s

' . "\n", 172 | $field->name, 173 | $field->name, 174 | $field->name, 175 | Template::quotes ($o->{$field->name}), 176 | $rule 177 | ); 178 | printf ( 179 | "\n", 180 | $field->name 181 | ); 182 | break; 183 | case 'time': 184 | if (! $timepicker_loaded) { 185 | $page->add_script ('/js/jquery-ui/jquery-ui.css'); 186 | $page->add_script ('/js/jquery-ui/jquery-ui.min.js'); 187 | $page->add_script ( 188 | '' 196 | ); 197 | $page->add_script ('/apps/blog/js/jquery.timepicker.js'); 198 | $timepicker_loaded = true; 199 | } 200 | printf ( 201 | '

%s:
%s

' . "\n", 202 | $field->name, 203 | $field->name, 204 | $field->name, 205 | Template::quotes ($o->{$field->name}), 206 | $rule 207 | ); 208 | printf ( 209 | "\n", 210 | $field->name 211 | ); 212 | break; 213 | case 'datetime': 214 | if (! $timepicker_loaded) { 215 | $page->add_script ('/js/jquery-ui/jquery-ui.css'); 216 | $page->add_script ('/js/jquery-ui/jquery-ui.min.js'); 217 | $page->add_script ( 218 | '' 226 | ); 227 | $page->add_script ('/apps/blog/js/jquery.timepicker.js'); 228 | $timepicker_loaded = true; 229 | } 230 | printf ( 231 | '

%s:
%s

' . "\n", 232 | $field->name, 233 | $field->name, 234 | $field->name, 235 | Template::quotes ($o->{$field->name}), 236 | $rule 237 | ); 238 | printf ( 239 | "\n", 240 | $field->name 241 | ); 242 | break; 243 | case 'enum': 244 | printf ( 245 | '

%s:
%s

' . "\n", 263 | $rule 264 | ); 265 | break; 266 | case 'select': 267 | printf ( 268 | '

%s:
%s

' . "\n", 287 | $rule 288 | ); 289 | break; 290 | default: 291 | printf ( 292 | '

%s:
%s

' . "\n", 293 | $field->name, 294 | $field->name, 295 | Template::quotes ($o->{$field->name}), 296 | $rule 297 | ); 298 | break; 299 | } 300 | } 301 | echo "

\n"; 302 | 303 | // display any notices for failed fields 304 | if (count ($o->failed) > 0) { 305 | echo "\n"; 310 | } 311 | 312 | ?> --------------------------------------------------------------------------------