From 8036ba129e6a0308b37eb1e667ceca9d45e24caa Mon Sep 17 00:00:00 2001 From: Chuck Scott Date: Mon, 10 Nov 2014 21:18:13 -0500 Subject: [PATCH] Completed updates to MVC approach and completed base functionality. Should all be working now. Still needs more documentation updates. --- controllers/admin.php | 463 +++++++++++++++++++++------ controllers/front.php | 38 +-- css/index.css | 5 +- index.php | 376 ++++++++++++---------- lib/smartyTemplateSupport.php | 170 +++++----- models/activate.php | 52 +-- models/admin/add.php | 221 ------------- models/admin/add/index.php | 260 +++++++++++++++ models/admin/display.php | 89 ----- models/admin/error/badAction.php | 112 +++++++ models/admin/error/index.php | 70 ++++ models/admin/list.php | 113 ------- models/admin/prototypes/delete.php | 110 +++++++ models/admin/prototypes/display.php | 124 +++++++ models/admin/prototypes/edit.php | 291 +++++++++++++++++ models/admin/prototypes/index.php | 144 +++++++++ models/deactivate.php | 53 +-- models/uninstall.php | 63 ++-- views/admin/add/index.html | 56 ++++ views/admin/add/submitted.html | 16 + views/admin/error/badAction.html | 8 + views/admin/prototype_add.html | 56 ---- views/admin/prototype_display.html | 11 - views/admin/prototype_list.html | 43 --- views/admin/prototype_submitted.html | 16 - views/admin/prototypes/display.html | 11 + views/admin/prototypes/edit.html | 56 ++++ views/admin/prototypes/index.html | 43 +++ views/admin/prototypes/updated.html | 7 + 29 files changed, 2065 insertions(+), 1012 deletions(-) delete mode 100644 models/admin/add.php create mode 100644 models/admin/add/index.php delete mode 100644 models/admin/display.php create mode 100644 models/admin/error/badAction.php create mode 100644 models/admin/error/index.php delete mode 100644 models/admin/list.php create mode 100644 models/admin/prototypes/delete.php create mode 100644 models/admin/prototypes/display.php create mode 100644 models/admin/prototypes/edit.php create mode 100644 models/admin/prototypes/index.php create mode 100644 views/admin/add/index.html create mode 100644 views/admin/add/submitted.html create mode 100644 views/admin/error/badAction.html delete mode 100644 views/admin/prototype_add.html delete mode 100644 views/admin/prototype_display.html delete mode 100644 views/admin/prototype_list.html delete mode 100644 views/admin/prototype_submitted.html create mode 100644 views/admin/prototypes/display.html create mode 100644 views/admin/prototypes/edit.html create mode 100644 views/admin/prototypes/index.html create mode 100644 views/admin/prototypes/updated.html diff --git a/controllers/admin.php b/controllers/admin.php index d1c6101..ca0deb7 100644 --- a/controllers/admin.php +++ b/controllers/admin.php @@ -1,6 +1,6 @@ array( + 'index', + 'display', + 'edit', + 'delete' + ), + 'add' => array( + 'index' + ), + 'error' => array( + 'badAction' + ) +); + +/** + * Admin Controller Class + * + * This is one of perhaps multiple controller classes that provide + * controller services for a major portion of this plugin. Typically + * there are such classes for Admin and Front-End functionality, but + * there could be others. + * + * This controller class performs all admin related operations for + * this plugin by calling the appropriate model and merging the resulting + * data with the appropriate view to produce any output. + * + * All requests for this controller class come through WordPress admin + * menus via hooks that "call back" methods in this class for each admin + * menu item in this plugin. Form submissions from an admin page selected + * by a particular menu item are directed to WordPress using the page + * reference of that menu item. Because of this, the callback for a form + * submission is also handled by the callback target method used by that + * menu item. + * + * Admin form submissions must use the URI for one of this plugin's + * menu items. The form post parameters may also provide an "action" name + * in the case where the default menu item behavior is not desired. A + * pathname for the model to execute is then complied using the menu + * item name as the name of a directory under models/admin and the + * requested action as the file name of the model to execute. The name + * "index" would be the default menu item action model. In essence the + * controller locates the model by menu item name and action name. for + * example... + * + * models/admin/prototypes/index.php + * models/admin/prototypes/display.php + * + * Similarly, the view associated with the action would be in a directory + * named the same as the model, and the view file would be named "index" + * or the name of the action. + * + * These hooks are established using the WordPress add_action() + * function and the configureMenus() method below. Other methods in this + * class then recieve any request from a menu item selection or form + * + * submission associated with a menu item by WordPress calling one of the + * "callback" methods below. + * + * The callback methods do nothing other than to call the controller() + * method and passing it the name of the menu item assocaiated with the + * callback. + * + * The controller() method determines which model to execute, executes + * that model, determines which view to merge with the data returned by + * the model, creates output from the result of that merge, and sends + * that output to the user. + * + * In situations where it may be desired to output directly to the browser + * without being contained in the admin Dashboard, the contructor can be + * directed + * to bypass setting up the admin hooks and execute the controller() method + * directly then exit. This results in output from the model/view withing being + * contained in the normal WordPress dashboard context. To trigger this use the + * following two form fields. + * + * glm_display_direct = 'true' + * glm_menu_item = (menu item name associated with the desired model) + * + * (no prameters) + * + * @return void + * @access public */ class glmProtoAdmin { - - /** - * WordPress Database Object - * @var $wpdb - * @access public - */ - public $wpdb; - - /** - * Admin Controller Constructor - * - * This contructor is executed by the main plugin index file when the site - * Dashboard is displayed. It's responsible for setting up any needed hooks - * into WordPress and setting up any support classes required to do admin - * work. - * - * (no prameters) - * - * @return void - * @access public - */ - public function __construct($wpdb) - { - - // Save WordPress Database object - $this->wpdb = $wpdb; - - // Add hooks to WordPress - add_action( 'admin_menu', array($this, 'configureMenus')); - - /* - * Check for Requested Actions - */ - if (isset($_REQUEST['glm_proto_action'])) { - - switch ($_REQUEST['glm_proto_action']) { - - // Display a prototype - case 'display': - require_once(GLM_PROTO_PLUGIN_DIR.'/models/admin/display.php'); - return new glmProtoAdminDisplayModel($this->wpdb); - break; - - default: - break; - - } - } - - } - - /** - * Configure Additional WordPress Menus - * - * This method is called by an add_action() hook setup in the contructor. We do it - * this way so that the menu related functions of WordPress are in scope when creating - * the additional menu items. WordPress will execute this call-back method when building - * its Dashboard menus. - * - * (no prameters) - * - * @return void - * @access public - */ - public function configureMenus() { - - // Add a new main menu item for management and display of customer prototypes. - add_menu_page('Site Prototypes', 'Prototypes', 'manage_options', 'glm-proto-admin-list', array($this, 'glmProtoAdminList')); - - // Add sub-menu for adding a new prototype under the Prototypes main menu item. - add_submenu_page('glm-proto-admin-list', 'Site Prototypes', 'Add', 'manage_options', 'glm-proto-admin-add', array($this, 'glmProtoAdminAdd')); - - } - - /* - * Menu action methods - * - * These methods are called by WordPress when specific menu items are selected by the user. - * They should only include and execute the appropriate model for that menu action. - * - */ - - // Main GLM Prototypes Menu Item - Lists current prototypes - public function glmProtoAdminList() - { - require_once(GLM_PROTO_PLUGIN_DIR.'/models/admin/list.php'); - new glmProtoAdminListModel($this->wpdb); - } - - // Add Prototype Sub Menu Item - Displays a form to add a new prototype - public function glmProtoAdminAdd() - { - require_once(GLM_PROTO_PLUGIN_DIR.'/models/admin/add.php'); - return new glmProtoAdminAddModel($this->wpdb); - } - + + /** + * WordPress Database Object + * + * @var $wpdb + * @access public + */ + public $wpdb; + + /** + * Admin Controller Constructor + * + * This contructor is executed by the main plugin index file when the site + * Dashboard is displayed. It's responsible for setting up any needed hooks + * into WordPress and setting up any support classes required to do admin + * work. + * + * (no prameters) + * + * @return void + * @access public + */ + public function __construct ($wpdb) + { + + // Save WordPress Database object + $this->wpdb = $wpdb; + + /* + * Check if there's a request to bypass the WordPress Dashboard and + * display + * directly to the browser using a specified menuItem/action. + * + */ + if (isset($_REQUEST['glm_display_direct']) && + $_REQUEST['glm_menu_item'] != '') { + + // Get the desired menu item name and call controller() with that. + $menuItem = sanitize_text_field($_REQUEST['glm_menu_item']); + $this->controller($menuItem); + + // Exit at this point to stop all WordPress Dashboard output + exit(); + } + + // Add hooks to WordPress + add_action('admin_menu', + array( + $this, + 'configureMenus' + )); + } + + /** + * Configure WordPress Menus for this Plugin + * + * This method is called by an add_action() hook setup in the contructor. We + * do it + * this way so that the menu related functions of WordPress are in scope + * when creating + * the additional menu items. WordPress will execute this call-back method + * when building + * its Dashboard menus. + * + * (no prameters) + * + * @return void + * @access public + */ + public function configureMenus () + { + + // Add a new main menu item for management and display of customer + // prototypes. + add_menu_page('Site Prototypes', 'Prototypes', 'manage_options', + 'glm-proto-admin-menu-prototypes', + array( + $this, + 'glmProtoAdminMenuPrototypes' + )); + + // Add sub-menu for adding a new prototype under the Prototypes main + // menu item. + + add_submenu_page('glm-proto-admin-menu-prototypes', 'Site Prototypes', + 'Add', 'manage_options', 'glm-proto-admin-menu-add', + array( + $this, + 'glmProtoAdminMenuAdd' + )); + } + + /* + * Menu item specific "Callback" methods + * + * These methods are called by WordPress when specific menu items are + * selected by the + * user or a form action is submitted associated with the menu item. + * + * These methods call the controller and pass it the menu item that was + * called + * but perform no other work. + * + */ + + // Main GLM Prototypes Menu Item - Default (list and other operations) + public function glmProtoAdminMenuPrototypes () + { + $this->controller('prototypes'); + } + + // Add Prototype Sub Menu Item - Displays a form to add a new prototype + public function glmProtoAdminMenuAdd () + { + $this->controller('add'); + } + + /** + * Admin controller + * + * This method is called by a plugin menu method. It is responsible for + * executing the approriate model, combining model data with a view, and + * outputing the result. It is therefore the core of the controller. + * + * This controller is supplied a menu item name and then determines if + * there is an additional action related to that menu item that needs to be + * executed rather than the default menu action. + * + * All models should return an array containing the following. + * + * 'status' + * + * True if successfull and false if there was a fatal failure. + * + * 'menuItemRedirect' + * + * If not false, provides a menu item the controller should + * execute after this one. Normally if this is used, there would also be a + * modelRedirect value supplied as well. + * + * 'modelRedirect' + * + * If not false, provides an action the controller should execute after + * this one. + * + * 'view' + * + * A suggested view name that the contoller should use instead of the + * default view for this model or false to indicate that the default view + * should be used. + * + * 'data' + * + * Data that the model is returning for use in merging with the view to + * produce output. + * + * For a better explanation of how this all works, see the description for + * this class. + * + * Controller parameters + * + * @menuItem string Name of the menu item that is being processed + * + * @return void + * @access public + */ + public function controller ($menuItem) + { + + /* + * Determine model to execute + */ + + // Default action is "index" + $action = 'index'; + + // Get any requested "action" from a form submission modify path/name + // accordingly + if (isset($_REQUEST['glm_action']) && $_REQUEST['glm_action'] != '') { + $a = sanitize_text_field($_REQUEST['glm_action']); + if ($a != '') { + $action = $a; + } + } + + // Loop as long as there's a model redirect. + do { + + // Verify Menu item and action using array at top of this file + if (! in_array($action, + $GLOBALS['glmProtoAdminValidActions'][$menuItem])) { + $menuItem = 'error'; + $action = 'badAction'; + } + + /* + * Execute the selected model + */ + // Build model and path and class names and load the model + $modelName = GLM_PROTO_PLUGIN_DIR . '/models/admin/' . $menuItem . + '/' . $action . '.php'; + $className = 'glmProtoAdmin_' . $menuItem . '_' . $action; + require_once ($modelName); + + // Instantiate the model and ask it to perform the work + $model = new $className($this->wpdb); + $results = $model->modelAction(); + + // Check if there's been a model redirect request + $modelRedirect = false; + if ($results['modelRedirect']) { + + // Set the new model action + $action = $results['modelRedirect']; + + // Check if there's also a menu item change + if ($results['menuItemRedirect']) { + $menuItem = $results['menuItemRedirect']; + } + + $modelRedirect = true; + } + + // Loop again if there's a model redirect + } while ($modelRedirect); + + /* + * Check model results + */ + + // Get suggested view + $view = $results['view']; + + // If there's a general model failure use the error view + if (! $results['status']) { + $view = 'admin/error/index.html'; + } + + /* + * Merge data returned from the model with the selected view + */ + + // Load Smarty Template support + require (GLM_PROTO_PLUGIN_DIR . '/lib/smartyTemplateSupport.php'); + $smarty = new smartyTemplateSupport(); + + // Add some standard parameters + $smarty->templateAssign( + array( + 'request_uri' => $_SERVER['REQUEST_URI'] + )); + + // Add data from model to Smarty template + $haveData = false; + if (is_array($results['data']) && count($results['data']) > 0) { + $haveData = true; + $smarty->templateAssign('data', $results['data']); + } + $smarty->templateAssign('haveData', $haveData); + + // $x = $smarty->template->getTemplateVars(); + // echo "
".print_r($x,1)."
"; + + // Generate output from model data and view + $smarty->template->display($view); + } } diff --git a/controllers/front.php b/controllers/front.php index e7a39b1..6ba764c 100644 --- a/controllers/front.php +++ b/controllers/front.php @@ -1,6 +1,7 @@ wpdb = $wpdb; - - /* - * This plugin does not currently have front-end functionality - */ - } + /** + * WordPress Database Object + * + * @var $wpdb + * @access public + */ + public $wpdb; + + public function __construct () + { + + // Save WordPress Database object + $this->wpdb = $wpdb; + /* + * This plugin does not currently have front-end functionality + */ + } } ?> \ No newline at end of file diff --git a/css/index.css b/css/index.css index b523bb6..c0bd6e9 100644 --- a/css/index.css +++ b/css/index.css @@ -1,6 +1,7 @@ .glm-proto-required { - color: red; + color: red; } + .glm-proto-error { - color: red; + color: red; } \ No newline at end of file diff --git a/index.php b/index.php index b1fae7b..85e9752 100644 --- a/index.php +++ b/index.php @@ -11,231 +11,263 @@ /** * Gaslight Media Prototype Management and Display - * Index + * Index * * PHP version 5.5 * * @category glmWordPressPlugin - * @package glmPrototypeManagement - * @author Chuck Scott - * @license http://www.gaslightmedia.com Gaslightmedia - * @version 1.0 + * @package glmPrototypeManagement + * @author Chuck Scott + * @license http://www.gaslightmedia.com Gaslightmedia + * @version 1.0 */ -/* Copyright 2014 Charles Scott (email : cscott@gaslightmedia.com) - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License, version 2, as -published by the Free Software Foundation. - -This program 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 -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -/********************************************************************************* - -Directory and File Structure - - index.php Index file for this plugin. All plugin processing starts - here. (See "Process Flow" below.) - - controllers Directory containing any controllers. Typically there - would be admin and front controllers in this directory. - These controllers do the general setup for the plugin, - determine and call the appropriate model, determine the - appropriate view, then merge any data returned by the model - with the view and output the result as appropriate. - - When executed, a model may determine that it cannot handle - the current request and return such a notice to the controller - possibly with a suggested model to execute. Models may also - return a desired view back to the controller based on the - result of processing, but should do so in a generic way so - as to permit multi-lingual output and use of multiple "skins" - (but not to the exception of appropriate use of WordPress - Themes). - - css Directory containing any css files specific to this plugin. - The use of additional styling should be kept to a minimum - so as to not interfere with the application of WordPress - default styling and Themes. - - js Directory containing any JAVAscript files specific to this - Plugin. This directory should be reserved for general script - files that provide functionality that can be used in various - views. Any JAVAscript that is specific to a view should be - located along with the associated view as it is logically - part of the view. - - lib Directory containing any class or function libraries that - are used generally by this plugin. Any class or other code - that is specific to a particular model should be located - in, or along with, that model since it is logically - associated only with that model. - - misc Directory containing ancillary directories and files. This - might be used for things like cach directories. An example - might be the "smarty" directory for Smaarty Templates. - - models Directory containing model files that execute a specific - process in this plugin. If this is a simple plugin, then - the model files can be placed directly in this directory. - If it's a more complex plugin, then there should be sub- - directories for various groupings of related model files. - - An individual model may consist of a grouping of files, - such as additional class files, that are specific only to - that model. In that case, these should be located in a - subdirectory under where the model file called by the - controller is located and that directory should be named - so as to be obviously associated with that model. - - There are three special files in the models directory. These - are activate.php, deactivate.php, and uninstall.php. These - are called via hooks setup in this file and should always - be here. If they do not provide any real functionality, they - should at least be a shell that can be called for those - situations. - - views Directory containing view files for producing output upon - request of a model file in the models directory. If this - is a simply plugin, then the view files can be placed - directly in this directory. If it's a more complex plugin, - then there should be sub-directories for the various - groupings of related view files. If using sub-directories, - those should generally match the associated model directories. - It may also be wise to use separate front and admin - directories under views to keep things organized. - - Additionally, views may be grouped in such a way that they - support the selection of various "skins" that output in - different ways, although any styling should be provided by - WordPress Themes or use default WordPress styling. - -Process Flow - - * WordPress calls the plugin index file. All plugin processing starts here. - - * The plugin index file performs the following operations ... - - Sets-up any required plugin-wide defines and data - - Instatiates any plugin-wide classes and objects - - Sets-up any plugin-wide WordPress hooks - - Determines which controller is to be executed - - Executes the selected controller - - * The selected controller performs the following operations ... - - Sets-up any controller specific defines and data - - Instatiates any controller specific classes and objects - - Sets-up any controller specific WordPress hooks - - Determines which model process is to be executed - - Executes the selected model - - * The selected model performs the following operations ... - - Sets-up any model specific defines and data - - Instatiates any model specific classes and objects - - Sets-up any model specific WordPress hooks - - Performs any specific processing required of the model - - Determines which view is to be used to generate output - - Generates output based on model data and the selected view - - * WordPress wraps everything up - -**********************************************************************************/ +/* + * Copyright 2014 Charles Scott (email : cscott@gaslightmedia.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +/** + * ******************************************************************************* + * + * *** Directory and File Structure *** + * + * index.php + * + * Index file for this plugin. All plugin processing starts here. (See + * "Process Flow" below.) + * + * controllers + * + * Directory containing any controllers. Typically there + * would be admin and front controllers in this directory. + * These controllers do the general setup for the plugin, + * determine and call the appropriate model, determine the + * appropriate view, then merge any data returned by the model + * with the view and output the result as appropriate. + * + * When executed, a model may determine that it cannot handle + * the current request and return such a notice to the controller + * possibly with a suggested model to execute. Models may also + * return a desired view back to the controller based on the + * result of processing, but should do so in a generic way so + * as to permit multi-lingual output and use of multiple "skins" + * (but not to the exception of appropriate use of WordPress + * Themes). + * + * css + * + * Directory containing any css files specific to this plugin. + * The use of additional styling should be kept to a minimum + * so as to not interfere with the application of WordPress + * default styling and Themes. + * + * js + * + * Directory containing any JAVAscript files specific to this + * Plugin. This directory should be reserved for general script + * files that provide functionality that can be used in various + * views. Any JAVAscript that is specific to a view should be + * located along with the associated view as it is logically + * part of the view. + * + * lib + * + * Directory containing any class or function libraries that + * are used generally by this plugin. Any class or other code + * that is specific to a particular model should be located + * in, or along with, that model since it is logically + * associated only with that model. + * + * misc + * + * Directory containing ancillary directories and files. This + * might be used for things like cach directories. An example + * might be the "smarty" directory for Smaarty Templates. + * + * models + * + * Directory containing model files that execute a specific + * process in this plugin. If this is a simple plugin, then + * the model files can be placed directly in this directory. + * If it's a more complex plugin, then there should be sub- + * directories for various groupings of related model files. + * + * An individual model may consist of a grouping of files, + * such as additional class files, that are specific only to + * that model. In that case, these should be located in a + * subdirectory under where the model file called by the + * controller is located and that directory should be named + * so as to be obviously associated with that model. + * + * There are three special files in the models directory. These + * are activate.php, deactivate.php, and uninstall.php. These + * are called via hooks setup in this file and should always + * be here. If they do not provide any real functionality, they + * should at least be a shell that can be called for those + * situations. + * + * views + * + * Directory containing view files for producing output upon + * request of a model file in the models directory. If this + * is a simply plugin, then the view files can be placed + * directly in this directory. If it's a more complex plugin, + * then there should be sub-directories for the various + * groupings of related view files. If using sub-directories, + * those should generally match the associated model directories. + * It may also be wise to use separate front and admin + * directories under views to keep things organized. + * + * Additionally, views may be grouped in such a way that they + * support the selection of various "skins" that output in + * different ways, although any styling should be provided by + * WordPress Themes or use default WordPress styling. + * + * *** Process Flow *** + * + * WordPress calls the plugin index file. All plugin processing starts here. + * + * The plugin index file performs the following operations ... + * - Sets-up any required plugin-wide defines and data + * - Instatiates any plugin-wide classes and objects + * - Sets-up any plugin-wide WordPress hooks + * - Determines which controller is to be executed + * - Executes the selected controller + * + * The selected controller performs the following operations ... + * - Sets-up any controller specific defines and data + * - Instatiates any controller specific classes and objects + * - Sets-up any controller specific WordPress hooks + * - Determines which model process is to be executed + * - Executes the selected model + * + * The selected model performs the following operations ... + * - Sets-up any model specific defines and data + * - Instatiates any model specific classes and objects + * - Sets-up any model specific WordPress hooks + * - Performs any specific processing required of the model + * - Determines which view is to be used to generate output + * - Generates output based on model data and the selected view + * + * WordPress wraps everything up + * + * ******************************************************************************** + */ /* - * Set some parameters + * + * Set standard parameters + * */ // WordPress Debugging -if (!defined( 'WP_DEBUG' )) { - define( 'WP_DEBUG', true ); +if (! defined('WP_DEBUG')) { + define('WP_DEBUG', true); } // Plugin URL -if (!defined( 'GLM_PROTO_PLUGIN_URL' )) { - define( 'GLM_PROTO_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); +if (! defined('GLM_PROTO_PLUGIN_URL')) { + define('GLM_PROTO_PLUGIN_URL', plugin_dir_url(__FILE__)); } // Plugin Directory -if (!defined( 'GLM_PROTO_PLUGIN_DIR' )) { - define( 'GLM_PROTO_PLUGIN_DIR', dirname( __FILE__ ) ); +if (! defined('GLM_PROTO_PLUGIN_DIR')) { + define('GLM_PROTO_PLUGIN_DIR', dirname(__FILE__)); } // Custom plugin post type (for wp_post entries) -define( 'GLM_PROTO_PLUGIN_POST_TYPE', 'glm_proto'); +define('GLM_PROTO_PLUGIN_POST_TYPE', 'glm_proto'); /* + * * Activate, Deactivate, Uninstall hooks + * */ // Activate -function glmProtoPluginActivate() +function glmProtoPluginActivate () { - global $wpdb; - require_once(GLM_PROTO_PLUGIN_DIR.'/models/activate.php'); - new glmProtoPluginActivate($wpdb); + global $wpdb; + require_once (GLM_PROTO_PLUGIN_DIR . '/models/activate.php'); + new glmProtoPluginActivate($wpdb); } -register_activation_hook( __FILE__, 'glmProtoPluginActivate' ); +register_activation_hook(__FILE__, 'glmProtoPluginActivate'); // Deactivate -function glmProtoPluginDeactivate() +function glmProtoPluginDeactivate () { - global $wpdb; - require_once(GLM_PROTO_PLUGIN_DIR.'/models/deactivate.php'); - new glmProtoPluginDectivate($wpdb); + global $wpdb; + require_once (GLM_PROTO_PLUGIN_DIR . '/models/deactivate.php'); + new glmProtoPluginDectivate($wpdb); } -register_deactivation_hook( __FILE__, 'glmProtoPluginDeativate' ); +register_deactivation_hook(__FILE__, 'glmProtoPluginDeativate'); // Uninstall -function glmProtoPluginUninstall() +function glmProtoPluginUninstall () { - global $wpdb; - require_once(GLM_PROTO_PLUGIN_DIR.'/models/uninstall.php'); - new glmProtoPluginUninstall($wpdb); + global $wpdb; + require_once (GLM_PROTO_PLUGIN_DIR . '/models/uninstall.php'); + new glmProtoPluginUninstall($wpdb); } -register_uninstall_hook( __FILE__, 'glmProtoPluginUninstall' ); +register_uninstall_hook(__FILE__, 'glmProtoPluginUninstall'); /* + * * Style Sheets + * */ -wp_register_style('glmProtoIndexStyle', GLM_PROTO_PLUGIN_URL.'css/index.css'); -wp_enqueue_style( 'glmProtoIndexStyle' ); + +// A simple set of styles for things I haven't found as a WordPress default yet +wp_register_style('glmProtoIndexStyle', GLM_PROTO_PLUGIN_URL . 'css/index.css'); +wp_enqueue_style('glmProtoIndexStyle'); /* - * Scripts + * + * JAVAscript files that need to be enqueued. + * */ + add_action('admin_enqueue_scripts', 'glmProtoScripts'); -function glmProtoScripts() { - wp_enqueue_media(); - wp_register_script('glm-proto-index-js', GLM_PROTO_PLUGIN_URL.'js/index.js', array('jquery')); - wp_enqueue_script('glm-proto-index-js'); -} +function glmProtoScripts () +{ + wp_enqueue_media(); + wp_register_script('glm-proto-index-js', + GLM_PROTO_PLUGIN_URL . 'js/index.js', + array( + 'jquery' + )); + wp_enqueue_script('glm-proto-index-js'); +} /* - * Standard includes + * + * Standard includes and classes + * */ -require_once(GLM_PROTO_PLUGIN_DIR.'/lib/smartyTemplateSupport.php'); /* - * Determine which controller to load + * Determine which controller to load */ if (is_admin()) { - - require_once(GLM_PROTO_PLUGIN_DIR.'/controllers/admin.php'); - new glmProtoAdmin($wpdb); + require_once (GLM_PROTO_PLUGIN_DIR . '/controllers/admin.php'); + new glmProtoAdmin($wpdb); } else { - - require_once(GLM_PROTO_PLUGIN_DIR.'/controllers/front.php'); - new glmProtoFront($wpdb); - -} + require_once (GLM_PROTO_PLUGIN_DIR . '/controllers/front.php'); + new glmProtoFront($wpdb); +} ?> \ No newline at end of file diff --git a/lib/smartyTemplateSupport.php b/lib/smartyTemplateSupport.php index 5efb8d4..39244a3 100644 --- a/lib/smartyTemplateSupport.php +++ b/lib/smartyTemplateSupport.php @@ -1,6 +1,7 @@ template. - * + * This class loads, instatiates, and configures Smarty Templates for this + * plugin. + * + * This class should be extended with the class that will need to use Smarty. + * This + * will make the $template object available to the class extending this one as + * $this->template. + * * The templateAssign() method simplifies adding parameters to the template. - * - * To use this class you must execute the following. No need to assign the result. - * - * new smartyTemplateSupport(); - * + * + * To use this class you must execute the following. No need to assign the + * result. + * + * new smartyTemplateSupport(); + * */ -abstract class smartyTemplateSupport +class smartyTemplateSupport { - /** - * Smarty Template Object - * @var $template - * @access public - */ - public $template; - /** - * Setup Smarty Templates - * - * Initializes Smarty Templates and assigns the Smarty object to $this->template - * for use in any classes that extend this class. - * - * To initialize Smarty Templates use the following call in the contructor of the - * extended class. - * - * $this->setupSmarty(); - * - * (no prameters) + * Smarty Template Object * - * @return void + * @var $template * @access public */ - public function setupSmarty() - { - - /* - * Load and instatiate Smarty Templates - */ - require(GLM_PROTO_PLUGIN_DIR.'/lib/Smarty-3.1.21/libs/Smarty.class.php'); + public $template; - $this->template = new Smarty(); - - /* - * Configure Smarty Templates for this site - */ - $this->template->setTemplateDir(GLM_PROTO_PLUGIN_DIR.'/views'); - $this->template->setCompileDir(GLM_PROTO_PLUGIN_DIR.'/misc/smarty/templates_c'); - $this->template->setCacheDir(GLM_PROTO_PLUGIN_DIR.'/misc/smarty/cache'); - $this->template->setConfigDir(GLM_PROTO_PLUGIN_DIR.'/misc/smarty/configs'); + /* + * Smarty Template Support Constructor + * + * This constructor loads and instatiates smarty templates, then adds some + * standard template parameters. + * + * @return object Class object + * + */ + public function __construct () + { - } - - /* - * Assign parameters to the template - * - * This method assigns either one parameter to the template object in this class - * or an array of parameters. - * - * Submit an array of parameters to the template. - * - * @param array Array of arrays with parameter name (key), value pairs - * - * or - * - * Submit one parameter to the template - * - * @param text parameter name - * @param {whatever} parameter value - * - * @return void - */ - public function templateAssign($param, $value = null) - { - - // If this is a single assignment - if ($value !== null) { + /* + * Load and instatiate Smarty Templates + */ + require (GLM_PROTO_PLUGIN_DIR . + '/lib/Smarty-3.1.21/libs/Smarty.class.php'); - $this->template->assign($param, $value); + $this->template = new Smarty(); - // Otherwise it's an array of parameter/value pairs - } elseif (is_array($param)) { - - while (list($key, $value) = each($param)) { - $this->template->assign($key, $value); - } - - } - - } - + /* + * Configure Smarty Templates for this site + */ + $this->template->setTemplateDir(GLM_PROTO_PLUGIN_DIR . '/views'); + $this->template->setCompileDir( + GLM_PROTO_PLUGIN_DIR . '/misc/smarty/templates_c'); + $this->template->setCacheDir( + GLM_PROTO_PLUGIN_DIR . '/misc/smarty/cache'); + $this->template->setConfigDir( + GLM_PROTO_PLUGIN_DIR . '/misc/smarty/configs'); + } -} + /* + * Assign parameters to the template + * + * This method assigns either one parameter to the template object in this + * class + * or an array of parameters. + * + * Submit an array of parameters to the template. + * + * @param array Array of arrays with parameter name (key), value pairs + * + * or + * + * Submit one parameter to the template + * + * @param text parameter name + * @param {whatever} parameter value + * + * @return void + */ + public function templateAssign ($param, $value = null) + { + // If this is a single assignment + if ($value !== null) { + $this->template->assign($param, $value); + + // Otherwise it's an array of parameter/value pairs + } elseif (is_array($param)) { + + while (list ($key, $value) = each($param)) { + $this->template->assign($key, $value); + } + } + } +} ?> \ No newline at end of file diff --git a/models/activate.php b/models/activate.php index eee198e..4d6b5cd 100644 --- a/models/activate.php +++ b/models/activate.php @@ -1,4 +1,5 @@ wpdb = $wpdb; - - // Perform any activation tasks here - } + /** + * WordPress Database Object + * + * @var $wpdb + * @access public + */ + public $wpdb; + + /* + * Constructor + * + * Performs all the work for this model + */ + public function __construct ($wpdb) + { + + // Make sure the current user has this capability + if (! current_user_can('activate_plugins')) { + return; + } + + // Save WordPress Database object + $this->wpdb = $wpdb; + + // Perform any activation tasks here + } } ?> \ No newline at end of file diff --git a/models/admin/add.php b/models/admin/add.php deleted file mode 100644 index 373e246..0000000 --- a/models/admin/add.php +++ /dev/null @@ -1,221 +0,0 @@ - - * @license http://www.gaslightmedia.com Gaslightmedia - * @release admin.php,v 1.0 2014/10/31 19:31:47 cscott Exp $ - * @link http://dev.gaslightmedia.com/ - */ - -/* - * This class performs the work needed to add a new prototype. - * - * If a prototype is properly submitted, it is stored in the wp_posts table - * using the following fields. - * - * post_date Current date - * post_date_gmt Current date in GMT - * post_content Serialized array with 'title', 'background', and 'prototype' - * post_title Title of prototype - * post_type 'glm_proto' - * - */ -class glmProtoAdminAddModel extends smartyTemplateSupport -{ - /** - * WordPress Database Object - * @var $wpdb - * @access public - */ - public $wpdb; - /** - * Selected Template File - * @var $templateFile - * @access public - */ - public $templateFile; - - /* - * Constructor - * - * Performs all the work for this model - */ - public function __construct($wpdb) - { - - // Save WordPress Database object - $this->wpdb = $wpdb; - - // Start Smarty Templates - creates $this->template object - $this->setupSmarty(); - - $templateData = array( - 'glm_proto_title' => '', - 'glm_proto_title_error' => '', - 'glm_proto_width' => '1500', - 'glm_proto_width_error' => '', - 'glm_proto_height' => '1200', - 'glm_proto_height_error' => '', - 'glm_proto_background' => '', - 'glm_proto_background_error' => '', - 'glm_proto_prototype' => '', - 'glm_proto_prototype_error' => '' - ); - - /* - * If a protototype is being submitted - */ - $submitError = false; - $submitted = false; - if ($_REQUEST['glm_proto_action'] == 'add_submit') { - - // Clean up all input - $templateData['glm_proto_title'] = sanitize_text_field($_REQUEST['glm_proto_title']); - $templateData['glm_proto_width'] = sanitize_text_field($_REQUEST['glm_proto_width']); - $templateData['glm_proto_height'] = sanitize_text_field($_REQUEST['glm_proto_height']); - $templateData['glm_proto_background'] = sanitize_text_field($_REQUEST['glm_proto_background']); - $templateData['glm_proto_prototype'] = sanitize_text_field($_REQUEST['glm_proto_prototype']); - - // Check title field - if ($templateData['glm_proto_title'] == '') { - $templateData['glm_proto_title_error'] = 'Required title not supplied'; - $submitError = true; - } - - // Check width field - if ($templateData['glm_proto_width'] == '') { - $templateData['glm_proto_width_error'] = 'Required width not supplied'; - $submitError = true; - } - - // Check height field - if ($templateData['glm_proto_height'] == '') { - $templateData['glm_proto_height_error'] = 'Required height not supplied'; - $submitError = true; - } - - // Check background image - Not required but must exist if provided - if ($templateData['glm_proto_background'] != '' && - !$this->glmProtoIsUploaded($templateData['glm_proto_background']) ) - { - $templateData['glm_proto_background_error'] = 'Supplied background image does not exists'; - $submitError = true; - } - - // Check prototype image - if ($templateData['glm_proto_prototype'] == '') { - $templateData['glm_proto_prototype_error'] = 'Required prototype image not supplied '; - $submitError = true; - } else { - $exists = $this->glmProtoIsUploaded($templateData['glm_proto_prototype']); - if (!$exists) { - $templateData['glm_proto_prototype_error'] .= 'Supplied prototype image does not exists'; - $submitError = true; - } - } - - if (!$submitError) { - - // Prepair data for storage - $date = date('Y-m-d', time()); - $timezoneBackup = date_default_timezone_get(); - date_default_timezone_set("GMT"); - $gmtDate = date('Y-m-d', time()); - date_default_timezone_set($timezoneBackup); - - $content = serialize( - array( - 'title' => $templateData['glm_proto_title'], - 'width' => $templateData['glm_proto_width'], - 'height' => $templateData['glm_proto_height'], - 'background' => $templateData['glm_proto_background'], - 'prototype' => $templateData['glm_proto_prototype'] - ) - ); - - // Store into wp_posts table - $result = $this->wpdb->insert( - 'wp_posts', - array( - 'post_date' => $date, - 'post_date_gmt' => $gmtDate, - 'post_content' => $content, - 'post_title' => $templateData['glm_proto_title'], - 'post_type' => 'glm_proto' - ) - ); - - // If there was a problem storing the prototype, pass that to the template - if (!$result) { - $templateData['glm_proto_title_error'] = 'There was an unknown problem storing this prototype.'; - } - - // Select view - $this->templateFile = 'admin/prototype_submitted.html'; - - // Add template parameters - $this->templateAssign($templateData); - - $prototypeSubmitted = true; - } - - } - - /* - * - */ - if ($prototypeSubmitted) { - - // No prototype was submitted so display the form. - } else { - - // Select view - $this->templateFile = 'admin/prototype_add.html'; - - // Add template parameters - $this->templateAssign('request_uri', $_SERVER['REQUEST_URI']); - $this->templateAssign($templateData); - - } - - // Output our template results - $this->template->display($this->templateFile); - - } - - /* - * Check if a file supposedly uploaded to WordPress exists - * - * This method accepts a URL to a WordPress file or image, strips the - * WordPress base file URL to get the remaining path and file name for - * the file, then adds the base path for WordPress files and checks to - * see if the file exists. - * - * @param string URL of uploaded WordPress file or image - * - * @return bool True if it exists, false if not - */ - private function glmProtoIsUploaded($url) { - - // Get current WordPress upload directory/url information - $paths = wp_upload_dir(); - $base = $paths['baseurl']; - $path = $paths['basedir']; - - // Strip base directory from supplied URL - $file = substr($url, strlen($base)); - - // Check if file exists - $exists = file_exists($path.$file); - return $exists; - - } -} - -?> \ No newline at end of file diff --git a/models/admin/add/index.php b/models/admin/add/index.php new file mode 100644 index 0000000..6bc1ae7 --- /dev/null +++ b/models/admin/add/index.php @@ -0,0 +1,260 @@ + + * @license http://www.gaslightmedia.com Gaslightmedia + * @release admin.php,v 1.0 2014/10/31 19:31:47 cscott Exp $ + * @link http://dev.gaslightmedia.com/ + */ + +/* + * This class performs the work needed to add a new prototype. + * + * If a prototype is properly submitted, it is stored in the wp_posts table + * using the following fields. + * + * post_date Current date + * post_date_gmt Current date in GMT + * post_content Serialized array with 'title', 'background', and 'prototype' + * post_title Title of prototype + * post_type 'glm_proto' + * + */ +class glmProtoAdmin_add_index +{ + + /** + * WordPress Database Object + * + * @var $wpdb + * @access public + */ + public $wpdb; + + /* + * Constructor + * + * This contructor sets up this model. At this time that only includes + * storing away the WordPress data object. + * + * @return object Class object + * + */ + public function __construct ($wpdb) + { + + // Save WordPress Database object + $this->wpdb = $wpdb; + } + + /* + * Perform Model Action + * + * This method does the work for this model and returns any resulting data + * + * @return array Status and data array + * + * 'status' + * + * True if successfull and false if there was a fatal failure. + * + * 'menuItemRedirect' + * + * If not false, provides a menu item the controller should + * execute after this one. Normally if this is used, there would also be a + * modelRedirect value supplied as well. + * + * 'modelRedirect' + * + * If not false, provides an action the controller should execute after + * this one. + * + * 'view' + * + * A suggested view name that the contoller should use instead of the + * default view for this model or false to indicate that the default view + * should be used. + * + * 'data' + * + * Data that the model is returning for use in merging with the view to + * produce output. + * + */ + public function modelAction () + { + $data = array( + 'glm_proto_title' => '', + 'glm_proto_title_error' => '', + 'glm_proto_width' => '1500', + 'glm_proto_width_error' => '', + 'glm_proto_height' => '1200', + 'glm_proto_height_error' => '', + 'glm_proto_background' => '', + 'glm_proto_background_error' => '', + 'glm_proto_prototype' => '', + 'glm_proto_prototype_error' => '' + ); + + /* + * If a protototype is being submitted + */ + $submitError = false; + $submitted = false; + if (isset($_REQUEST['glm_proto_title'])) { + + // Clean up all input + $data['glm_proto_title'] = sanitize_text_field( + $_REQUEST['glm_proto_title']); + $data['glm_proto_width'] = sanitize_text_field( + $_REQUEST['glm_proto_width']); + $data['glm_proto_height'] = sanitize_text_field( + $_REQUEST['glm_proto_height']); + $data['glm_proto_background'] = sanitize_text_field( + $_REQUEST['glm_proto_background']); + $data['glm_proto_prototype'] = sanitize_text_field( + $_REQUEST['glm_proto_prototype']); + + // Check title field + if ($data['glm_proto_title'] == '') { + $data['glm_proto_title_error'] = 'Required title not supplied'; + $submitError = true; + } + + // Check width field + if ($data['glm_proto_width'] == '') { + $data['glm_proto_width_error'] = 'Required width not supplied'; + $submitError = true; + } + + // Check height field + if ($data['glm_proto_height'] == '') { + $data['glm_proto_height_error'] = 'Required height not supplied'; + $submitError = true; + } + + // Check background image - Not required but must exist if provided + if ($data['glm_proto_background'] != '' && ! $this->glmProtoIsUploaded( + $data['glm_proto_background'])) { + $data['glm_proto_background_error'] = 'Supplied background image does not exists'; + $submitError = true; + } + + // Check prototype image + if ($data['glm_proto_prototype'] == '') { + $data['glm_proto_prototype_error'] = 'Required prototype image not supplied '; + $submitError = true; + } else { + $exists = $this->glmProtoIsUploaded( + $data['glm_proto_prototype']); + if (! $exists) { + $data['glm_proto_prototype_error'] .= 'Supplied prototype image does not exists'; + $submitError = true; + } + } + + if (! $submitError) { + + // Prepair data for storage + $date = date('Y-m-d', time()); + $timezoneBackup = date_default_timezone_get(); + date_default_timezone_set("GMT"); + $gmtDate = date('Y-m-d', time()); + date_default_timezone_set($timezoneBackup); + + $content = serialize( + array( + 'title' => $data['glm_proto_title'], + 'width' => $data['glm_proto_width'], + 'height' => $data['glm_proto_height'], + 'background' => $data['glm_proto_background'], + 'prototype' => $data['glm_proto_prototype'] + )); + + // Store into wp_posts table + $result = $this->wpdb->insert('wp_posts', + array( + 'post_date' => $date, + 'post_date_gmt' => $gmtDate, + 'post_content' => $content, + 'post_title' => $data['glm_proto_title'], + 'post_type' => 'glm_proto' + )); + + // If there was a problem storing the prototype, pass that to + // the template + if (! $result) { + $data['glm_proto_title_error'] = 'There was an unknown problem storing this prototype.'; + } + + $prototypeSubmitted = true; + } + } + + // If submitted OK then display success page + $menuItemRedirect = false; + $modelRedirect = false; + if ($prototypeSubmitted) { + + $view = 'admin/add/submitted.html'; + + // Prototype was not successfully submitted so redisplay the form. + } else { + + // Select view + $view = 'admin/add/index.html'; + } + + // Return status, suggested view, and data to controller + return array( + 'status' => true, + 'menuItemRedirect' => false, + 'modelRedirect' => false, + 'view' => $view, + 'data' => $data + ); + } + + /* + * Check if a file supposedly uploaded to WordPress exists + * + * This method accepts a URL to a WordPress file or image, strips the + * WordPress base file URL to get the remaining path and file name for + * the file, then adds the base path for WordPress files and checks to + * see if the file exists. + * + * @param string URL of uploaded WordPress file or image + * + * @return bool True if it exists, false if not + */ + private function glmProtoIsUploaded ($url) + { + + // Get current WordPress upload directory/url information + $paths = wp_upload_dir(); + $base = $paths['baseurl']; + $path = $paths['basedir']; + + // Strip base directory from supplied URL + $file = substr($url, strlen($base)); + + // If there's now no file name + if (trim($file) == '') { + return false; + } + + // Check if file exists + $exists = file_exists($path . $file); + + return $exists; + } +} + +?> \ No newline at end of file diff --git a/models/admin/display.php b/models/admin/display.php deleted file mode 100644 index d9a050b..0000000 --- a/models/admin/display.php +++ /dev/null @@ -1,89 +0,0 @@ - - * @license http://www.gaslightmedia.com Gaslightmedia - * @release admin.php,v 1.0 2014/10/31 19:31:47 cscott Exp $ - * @link http://dev.gaslightmedia.com/ - */ - -/* - * This class performs the work needed to list existing prototypes. - */ -class glmProtoAdminDisplayModel extends smartyTemplateSupport -{ - - /** - * WordPress Database Object - * @var $wpdb - * @access public - */ - public $wpdb; - /** - * Selected Template File - * @var $templateFile - * @access public - */ - public $templateFile; - - /* - * Constructor - * - * Performs all the work for this model - */ - public function __construct($wpdb) - { - // Save WordPress Database object - $this->wpdb = $wpdb; - - // Start Smarty Templates - creates $this->template object - $this->setupSmarty(); - - // Check if we recieved a prototype ID - $id = $_REQUEST['proto_id'] - 0; - if ($id > 0) { - - // Get the prototype data - $res = $this->wpdb->get_row(" - SELECT id, - DATE_FORMAT(post_date, '%Y-%m-%d') p_date, - post_title, - post_content - FROM wp_posts - WHERE post_type = 'glm_proto' - AND id = $id - ", ARRAY_A ); - - // If we have results - - if ($res['post_content']) { - - // Break out the prototype file data - $d = unserialize($res['post_content']); - $res['content'] = $d; - - } - - $this->templateAssign($res); - - - } - - // Select view - $this->templateFile = 'admin/prototype_display.html'; - - // Output our template results - $this->template->display($this->templateFile); - exit; - - } - -} - -?> \ No newline at end of file diff --git a/models/admin/error/badAction.php b/models/admin/error/badAction.php new file mode 100644 index 0000000..bc2c689 --- /dev/null +++ b/models/admin/error/badAction.php @@ -0,0 +1,112 @@ + + * @license http://www.gaslightmedia.com Gaslightmedia + * @release admin.php,v 1.0 2014/10/31 19:31:47 cscott Exp $ + * @link http://dev.gaslightmedia.com/ + */ + +/* + * This model is called if a bad action has been requested. + * + */ +class glmProtoAdmin_error_badAction +{ + + /** + * WordPress Database Object + * + * @var $wpdb + * @access public + */ + public $wpdb; + + /* + * Constructor + * + * This contructor performs the work for this model. This model returns + * an array containing the following. + * + * 'status' + * + * True if successfull and false if there was a fatal failure. + * + * 'view' + * + * A suggested view name that the contoller should use instead of the + * default view for this model or false to indicate that the default view + * should be used. + * + * 'data' + * + * Data that the model is returning for use in merging with the view to + * produce output. + * + * @wpdb object WordPress database object + * + * @return array Array containing status, suggested view, and any data + */ + public function __construct ($wpdb) + { + $this->wpdb = $wpdb; + } + + /* + * Perform Model Action + * + * This method does the work for this model and returns any resulting data + * + * @return array Status and data array + * + * 'status' + * + * True if successfull and false if there was a fatal failure. + * + * 'menuItemRedirect' + * + * If not false, provides a menu item the controller should + * execute after this one. Normally if this is used, there would also be a + * modelRedirect value supplied as well. + * + * 'modelRedirect' + * + * If not false, provides an action the controller should execute after + * this one. + * + * 'view' + * + * A suggested view name that the contoller should use instead of the + * default view for this model or false to indicate that the default view + * should be used. + * + * 'data' + * + * Data that the model is returning for use in merging with the view to + * produce output. + * + */ + public function modelAction () + { + + // No work to perform here so just return status + + // Return status, any suggested view, and any data to controller + return array( + 'status' => true, + 'menuItemRedirect' => false, + 'modelRedirect' => false, + 'view' => 'admin/error/badAction.html', + 'data' => false + ); + } +} + +?> \ No newline at end of file diff --git a/models/admin/error/index.php b/models/admin/error/index.php new file mode 100644 index 0000000..43437c9 --- /dev/null +++ b/models/admin/error/index.php @@ -0,0 +1,70 @@ + + * @license http://www.gaslightmedia.com Gaslightmedia + * @release admin.php,v 1.0 2014/10/31 19:31:47 cscott Exp $ + * @link http://dev.gaslightmedia.com/ + */ + +/* + * This model is called if there is a generic/unknown error + * + */ +class glmProtoAdminError +{ + + /** + * WordPress Database Object + * + * @var $wpdb + * @access public + */ + public $wpdb; + + /* + * Constructor + * + * This contructor performs the work for this model. This model returns + * an array containing the following. + * + * 'status' + * + * True if successfull and false if there was a fatal failure. + * + * 'view' + * + * A suggested view name that the contoller should use instead of the + * default view for this model or false to indicate that the default view + * should be used. + * + * 'data' + * + * Data that the model is returning for use in merging with the view to + * produce output. + * + * @wpdb object WordPress database object + * + * @return array Array containing status, suggested view, and any data + */ + public function __construct ($wpdb) + { + // No work to perform here so just return status + + // Return status, any suggested view, and any data to controller + return array( + 'status' => true, + 'view' => false, + 'data' => false + ); + } +} + +?> \ No newline at end of file diff --git a/models/admin/list.php b/models/admin/list.php deleted file mode 100644 index 9ea18c1..0000000 --- a/models/admin/list.php +++ /dev/null @@ -1,113 +0,0 @@ - - * @license http://www.gaslightmedia.com Gaslightmedia - * @release admin.php,v 1.0 2014/10/31 19:31:47 cscott Exp $ - * @link http://dev.gaslightmedia.com/ - */ - -/* - * This class performs the work needed to list existing prototypes. - */ -class glmProtoAdminListModel extends smartyTemplateSupport -{ - /** - * WordPress Database Object - * @var $wpdb - * @access public - */ - public $wpdb; - /** - * Selected Template File - * @var $templateFile - * @access public - */ - public $templateFile; - - /* - * Constructor - * - * Performs all the work for this model - */ - public function __construct($wpdb) - { - - // If there's any other display operation requested, don't do this one. - if (isset($_REQUEST['glm_proto_action'])) { - return; - } - - // Save WordPress Database object - $this->wpdb = $wpdb; - - // Start Smarty Templates - creates $this->template object - $this->setupSmarty(); - - // Get a current list of prototypes - $list = $this->getList(); - - // If we have list entries - if ($list) { - - // Expand content array in case we need it - while (list($key, $value) = each($list)) { - $list[$key]['content'] = unserialize($value['post_content']); - } - - // Add data to templates - $this->templateAssign('haveList', true); - $this->templateAssign('prototypes', $list); - - } else { - $this->templateAssign('haveList', false); - } - - // Also add the current request URI to use for links back for display - $this->templateAssign('request_uri', $_SERVER['REQUEST_URI']); - - // Select view - $this->templateFile = 'admin/prototype_list.html'; - - // Output our template results - $this->template->display($this->templateFile); - - } - - /* - * Constructor - * - * Performs all the work for this model - */ - public function getList() - { - - // Get all prototypes - $res = $this->wpdb->get_results( " - SELECT id, - DATE_FORMAT(post_date, '%Y-%m-%d') p_date, - post_title, - post_content - FROM wp_posts - WHERE post_type = 'glm_proto' - ORDER BY post_date, post_title - ", ARRAY_A - ); - - // If we have results - if (is_array($res) && count($res) > 0) { - return $res; - } - - return false; - } - -} - -?> \ No newline at end of file diff --git a/models/admin/prototypes/delete.php b/models/admin/prototypes/delete.php new file mode 100644 index 0000000..0e0db90 --- /dev/null +++ b/models/admin/prototypes/delete.php @@ -0,0 +1,110 @@ + + * @license http://www.gaslightmedia.com Gaslightmedia + * @release admin.php,v 1.0 2014/10/31 19:31:47 cscott Exp $ + * @link http://dev.gaslightmedia.com/ + */ + +/* + * This class performs the work needed to delete an existing prototype. + */ +class glmProtoAdmin_prototypes_delete +{ + + /** + * WordPress Database Object + * + * @var $wpdb + * @access public + */ + public $wpdb; + + /* + * Constructor + * + * This contructor sets up this model. At this time that only includes + * storing away the WordPress data object. + * + * @return object Class object + * + */ + public function __construct ($wpdb) + { + + // Save WordPress Database object + $this->wpdb = $wpdb; + } + + /* + * Perform Model Action + * + * This method does the work for this model and returns any resulting data + * + * @return array Status and data array + * + * 'status' + * + * True if successfull and false if there was a fatal failure. + * + * 'menuItemRedirect' + * + * If not false, provides a menu item the controller should + * execute after this one. Normally if this is used, there would also be a + * modelRedirect value supplied as well. + * + * 'modelRedirect' + * + * If not false, provides an action the controller should execute after + * this one. + * + * 'view' + * + * A suggested view name that the contoller should use instead of the + * default view for this model or false to indicate that the default view + * should be used. + * + * 'data' + * + * Data that the model is returning for use in merging with the view to + * produce output. + * + */ + public function modelAction () + { + + // Check if we recieved a prototype ID + $id = $_REQUEST['proto_id'] - 0; + if ($id > 0) { + + // Delete the prototype data + $res = $this->wpdb->get_row( + " + DELETE + FROM wp_posts + WHERE post_type = 'glm_proto' + AND id = $id + ", ARRAY_A); + } + + // Return status - in this case we always redirect to the prototypes + // index model (list) + return array( + 'status' => true, + 'menuItemRedirect' => 'prototypes', + 'modelRedirect' => 'index', + 'view' => false, + 'data' => false + ); + } +} + +?> \ No newline at end of file diff --git a/models/admin/prototypes/display.php b/models/admin/prototypes/display.php new file mode 100644 index 0000000..49d59e1 --- /dev/null +++ b/models/admin/prototypes/display.php @@ -0,0 +1,124 @@ + + * @license http://www.gaslightmedia.com Gaslightmedia + * @release admin.php,v 1.0 2014/10/31 19:31:47 cscott Exp $ + * @link http://dev.gaslightmedia.com/ + */ + +/* + * This class performs the work needed to display an existing prototype. + */ +class glmProtoAdmin_prototypes_display +{ + + /** + * WordPress Database Object + * + * @var $wpdb + * @access public + */ + public $wpdb; + + /* + * Constructor + * + * This contructor sets up this model. At this time that only includes + * storing away the WordPress data object. + * + * @return object Class object + * + */ + public function __construct ($wpdb) + { + + // Save WordPress Database object + $this->wpdb = $wpdb; + } + + /* + * Perform Model Action + * + * This method does the work for this model and returns any resulting data + * + * @return array Status and data array + * + * 'status' + * + * True if successfull and false if there was a fatal failure. + * + * 'menuItemRedirect' + * + * If not false, provides a menu item the controller should + * execute after this one. Normally if this is used, there would also be a + * modelRedirect value supplied as well. + * + * 'modelRedirect' + * + * If not false, provides an action the controller should execute after + * this one. + * + * 'view' + * + * A suggested view name that the contoller should use instead of the + * default view for this model or false to indicate that the default view + * should be used. + * + * 'data' + * + * Data that the model is returning for use in merging with the view to + * produce output. + * + */ + public function modelAction () + { + + // Check if we recieved a prototype ID + $id = $_REQUEST['proto_id'] - 0; + if ($id > 0) { + + // Get the prototype data + $res = $this->wpdb->get_row( + " + SELECT id, + DATE_FORMAT(post_date, '%Y-%m-%d') p_date, + post_title, + post_content + FROM wp_posts + WHERE post_type = 'glm_proto' + AND id = $id + ", ARRAY_A); + + // If we have results + $success = false; + if ($res['post_content']) { + + $success = true; + + // Break out the prototype file data + $d = unserialize($res['post_content']); + $res['content'] = $d; + } + + } + + // Return status, any suggested view, and any data to controller + return array( + 'status' => $success, + 'menuItemRedirect' => false, + 'modelRedirect' => false, + 'view' => 'admin/prototypes/display.html', + 'data' => $res['content'] + ); + } +} + +?> \ No newline at end of file diff --git a/models/admin/prototypes/edit.php b/models/admin/prototypes/edit.php new file mode 100644 index 0000000..39f5533 --- /dev/null +++ b/models/admin/prototypes/edit.php @@ -0,0 +1,291 @@ + + * @license http://www.gaslightmedia.com Gaslightmedia + * @release admin.php,v 1.0 2014/10/31 19:31:47 cscott Exp $ + * @link http://dev.gaslightmedia.com/ + */ + +/* + * This class performs the work needed to add a new prototype. + * + */ +class glmProtoAdmin_prototypes_edit +{ + + /** + * WordPress Database Object + * + * @var $wpdb + * @access public + */ + public $wpdb; + + /* + * Constructor + * + * This contructor sets up this model. At this time that only includes + * storing away the WordPress data object. + * + * @return object Class object + * + */ + public function __construct ($wpdb) + { + + // Save WordPress Database object + $this->wpdb = $wpdb; + } + + /* + * Perform Model Action + * + * This method does the work for this model and returns any resulting data + * + * @return array Status and data array + * + * 'status' + * + * True if successfull and false if there was a fatal failure. + * + * 'menuItemRedirect' + * + * If not false, provides a menu item the controller should + * execute after this one. Normally if this is used, there would also be a + * modelRedirect value supplied as well. + * + * 'modelRedirect' + * + * If not false, provides an action the controller should execute after + * this one. + * + * 'view' + * + * A suggested view name that the contoller should use instead of the + * default view for this model or false to indicate that the default view + * should be used. + * + * 'data' + * + * Data that the model is returning for use in merging with the view to + * produce output. + * + */ + public function modelAction () + { + + // Check that we have a prototype ID + $id = 0; + if (isset($_REQUEST['proto_id'])) { + // Make sure it's an integer + $id = $_REQUEST['proto_id'] - 0; + } + if (! $id) { + echo "FAILURE"; + exit(); + } + + // Get the current data for the selected prototype + // Get the prototype data + $res = $this->wpdb->get_row( + " + SELECT id, + DATE_FORMAT(post_date, '%Y-%m-%d') p_date, + post_title, + post_content + FROM wp_posts + WHERE post_type = 'glm_proto' + AND id = $id + ", ARRAY_A); + + // If we have results + $success = false; + if ($res['post_content']) { + + $success = true; + + // Break out the prototype file data + $content = unserialize($res['post_content']); + } + + // Setup input data array + $data = array( + 'glm_proto_title' => $content['title'], + 'glm_proto_title_error' => '', + 'glm_proto_width' => $content['width'], + 'glm_proto_width_error' => '', + 'glm_proto_height' => $content['height'], + 'glm_proto_height_error' => '', + 'glm_proto_background' => $content['background'], + 'glm_proto_background_error' => '', + 'glm_proto_prototype' => $content['prototype'], + 'glm_proto_prototype_error' => '' + ); + + /* + * If a protototype update is being submitted + */ + $submitError = false; + $submitted = false; + if (isset($_REQUEST['glm_proto_title'])) { + + // Clean up all input + $data['glm_proto_title'] = sanitize_text_field( + $_REQUEST['glm_proto_title']); + $data['glm_proto_width'] = sanitize_text_field( + $_REQUEST['glm_proto_width']); + $data['glm_proto_height'] = sanitize_text_field( + $_REQUEST['glm_proto_height']); + $data['glm_proto_background'] = sanitize_text_field( + $_REQUEST['glm_proto_background']); + $data['glm_proto_prototype'] = sanitize_text_field( + $_REQUEST['glm_proto_prototype']); + + // Check title field + if ($data['glm_proto_title'] == '') { + $data['glm_proto_title_error'] = 'Required title not supplied'; + $submitError = true; + } + + // Check width field + if ($data['glm_proto_width'] == '') { + $data['glm_proto_width_error'] = 'Required width not supplied'; + $submitError = true; + } + + // Check height field + if ($data['glm_proto_height'] == '') { + $data['glm_proto_height_error'] = 'Required height not supplied'; + $submitError = true; + } + + // Check background image - Not required but must exist if provided + if ($data['glm_proto_background'] != '' && ! $this->glmProtoIsUploaded( + $data['glm_proto_background'])) { + $data['glm_proto_background_error'] = 'Supplied background image does not exists'; + $submitError = true; + } + + // Check prototype image + if ($data['glm_proto_prototype'] == '') { + $data['glm_proto_prototype_error'] = 'Required prototype image not supplied '; + $submitError = true; + } else { + $exists = $this->glmProtoIsUploaded( + $data['glm_proto_prototype']); + if (! $exists) { + $data['glm_proto_prototype_error'] .= 'Supplied prototype image does not exists'; + $submitError = true; + } + } + + if (! $submitError) { + + // Prepair data for storage + $date = date('Y-m-d', time()); + $timezoneBackup = date_default_timezone_get(); + date_default_timezone_set("GMT"); + $gmtDate = date('Y-m-d', time()); + date_default_timezone_set($timezoneBackup); + + $content = serialize( + array( + 'title' => $data['glm_proto_title'], + 'width' => $data['glm_proto_width'], + 'height' => $data['glm_proto_height'], + 'background' => $data['glm_proto_background'], + 'prototype' => $data['glm_proto_prototype'] + )); + + // Store into wp_posts table + $result = $this->wpdb->update( + 'wp_posts', + array( + 'post_date' => $date, + 'post_date_gmt' => $gmtDate, + 'post_content' => $content, + 'post_title' => $data['glm_proto_title'], + 'post_type' => 'glm_proto' + ), + array( + 'id' => $id + )); + + // If there was a problem storing the prototype, pass that to + // the template + if (! $result) { + $data['glm_proto_title_error'] = 'There was an unknown problem updating this prototype.'; + } + + $prototypeSubmitted = true; + } + } + + // If submitted OK then display success page + $menuItemRedirect = false; + $modelRedirect = false; + if ($prototypeSubmitted) { + + $view = 'admin/prototypes/updated.html'; + + // Prototype was not successfully submitted so redisplay the form. + } else { + + // Select view + $view = 'admin/prototypes/edit.html'; + } + + // Return status, suggested view, and data to controller + return array( + 'status' => true, + 'menuItemRedirect' => false, + 'modelRedirect' => false, + 'view' => $view, + 'data' => $data + ); + } + + /* + * Check if a file supposedly uploaded to WordPress exists + * + * This method accepts a URL to a WordPress file or image, strips the + * WordPress base file URL to get the remaining path and file name for + * the file, then adds the base path for WordPress files and checks to + * see if the file exists. + * + * @param string URL of uploaded WordPress file or image + * + * @return bool True if it exists, false if not + */ + private function glmProtoIsUploaded ($url) + { + + // Get current WordPress upload directory/url information + $paths = wp_upload_dir(); + $base = $paths['baseurl']; + $path = $paths['basedir']; + + // Strip base directory from supplied URL + $file = substr($url, strlen($base)); + + // If there's now no file name + if (trim($file) == '') { + return false; + } + + // Check if file exists + $exists = file_exists($path . $file); + + return $exists; + } +} + +?> \ No newline at end of file diff --git a/models/admin/prototypes/index.php b/models/admin/prototypes/index.php new file mode 100644 index 0000000..8c8bc71 --- /dev/null +++ b/models/admin/prototypes/index.php @@ -0,0 +1,144 @@ + + * @license http://www.gaslightmedia.com Gaslightmedia + * @release admin.php,v 1.0 2014/10/31 19:31:47 cscott Exp $ + * @link http://dev.gaslightmedia.com/ + */ + +/* + * This class performs the work for the default action of the "Prototypes" menu + * option. + * + * The default action is to list existing prototypes. + */ +class glmProtoAdmin_prototypes_index +{ + + /** + * WordPress Database Object + * + * @var $wpdb + * @access public + */ + public $wpdb; + + /* + * Constructor + * + * This contructor sets up this model. At this time that only includes + * storing away the WordPress data object. + * + * @return object Class object + * + */ + public function __construct ($wpdb) + { + + // Save WordPress Database object + $this->wpdb = $wpdb; + } + + /* + * Perform Model Action + * + * This method does the work for this model and returns any resulting data + * + * @return array Status and data array + * + * 'status' + * + * True if successfull and false if there was a fatal failure. + * + * 'menuItemRedirect' + * + * If not false, provides a menu item the controller should + * execute after this one. Normally if this is used, there would also be a + * modelRedirect value supplied as well. + * + * 'modelRedirect' + * + * If not false, provides an action the controller should execute after + * this one. + * + * 'view' + * + * A suggested view name that the contoller should use instead of the + * default view for this model or false to indicate that the default view + * should be used. + * + * 'data' + * + * Data that the model is returning for use in merging with the view to + * produce output. + * + */ + public function modelAction () + { + // Get a current list of prototypes + $list = $this->getList(); + + // If we have list entries - even if it's an empty list + $success = false; + if ($list !== false) { + + $success = true; + + // If we have any prototypes listed + if (count($list) > 0) { + + // Expand content array in case we need it + while (list ($key, $value) = each($list)) { + $list[$key]['content'] = unserialize($value['post_content']); + } + } + } + + // Return status, suggested view, and data to controller + return array( + 'status' => $success, + 'menuItemRedirect' => false, + 'modelRedirect' => false, + 'view' => 'admin/prototypes/index.html', + 'data' => $list + ); + } + + /* + * Get List of Prototypes from Database + * + * @return array Array of prototypes data + */ + public function getList () + { + + // Get all prototypes + $res = $this->wpdb->get_results( + " + SELECT id, + DATE_FORMAT(post_date, '%Y-%m-%d') p_date, + post_title, + post_content + FROM wp_posts + WHERE post_type = 'glm_proto' + ORDER BY post_date, post_title + ", ARRAY_A); + + // If we have results - even an empty array + if (is_array($res)) { + return $res; + } + + return false; + } +} + +?> \ No newline at end of file diff --git a/models/deactivate.php b/models/deactivate.php index a5b29c6..7022c29 100644 --- a/models/deactivate.php +++ b/models/deactivate.php @@ -1,4 +1,5 @@ wpdb = $wpdb; - - // Perform any deactivation tasks here - - } + /** + * WordPress Database Object + * + * @var $wpdb + * @access public + */ + public $wpdb; + + /* + * Constructor + * + * Performs all the work for this model + */ + public function __construct ($wpdb) + { + + // Make sure the current user has this capability + if (! current_user_can('activate_plugins')) { + return; + } + + // Save WordPress Database object + $this->wpdb = $wpdb; + + // Perform any deactivation tasks here + } } ?> \ No newline at end of file diff --git a/models/uninstall.php b/models/uninstall.php index 1641802..b0337f0 100644 --- a/models/uninstall.php +++ b/models/uninstall.php @@ -1,4 +1,5 @@ wpdb = $wpdb; - - // Perform any uninstall tasks here - - } + /** + * WordPress Database Object + * + * @var $wpdb + * @access public + */ + public $wpdb; + + /* + * Constructor + * + * Performs all the work for this model + */ + public function __construct ($wpdb) + { + // Verify that uninstall is called from WordPress + if (! defined('WP_UNINSTALL_PLUGIN')) { + return; + } + + // Make sure the current user has this capability + if (! current_user_can('delete_plugins')) { + return; + } + + // Save WordPress Database object + $this->wpdb = $wpdb; + + // Perform any uninstall tasks here + } } ?> \ No newline at end of file diff --git a/views/admin/add/index.html b/views/admin/add/index.html new file mode 100644 index 0000000..f7270a4 --- /dev/null +++ b/views/admin/add/index.html @@ -0,0 +1,56 @@ +
+ +

Add a New Prototype

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + {if $data.glm_proto_title_error != ''}
{$data.glm_proto_title_error}{/if} +
+ + {if $data.glm_proto_width_error != ''}
{$data.glm_proto_width_error}{/if} +
+ + {if $data.glm_proto_height_error != ''}
{$data.glm_proto_height_error}{/if} +
+ + + {if $data.glm_proto_background_error != ''}
{$data.glm_proto_background_error}{/if} +
+ + + {if $data.glm_proto_prototype_error != ''}
{$data.glm_proto_prototype_error}{/if} +
 
+

* Required

+
+ +
+ + + diff --git a/views/admin/add/submitted.html b/views/admin/add/submitted.html new file mode 100644 index 0000000..14a0ca7 --- /dev/null +++ b/views/admin/add/submitted.html @@ -0,0 +1,16 @@ +
+ + {if $glm_proto_title_error != ''} + +

Error Adding Prototype

+

{$data.glm_proto_title_error} + + {else} + +

New Prototype Added

+ +

Prototype Title: {$data.glm_proto_title}

+ + {/if} + +
diff --git a/views/admin/error/badAction.html b/views/admin/error/badAction.html new file mode 100644 index 0000000..9d31e46 --- /dev/null +++ b/views/admin/error/badAction.html @@ -0,0 +1,8 @@ +
+ +

Sorry, we've had an error.

+ +

There's been some kind of error and we're not able to do what you requested.

+

Please try again. If you still get the same error, contact Gaslight Media at 231-487-0692 for assistance.

+ +
diff --git a/views/admin/prototype_add.html b/views/admin/prototype_add.html deleted file mode 100644 index a9e990b..0000000 --- a/views/admin/prototype_add.html +++ /dev/null @@ -1,56 +0,0 @@ -
- -

Add a New Prototype

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- - {if $glm_proto_title_error != ''}
{$glm_proto_title_error}{/if} -
- - {if $glm_proto_width_error != ''}
{$glm_proto_width_error}{/if} -
- - {if $glm_proto_height_error != ''}
{$glm_proto_height_error}{/if} -
- - - {if $glm_proto_background_error != ''}
{$glm_proto_background_error}{/if} -
- - - {if $glm_proto_prototype_error != ''}
{$glm_proto_prototype_error}{/if} -
 
-

* Required

-
- -
- - - diff --git a/views/admin/prototype_display.html b/views/admin/prototype_display.html deleted file mode 100644 index 273a109..0000000 --- a/views/admin/prototype_display.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Prototype: {$content.title} - - - -
-
- - diff --git a/views/admin/prototype_list.html b/views/admin/prototype_list.html deleted file mode 100644 index 60ed29e..0000000 --- a/views/admin/prototype_list.html +++ /dev/null @@ -1,43 +0,0 @@ - -
- -

List of Prototypes

- - - - - - - - - - -{if $haveList} - {foreach $prototypes as $p} - - - - - - {/foreach} -{else} - -{/if} - -
DatePrototype Name 
{$p.p_date}{$p.post_title} - View - Edit - Delete -
(no prototypes listed)
- -
- diff --git a/views/admin/prototype_submitted.html b/views/admin/prototype_submitted.html deleted file mode 100644 index 26add73..0000000 --- a/views/admin/prototype_submitted.html +++ /dev/null @@ -1,16 +0,0 @@ -
- - {if $glm_proto_title_error != ''} - -

Error Adding Prototype

-

{$glm_proto_title_error} - - {else} - -

New Prototype Added

- -

Prototype Title: {$glm_proto_title}

- - {/if} - -
diff --git a/views/admin/prototypes/display.html b/views/admin/prototypes/display.html new file mode 100644 index 0000000..8c27573 --- /dev/null +++ b/views/admin/prototypes/display.html @@ -0,0 +1,11 @@ + + + + Prototype: {$data.title} + + + +
+
+ + diff --git a/views/admin/prototypes/edit.html b/views/admin/prototypes/edit.html new file mode 100644 index 0000000..79739ed --- /dev/null +++ b/views/admin/prototypes/edit.html @@ -0,0 +1,56 @@ +
+ +

Edit a Prototype

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + {if $data.glm_proto_title_error != ''}
{$data.glm_proto_title_error}{/if} +
+ + {if $data.glm_proto_width_error != ''}
{$data.glm_proto_width_error}{/if} +
+ + {if $data.glm_proto_height_error != ''}
{$data.glm_proto_height_error}{/if} +
+ + + {if $data.glm_proto_background_error != ''}
{$data.glm_proto_background_error}{/if} +
+ + + {if $data.glm_proto_prototype_error != ''}
{$data.glm_proto_prototype_error}{/if} +
 
+

* Required

+
+ +
+ + + diff --git a/views/admin/prototypes/index.html b/views/admin/prototypes/index.html new file mode 100644 index 0000000..26d81dc --- /dev/null +++ b/views/admin/prototypes/index.html @@ -0,0 +1,43 @@ + +
+ +

List of Prototypes

+ + + + + + + + + + +{if $haveData} + {foreach $data as $d} + + + + + + {/foreach} +{else} + +{/if} + +
DatePrototype Name 
{$d.p_date}{$d.post_title} + View + Edit + Delete +
(no prototypes listed)
+ +
+ diff --git a/views/admin/prototypes/updated.html b/views/admin/prototypes/updated.html new file mode 100644 index 0000000..f08ffd4 --- /dev/null +++ b/views/admin/prototypes/updated.html @@ -0,0 +1,7 @@ +
+ +

Prototype Updated

+ +

Prototype Title: {$data.glm_proto_title}

+ +
-- 2.17.1