├── 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 |
4 | 5 | 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 ("\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 | 2 | 3 | 25 | -------------------------------------------------------------------------------- /views/info.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |4 |
| {"Column"} | 7 |{"Type"} | 8 |{"Length"} | 9 |{"Null"} | 10 |{"Key"} | 11 |{"Default"} | 12 |{"Extra"} | 13 |
|---|---|---|---|---|---|---|
| {{ loop_value->name }} | 17 |{{ loop_value->type }} | 18 |{{ loop_value->length }} | 19 |{{ loop_value->notnull }} | 20 |{{ loop_value->key }} | 21 |{{ loop_value->default }} | 22 |{{ loop_value->extra }} | 23 |
2 | {"SQL Shell"} 3 | | 4 | {"CSV Importer"} 5 |
6 | 7 |8 |
| {"Table"} | 11 |{"Count"} | 12 |13 | |
|---|---|---|
| {{ loop_value }} | 17 |{{ loop_value|DBMan::count }} | 18 |{"Info"} | {"Export"} | {"Drop"} | 19 |
%s: %s
', __ ('Import failed'), $obj->error); 41 | printf ('', __ ('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 ('', $_POST['table'], __ ('Continue')); 51 | } else { 52 | printf ('%s
', __ ('Import failed.')); 53 | printf ('', __ ('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\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 = $('\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\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"; 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\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"; 302 | 303 | // display any notices for failed fields 304 | if (count ($o->failed) > 0) { 305 | echo "\n"; 310 | } 311 | 312 | ?> --------------------------------------------------------------------------------