New admin panel Pages: Options + Config [WIP]

This commit is contained in:
slawkens 2020-05-14 23:59:37 +02:00
parent 416de6b584
commit 89d82e5117
7 changed files with 637 additions and 55 deletions

View File

@ -46,40 +46,59 @@
<nav class="mt-2"> <nav class="mt-2">
<ul class="nav nav-pills nav-sidebar flex-column nav-legacy nav-child-indent" data-widget="treeview" data-accordion="false"> <ul class="nav nav-pills nav-sidebar flex-column nav-legacy nav-child-indent" data-widget="treeview" data-accordion="false">
<?php <?php
$optionsMenu = [];
foreach(get_plugins() as $name) {
$pluginJson = Plugins::getPluginJson($name);
if(!$pluginJson) {
continue;
}
if (!isset($pluginJson['options']) || !file_exists(BASE . $pluginJson['options'])) {
continue;
}
$optionsMenu[] = ['name' => $pluginJson['name'], 'link' => 'options&plugin=' . $name];
}
// Name = Display name of // Name = Display name of
// link = Page link // link = Page link
// icon = fontawesome icon namewithout "fas fa-" // icon = fontawesome icon namewithout "fas fa-"
// menu = menu array for sub items. // menu = menu array for sub items.
$menus = array( $menus = [
0 => array('name' => 'Dashboard', 'link' => 'dashboard', 'icon' => 'tachometer-alt'), ['name' => 'Dashboard', 'link' => 'dashboard', 'icon' => 'tachometer-alt'],
1 => array('name' => 'News', 'link' => 'news', 'icon' => 'newspaper'), ['name' => 'Config', 'link' => 'config', 'icon' => 'wrench'],
2 => array('name' => 'Mailer', 'link' => 'mailer', 'icon' => 'envelope'), ['name' => 'News', 'link' => 'news', 'icon' => 'newspaper'],
3 => array('name' => 'Pages', 'icon' => 'book', 'menu' => array( ['name' => 'Mailer', 'link' => 'mailer', 'icon' => 'envelope'],
0 => array('name' => 'All Pages', 'link' => 'pages'), ['name' => 'Pages', 'icon' => 'book', 'menu' =>
1 => array('name' => 'Add new', 'link' => 'pages&action=new'), [
),), ['name' => 'All Pages', 'link' => 'pages'],
4 => array('name' => 'Menus', 'link' => 'menus', 'icon' => 'list'), ['name' => 'Add new', 'link' => 'pages&action=new'],
5 => array('name' => 'Plugins', 'link' => 'plugins', 'icon' => 'plug'), ],
6 => array('name' => 'Visitors', 'link' => 'visitors', 'icon' => 'user'), ],
7 => array('name' => 'Editor', 'icon' => 'book', 'menu' => array( ['name' => 'Menus', 'link' => 'menus', 'icon' => 'list'],
0 => array('name' => 'Accounts', 'link' => 'accounts'), ['name' => 'Plugins', 'link' => 'plugins', 'icon' => 'plug'],
1 => array('name' => 'Players', 'link' => 'players'), ['name' => 'Options', 'icon' => 'book', 'menu' => $optionsMenu],
),), ['name' => 'Visitors', 'link' => 'visitors', 'icon' => 'user'],
8 => array('name' => 'Items', 'link' => 'items', 'icon' => 'gavel'), ['name' => 'Items', 'link' => 'items', 'icon' => 'gavel'],
9 => array('name' => 'Editor', 'icon' => 'wrench', 'menu' => array( ['name' => 'Editor', 'icon' => 'wrench', 'menu' =>
0 => array('name' => 'Accounts', 'link' => 'accounts'), [
1 => array('name' => 'Players', 'link' => 'players'), ['name' => 'Accounts', 'link' => 'accounts'],
),), ['name' => 'Players', 'link' => 'players'],
9 => array('name' => 'Tools', 'link' => '', 'icon' => 'tools', 'menu' => array( ],
0 => array('name' => 'Notepad', 'link' => 'notepad'), ],
1 => array('name' => 'phpinfo', 'link' => 'phpinfo'), ['name' => 'Tools', 'link' => '', 'icon' => 'tools', 'menu' =>
),), [
10 => array('name' => 'Logs', 'link' => '', 'icon' => 'bug', 'menu' => array( ['name' => 'Notepad', 'link' => 'notepad'],
0 => array('name' => 'Logs', 'link' => 'logs'), ['name' => 'phpinfo', 'link' => 'phpinfo'],
1 => array('name' => 'Reports', 'link' => 'reports'), ],
), ],
), ['name' => 'Logs', 'link' => '', 'icon' => 'bug', 'menu' =>
); [
['name' => 'Logs', 'link' => 'logs'],
['name' => 'Reports', 'link' => 'reports'],
],
],
];
foreach ($menus as $category => $menu) { foreach ($menus as $category => $menu) {
$has_child = isset($menu['menu']); $has_child = isset($menu['menu']);

View File

@ -28,7 +28,7 @@ session_start();
define('MYAAC', true); define('MYAAC', true);
define('MYAAC_VERSION', '0.8.2-dev'); define('MYAAC_VERSION', '0.8.2-dev');
define('DATABASE_VERSION', 30); define('DATABASE_VERSION', 31);
define('TABLE_PREFIX', 'myaac_'); define('TABLE_PREFIX', 'myaac_');
define('START_TIME', microtime(true)); define('START_TIME', microtime(true));
define('MYAAC_OS', stripos(PHP_OS, 'WIN') === 0 ? 'WINDOWS' : (strtoupper(PHP_OS) === 'DARWIN' ? 'MAC' : 'LINUX')); define('MYAAC_OS', stripos(PHP_OS, 'WIN') === 0 ? 'WINDOWS' : (strtoupper(PHP_OS) === 'DARWIN' ? 'MAC' : 'LINUX'));

View File

@ -210,6 +210,51 @@ CREATE TABLE `myaac_monsters` (
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
CREATE TABLE `myaac_options_bool`
(
`name` VARCHAR(255) NOT NULL,
`key` VARCHAR(255) NOT NULL,
`value` INT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`key`),
UNIQUE (`key`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
CREATE TABLE `myaac_options_double`
(
`name` VARCHAR(255) NOT NULL,
`key` VARCHAR(255) NOT NULL,
`value` DOUBLE NOT NULL DEFAULT 0,
PRIMARY KEY (`key`),
UNIQUE (`key`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
CREATE TABLE `myaac_options_int`
(
`name` VARCHAR(255) NOT NULL,
`key` VARCHAR(255) NOT NULL,
`value` INT(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`key`),
UNIQUE (`key`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
CREATE TABLE `myaac_options_text`
(
`name` VARCHAR(255) NOT NULL,
`key` VARCHAR(255) NOT NULL,
`value` TEXT NOT NULL,
PRIMARY KEY (`key`),
UNIQUE (`key`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
CREATE TABLE `myaac_options_varchar`
(
`name` VARCHAR(255) NOT NULL,
`key` VARCHAR(255) NOT NULL,
`title` VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (`key`),
UNIQUE (`key`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
CREATE TABLE `myaac_videos` CREATE TABLE `myaac_videos`
( (
`id` INT(11) NOT NULL AUTO_INCREMENT, `id` INT(11) NOT NULL AUTO_INCREMENT,

View File

@ -56,23 +56,14 @@ class Plugins {
} }
$hooks = []; $hooks = [];
foreach(get_plugins() as $filename) { foreach (get_plugins() as $filename) {
$string = file_get_contents(PLUGINS . $filename . '.json'); $plugin_json = self::getPluginJson($filename);
$string = self::removeComments($string); if (!$plugin_json) {
$plugin = json_decode($string, true);
self::$plugin_json = $plugin;
if ($plugin == null) {
self::$warnings[] = 'Cannot load ' . $filename . '.json. File might be not a valid json code.';
continue; continue;
} }
if(isset($plugin['enabled']) && $plugin['enabled'] === 0) { if (isset($plugin_json['hooks'])) {
self::$warnings[] = 'Skipping ' . $filename . '... The plugin is disabled.'; foreach ($plugin_json['hooks'] as $_name => $info) {
continue;
}
if (isset($plugin['hooks'])) {
foreach ($plugin['hooks'] as $_name => $info) {
if (defined('HOOK_'. $info['type'])) { if (defined('HOOK_'. $info['type'])) {
$hook = constant('HOOK_'. $info['type']); $hook = constant('HOOK_'. $info['type']);
$hooks[] = ['name' => $_name, 'type' => $hook, 'file' => $info['file']]; $hooks[] = ['name' => $_name, 'type' => $hook, 'file' => $info['file']];
@ -90,6 +81,42 @@ class Plugins {
return $hooks; return $hooks;
} }
public static function getPluginOptions($pluginName)
{
$plugin_json = self::getPluginJson($pluginName);
if (!$plugin_json) {
return false;
}
if (!isset($plugin_json['options']) || !file_exists(BASE . $plugin_json['options'])) {
return false;
}
return $plugin_json['options'];
}
public static function getPluginJson($name = null)
{
if(!isset($name)) {
return self::$plugin_json;
}
$string = file_get_contents(PLUGINS . $name . '.json');
$string = self::removeComments($string);
$plugin_json = json_decode($string, true);
if ($plugin_json == null) {
self::$warnings[] = 'Cannot load ' . $name . '.json. File might be not a valid json code.';
return false;
}
if(isset($plugin_json['enabled']) && $plugin_json['enabled'] === 0) {
self::$warnings[] = 'Skipping ' . $name . '... The plugin is disabled.';
return false;
}
return $plugin_json;
}
public static function install($file) { public static function install($file) {
global $db; global $db;
@ -382,10 +409,6 @@ class Plugins {
return self::$error; return self::$error;
} }
public static function getPluginJson() {
return self::$plugin_json;
}
public static function removeComments($string) { public static function removeComments($string) {
$string = preg_replace('!/\*.*?\*/!s', '', $string); $string = preg_replace('!/\*.*?\*/!s', '', $string);
$string = preg_replace('/\n\s*\n/', "\n", $string); $string = preg_replace('/\n\s*\n/', "\n", $string);

46
system/migrations/31.php Normal file
View File

@ -0,0 +1,46 @@
<?php
$db->exec("CREATE TABLE `myaac_options_bool`
(
`name` VARCHAR(255) NOT NULL,
`key` VARCHAR(255) NOT NULL,
`value` INT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`key`),
UNIQUE (`key`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
CREATE TABLE `myaac_options_double`
(
`name` VARCHAR(255) NOT NULL,
`key` VARCHAR(255) NOT NULL,
`value` DOUBLE NOT NULL DEFAULT 0,
PRIMARY KEY (`key`),
UNIQUE (`key`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
CREATE TABLE `myaac_options_int`
(
`name` VARCHAR(255) NOT NULL,
`key` VARCHAR(255) NOT NULL,
`value` INT(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`key`),
UNIQUE (`key`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
CREATE TABLE `myaac_options_text`
(
`name` VARCHAR(255) NOT NULL,
`key` VARCHAR(255) NOT NULL,
`value` TEXT NOT NULL,
PRIMARY KEY (`key`),
UNIQUE (`key`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
CREATE TABLE `myaac_options_varchar`
(
`name` VARCHAR(255) NOT NULL,
`key` VARCHAR(255) NOT NULL,
`value` VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (`key`),
UNIQUE (`key`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;");

View File

@ -0,0 +1,309 @@
<?php
/**
* Tools
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
defined('MYAAC') or die('Direct access not allowed!');
$title = 'Config Editor';
require_once SYSTEM . 'clients.conf.php';
$message = '';
$config_options = array(
'server_path' => array(
'name' => 'Server Path',
'type' => 'text',
'desc' => 'path to the server directory (same directory where config file is located)'
),
'env' => array(
'name' => 'Environment',
'type' => 'options',
'options' => array('prod' => 'Production', 'dev' => 'Development'),
'desc' => 'if you use this script on your live server - set to <i>Production</i><br/>
if you want to test and debug the script locally, or develop plugins, set to <i>Development</i><br/>
<strong>WARNING</strong>: on <i>Development</i> cache is disabled, so site will be significantly slower !!!<br/>
<strong>Recommended</strong>: <i>Production</i> cause of speed (page load time is better)'
),
'template' => array(
'name' => 'Template Name',
'type' => 'options',
'options' => '$templates',
'desc' => 'Name of the template used by website'
),
'template_allow_change' => array(
'name' => 'Template Allow Change',
'type' => 'boolean',
'desc' => 'Allow changing template of the website by showing a special select in the part of website'
),
'vocations_amount' => array(
'name' => 'Amount of Vocations',
'type' => 'number',
'desc' => 'how much basic vocations your server got (without promotion)'
),
'client' => array(
'name' => 'Client Version',
'type' => 'options',
'options' => '$clients',
'desc' => 'what client version are you using on this OT?<br/>
used for the Downloads page and some templates aswell'
),
'session_prefix' => array(
'name' => 'Session Prefix',
'type' => 'text',
'desc' => 'must be unique for every site on your server',
),
'friendly_urls' => array(
'name' => 'Friendly URLs',
'type' => 'boolean',
'desc' => 'mod_rewrite is required for this, it makes links looks more elegant to eye, and also are SEO friendly (example: https://my-aac.org/guilds/Testing instead of https://my-aac.org/?subtopic=guilds&name=Testing).<br/><strong>Remember to rename .htaccess.dist to .htaccess</strong>'
),
'gzip_output' => array(
'name' => 'GZIP Output',
'type' => 'boolean',
'desc' => 'gzip page content before sending it to the browser, uses less bandwidth but more cpu cycles'
),
'backward_support' => array(
'name' => 'Gesior Backward Support',
'type' => 'boolean',
'desc' => 'gesior backward support (templates & pages)<br/>
allows using gesior templates and pages with myaac<br/>
might bring some performance when disabled'
),
'meta_description' => array(
'name' => 'Meta Description',
'type' => 'textarea',
'desc' => 'description of the site in <meta>'
),
'meta_keywords' => array(
'name' => 'Meta Keywords',
'type' => 'textarea',
'desc' => 'keywords list separated by commas'
),
'title_separator' => array(
'name' => 'Title Separator',
'type' => 'text',
'desc' => 'Separator used in the title of the website'
),
'footer' => array(
'name' => 'Footer',
'type' => 'textarea',
'desc' => 'For example: "<br/>Your Server &copy; 2016. All rights reserved."'
),
'language' => array(
'name' => 'Language',
'type' => 'options',
'options' => array('en' => 'English'),
'desc' => 'default language (currently only English available)'
),
'visitors_counter' => array(
'name' => 'Visitors Counter',
'type' => 'boolean',
'desc' => 'Enable Visitors Counter? It will show list of online members on the website in Admin Panel'
),
'visitors_counter_ttl' => array(
'name' => 'Visitors Counter TTL',
'type' => 'number',
'desc' => 'Time To Live for Visitors Counter. In other words - how long user will be marked as online. In Minutes'
),
'views_counter' => array(
'name' => 'Views Counter',
'type' => 'boolean',
'desc' => 'Enable Views Counter? It will show how many times the website has been viewed by users'
),
'cache_engine' => array(
'name' => 'Cache Engine',
'type' => 'text',
'desc' => 'cache system. by default file cache is used.<br/>
Other available options: apc, apcu, eaccelerator, xcache, file, auto, or blank to disable'
),
'cache_prefix' => array(
'name' => 'Cache Prefix',
'type' => 'text',
'desc' => 'have to be unique if running more MyAAC instances on the same server (except file system cache)'
),
'database_log' => array(
'name' => 'Database Log',
'type' => 'boolean',
'desc' => 'Should database queries be logged and displayed in the page source? They will be included at the end of the .html source of the page, only for Super Admin'
),
'database_socket' => array(
'name' => 'Database Socket',
'type' => 'text',
'desc' => 'Set if you want to connect to database through socket (example: /var/run/mysqld/mysqld.sock)'
),
'database_persistent' => array(
'name' => 'Database Persistent',
'type' => 'boolean',
'desc' => 'Use database permanent connection (like server), may speed up your site'
),
'outfit_images_url' => array(
'name' => 'Outfit Images URL',
'type' => 'text',
'desc' => 'Set to animoutfit.php for animated outfit'
),
'item_images_url' => array(
'name' => 'Item Images URL',
'type' => 'text',
'desc' => 'Set to images/items if you host your own items in images folder'
),
);
if (isset($_POST['save'])) {
$content = '<?php' . PHP_EOL . PHP_EOL .
'// place for your configuration directives, so you can later easily update myaac' . PHP_EOL . PHP_EOL .
"\$config['installed'] = true;";
foreach($config_options as $key => $_config) {
$content .= PHP_EOL . "\$config['$key'] = ";
if (in_array($_config['type'], array('boolean', 'number'))) {
$content .= $_POST[$key];
}
else if (in_array($_config['type'], array('text', 'textarea'))) {
$content .= "'" . $_POST[$key] . "'";
}
else if($_config['type'] === 'options') {
if(is_numeric($_POST[$key])) {
$content .= $_POST[$key];
}
else {
$content .= "'" . $_POST[$key] . "'";
}
}
$content .= ';';
}
//$saved = file_put_contents(BASE . 'config.local.php', $content);
$saved = false;
ob_start();
if($saved) {
?>
<div class="alert alert-success">
Config has been successfully saved.
</div>
<?php
}
else {
?>
<div class="alert alert-error">
<?= BASE ?>config.local.php couldn't be opened. Please copy this content and paste there:
<br/>
<textarea class="form-control" cols="70" rows="10"><?= $content ?></textarea>
</div>
<?php
}
$message = ob_get_clean();
}
?>
<form method="post">
<div class="row">
<div class="col-md-12">
<div class="box">
<div class="box-header">
<h3 class="box-title">Configuration Editor</h3>
</div>
<div class="box-body">
<?= $message ?>
<button name="save" type="submit" class="btn btn-primary">Save</button>
</div>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th width="10%">Key</th>
<th width="10%">Name</th>
<th width="30%">Value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<?php
foreach($config_options as $key => $_config) {
?>
<tr>
<td><label for="<?= $key ?>" class="control-label"><?= $key ?></label></td>
<td><label for="<?= $key ?>" class="control-label"><?= $_config['name'] ?></label></td>
<td>
<?php
if ($_config['type'] === 'boolean') {
$_config['type'] = 'options';
$_config['options'] = array('true' => 'Yes', 'false' => 'No');
}
else if (in_array($_config['type'], array('text', 'number'))) {
echo '<input class="form-control" type="' . $_config['type'] . '" name="' . $key . '" value="' . config($key) . '" id="' . $key . '"/>';
}
else if($_config['type'] === 'textarea') {
echo '<textarea class="form-control" name="' . $key . '" id="' . $key . '">' . config($key) . '</textarea>';
}
if ($_config['type'] === 'options') {
if ($_config['options'] === '$templates') {
$templates = array();
foreach (get_templates() as $value) {
$templates[$value] = $value;
}
$_config['options'] = $templates;
}
else if($_config['options'] === '$clients') {
$clients = array();
foreach((array)config('clients') as $client) {
$client_version = (string)($client / 100);
if(strpos($client_version, '.') === false)
$client_version .= '.0';
$clients[$client] = $client_version;
}
$_config['options'] = $clients;
}
echo '<select class="form-control" name="' . $key . '" id="' . $key . '">';
foreach ($_config['options'] as $value => $option) {
if($value === 'true') {
$selected = config($key) === true;
}
else if($value === 'false') {
$selected = config($key) === false;
}
else {
$selected = config($key) == $value;
}
echo '<option value="' . $value . '" ' . ($selected ? 'selected' : '') . '>' . $option . '</option>';
}
echo '</select>';
}
?>
</td>
<td>
<div class="well">
<?= $_config['desc'] ?>
</div>
</td>
</tr>
<?php
}
?>
</tbody>
</table>
<div class="box-footer">
<button name="save" type="submit" class="btn btn-primary">Save</button>
</div>
</div>
</div>
</div>
</form>

View File

@ -0,0 +1,140 @@
<?php
/**
* Menus
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
defined('MYAAC') or die('Direct access not allowed!');
$title = 'Options';
$plugin = $_GET['plugin'];
if (!isset($plugin) || empty($plugin)) {
error('Please select plugin name from left Panel.');
return;
}
$pluginOptions = Plugins::getPluginOptions($plugin);
if (!$pluginOptions) {
error('This plugin does not exist or does not have options defined.');
return;
}
$message = '';
$optionsFile = require BASE . $pluginOptions;
if (!is_array($optionsFile)) {
return;
}
$name = $optionsFile['name'];
$options = $optionsFile['options'];
if (isset($_POST['save'])) {
foreach ($options as $key => $_config) {
// TODO:
// Save functionality
// Check if exist, then INSERT or UPDATE
/*$query = $db->query(
sprintf('SELECT `value` FROM `%s` WHERE `name` = %s AND `key` = %s',
TABLE_PREFIX . 'options_' . $table,
$name,
$key)
);*/
}
}
$optionsValues = [];
$optionsTypes = ['bool', 'double', 'int', 'text', 'varchar'];
foreach($optionsTypes as $type) {
$query = 'SELECT `key`, `value` FROM `' . TABLE_PREFIX . 'options_' . $type . '` WHERE `name` = ' . $db->quote($name) . ';';
$query = $db->query($query);
$optionsValues = $optionsValues + $query->fetchAll();
}
//var_dump($optionsValues);
?>
<form method="post">
<div class="row">
<div class="col-md-12">
<div class="box">
<div class="box-header">
<h3 class="box-title">Plugin Options - <?= $plugin ?></h3>
</div>
<div class="box-body">
<?= $message ?>
<button name="save" type="submit" class="btn btn-primary">Save</button>
</div>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="width: 10%">Key</th>
<th style="width: 10%">Name</th>
<th style="width: 30%">Value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<?php
foreach($options as $key => $_config) {
?>
<tr>
<td><label for="<?= $key ?>" class="control-label"><?= $key ?></label></td>
<td><label for="<?= $key ?>" class="control-label"><?= $_config['name'] ?></label></td>
<td>
<?php
if ($_config['type'] === 'boolean') {
$_config['type'] = 'options';
$_config['options'] = ['true' => 'Yes', 'false' => 'No'];
}
else if (in_array($_config['type'], ['varchar', 'number'])) {
echo '<input class="form-control" type="' . $_config['type'] . '" name="' . $key . '" value="' . (config($key) === null ? $_config['default'] : config($key)) . '" id="' . $key . '"/>';
}
else if($_config['type'] === 'textarea') {
echo '<textarea class="form-control" name="' . $key . '" id="' . $key . '">' . config($key) . '</textarea>';
}
if ($_config['type'] === 'options') {
echo '<select class="form-control" name="' . $key . '" id="' . $key . '">';
foreach ($_config['options'] as $value => $option) {
if($value === 'true') {
$selected = config($key) === true;
}
else if($value === 'false') {
$selected = config($key) === false;
}
else {
$selected = config($key) == $value;
}
echo '<option value="' . $value . '" ' . ($selected ? 'selected' : '') . '>' . $option . '</option>';
}
echo '</select>';
}
?>
</td>
<td>
<div class="well">
<?= $_config['desc'] ?>
</div>
</td>
</tr>
<?php
}
?>
</tbody>
</table>
<div class="box-footer">
<button name="save" type="submit" class="btn btn-primary">Save</button>
</div>
</div>
</div>
</div>
</form>