{"id":985,"date":"2014-06-12T22:45:07","date_gmt":"2014-06-13T03:45:07","guid":{"rendered":"https:\/\/justinparrtech.com\/JustinParr-Tech\/?p=985"},"modified":"2014-06-23T14:46:49","modified_gmt":"2014-06-23T19:46:49","slug":"wordpress-how-to-write-a-plugin-options-page-simplified","status":"publish","type":"post","link":"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/","title":{"rendered":"WordPress:  How to Write a Plugin Options Page SIMPLIFIED"},"content":{"rendered":"<p>While trying to write an options page and leverage persistent, admin-configurable settings for &#8220;<a title=\"Wordpress plugin:  JP User Registration Blacklist\" href=\"http:\/\/wordpress.org\/support\/plugin\/jp-user-registration-blacklist\" target=\"_blank\">JP User Registration Blacklist<\/a>&#8220;, I found a maze of conflicting standards and options.<\/p>\n<p>This is my attempt to provide a simple, straightforward, and comprehensive example, with source code that you can copy and implement with your own plugin.<\/p>\n<p><!--more--><\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_82_2 counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\"><p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<\/div><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#special-thanks\" >Special Thanks!<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#objective\" >Objective<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#overview-%e2%80%94-how-the-heck-does-this-thing-work\" >Overview &#8212; How the heck does this thing work?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#create-a-class\" >Create a Class<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#create-a-hook\" >Create a Hook<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#create-the-%e2%80%9cplugins%e2%80%9d-link\" >Create the &#8220;Plugins&#8221; Link<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#store-defaults-upon-activation\" >Store Defaults Upon Activation<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#tip-deactivate-plugin-before-editing\" >Tip:\u00a0 Deactivate Plugin Before Editing!<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#structure\" >Structure<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#modify-your-plugin\" >Modify your Plugin<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#include-settings-file\" >Include Settings File<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#read-global-options-array\" >Read Global Options Array<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#callback-hook-for-%e2%80%9csettings%e2%80%9d-link\" >Callback Hook for &#8220;Settings&#8221; Link<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#instantiate-the-admin-options-class\" >Instantiate the Admin Options Class<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#create-the-activation-event-hook\" >Create the Activation Event Hook<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#sample-plugin-file\" >Sample Plugin File<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#customize-the-settings-file\" >Customize the Settings File<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#customize-the-fields\" >Customize the Fields<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-19\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#customize-default-values\" >Customize Default Values<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-20\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#customize-the-input-validation\" >Customize the Input Validation<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-21\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#customize-the-settings-link\" >Customize the Settings Link<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-22\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#sample-settings-file\" >Sample Settings File<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-23\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#ok-so-what-now\" >OK, So What Now?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-24\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wordpress-how-to-write-a-plugin-options-page-simplified\/#conclusion\" >Conclusion<\/a><\/li><\/ul><\/nav><\/div>\n\n<p>&nbsp;<\/p>\n<h2><span class=\"ez-toc-section\" id=\"special-thanks\"><\/span>Special Thanks!<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>I pulled a great deal from Otto&#8217;s tutorial, so, THANKS, Otto!<\/p>\n<p><a title=\"Otto's WordPress Plugin Options Page Tutorial\" href=\"http:\/\/ottopress.com\/2009\/wordpress-settings-api-tutorial\/\" target=\"_blank\">http:\/\/ottopress.com\/2009\/wordpress-settings-api-tutorial\/<\/a><\/p>\n<p>&nbsp;<\/p>\n<h2><span class=\"ez-toc-section\" id=\"objective\"><\/span>Objective<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>I wrote a simple plugin, JP User Registration Blacklist, and there are a few static options, such as error messages, that I wanted to give the administrator the ability to change.<\/p>\n<p>I also wanted the ability to have and modify persistent settings, where previous versions had been based on hard-coded values.\u00a0 Without an admin options page, the only option available would be to make database updates &#8211; not very user-friendly!<\/p>\n<p>Some additional considerations:<\/p>\n<ul>\n<li>I wanted this to &#8216;look and feel&#8217; like other plugins<\/li>\n<li>I wanted a &#8216;settings&#8217; link, directly from the plugins page<\/li>\n<li>I wanted the plugin to automatically create and store a default set of persistent options (no configuration required)<\/li>\n<li>I wanted the ability to specify an extensible list of fields, and create generic, reusable code that I can use for other projects (or share for others)<\/li>\n<\/ul>\n<p>Obviously, there is always more than one way to accomplish the objective, and I&#8217;m relatively new to PHP \/ WordPress, so there may be room for improvement or optimization.\u00a0 I&#8217;m always open to suggestions and polite feedback!<\/p>\n<p>Also, I don&#8217;t want to present the impression that this is all copy and paste from other sources.\u00a0 I wrote a lot of my own code that I&#8217;m sharing, and I&#8217;ve put dozens of hours in to wrestling this thing in to submission.<\/p>\n<p>&nbsp;<\/p>\n<h2><span class=\"ez-toc-section\" id=\"overview-%e2%80%94-how-the-heck-does-this-thing-work\"><\/span>Overview &#8212; How the heck does this thing work?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>WordPress uses &#8220;hook&#8221; functions (callbacks) for everything.\u00a0 Building an admin options page consists of four main parts:<\/p>\n<ul>\n<li>Create a class that can be instantiated, that essentially encapsulates the admin options page, and all of its functions<\/li>\n<li>Create a hook.\u00a0 Some plugins seem to be able to directly call a config page by it&#8217;s URL (call the PHP file directly), while others hook in to random places within the WordPress menu structure.<\/li>\n<li>Once the hook is in place, the admin options page can be called via its slug.\u00a0 An additional hook creates the &#8220;Settings&#8221; link, that many plugins have on the Plugins page.<\/li>\n<li>Hook the &#8220;Activation&#8221; event, to create and store default options in the database, if they don&#8217;t already exist.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"create-a-class\"><\/span>Create a Class<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The class is a maze of callbacks.\u00a0 Everything is a callback.\u00a0 Rather than get in to the guts of what is calling what else, I&#8217;ve made this as simple as defining all the field properties in an array, and the class takes care of all the registration, formatting, and updating.<\/p>\n<p>Currently, default values are handled as discreet functions for each field.\u00a0 Again, there are multiple ways to do this, including &#8216;switch&#8217;, or include the default values as part of the field definition itself.\u00a0 If there is a better way to do it, from a performance or efficiency standpoint, I&#8217;m open to suggestions.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"create-a-hook\"><\/span>Create a Hook<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>I could not get the &#8220;direct PHP call&#8221; approach to work.\u00a0 Again, I may just not know the secret recipe, or may be overlooking something simple.<\/p>\n<p>I settled for creating a WordPress menu hook, which results in a &#8220;page slug&#8221; that can be referenced from anywhere.<\/p>\n<p>I&#8217;ve seen plugins that extend the &#8216;Settings&#8217; menu, or even add their own top-level menu.\u00a0 I&#8217;m a purist, and I&#8217;d prefer not to screw up the WordPress menu interface with a bunch of random junk (even my own random junk), so I opted to extend the &#8216;Plugins&#8217; menu, which logically makes sense, anyway.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"create-the-%e2%80%9cplugins%e2%80%9d-link\"><\/span>Create the &#8220;Plugins&#8221; Link<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>By default, you get nothing.\u00a0 Many plugins add a &#8220;Settings&#8221; link, so that you can conveniently activate, and then configure your plugin, without having to hunt around for where someone hid the options menu.<\/p>\n<p>Additionally, some plugins have &#8220;Settings&#8221; on the left (as the first option), and others add it to the end, on the right.<\/p>\n<p>I opted for the right, but I left the code remarked out, to add it to the left.<\/p>\n<p>Registering the menu hook allows you to specify a slug for the admin options page itself, which you can then point to from the link.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"store-defaults-upon-activation\"><\/span>Store Defaults Upon Activation<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Unless your plugin executes as a persistent object, it won&#8217;t have the ability to create and store options, unless the admin configures them in advance.<\/p>\n<p>Hooking the &#8220;Activation&#8221; event allows your plugin to store a basic set of default options in the database, without instantiating anything, or having to create a special class.<\/p>\n<p>Your defaults should allow the plugin to work immediately, without admin-intervention.\u00a0 Remember that this is a live site, and users may be hitting it as you are messing around with the plugin.<\/p>\n<p>Persistent options can be stored as individual entries, or you can create an array and store the entire array as one entry in the database.\u00a0 I opted for the latter, which is cleaner and easier to clean up, but can be a bit harder to tweak or debug.\u00a0 Doing it this way allows the plugin to be reset, worst case, by deleting one row from the database.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"tip-deactivate-plugin-before-editing\"><\/span>Tip:\u00a0 Deactivate Plugin Before Editing!<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>I ran in to a couple of situations where, through syntax error, or simply not knowing what I was doing, I couldn&#8217;t save, couldn&#8217;t go back to the plugins page, and couldn&#8217;t access the dashboard.<\/p>\n<p>I ended up having to connect to the host and edit the file directly in order to recover.<\/p>\n<p>To save yourself the time and hassle, just be sure to disable the plugin each time, before you edit one of the plugin files.<\/p>\n<p>&nbsp;<\/p>\n<h2><span class=\"ez-toc-section\" id=\"structure\"><\/span>Structure<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>I attempted to put most of the code for the admin options in a separate file, which will provide some level of isolation for future updates, simplifies troubleshooting, and keeps the main file as clean as possible.<\/p>\n<p>For this example, let&#8217;s assume your plugin is called &#8220;My Cool Plugin&#8221;:<\/p>\n<ul>\n<li>Name:\u00a0 &#8220;My Cool Plugin&#8221; &#8211; displayed on the Plugins page<\/li>\n<li>Path: &#8220;wp-content\/plugins\/mycoolplugin&#8221;<\/li>\n<li>Main file:\u00a0 &#8220;mycoolplugin.php&#8221;<\/li>\n<li>Short name:\u00a0 &#8220;MCP&#8221; (My Cool Plugin), will be used to ensure that all variables and functions are globally-unique.<\/li>\n<li>Admin page slug:\u00a0 &#8220;MCP-settings&#8221; is the &#8220;slug&#8221; (shortcut URL) for the new Admin options page<\/li>\n<\/ul>\n<p>Let&#8217;s assume your admin options code is then stored in:<\/p>\n<ul>\n<li>Settings file:\u00a0 &#8220;mycoolplugin_settings.php&#8221; (same path)<\/li>\n<\/ul>\n<p>The rest of this article is a walkthrough for modifying your plugin (with code samples included).<\/p>\n<p>At the end, a recap is provided, with actual complete skeleton files that you can copy and customize for your next plugin project.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h2><span class=\"ez-toc-section\" id=\"modify-your-plugin\"><\/span>Modify your Plugin<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>A few simple things need to be added to the main (&#8220;mycoolplugin.php&#8221;) file:<\/p>\n<ul>\n<li>At the top of the file:\n<ul>\n<li>Include the settings file (one-liner , using the PHP require_once function)<\/li>\n<li>Read the global options array from the database (another one-liner, using the WordPress get_option function)<\/li>\n<\/ul>\n<\/li>\n<li>At the bottom of the file:\n<ul>\n<li>Add the callback hook for the settings link (WordPress plugin_action_links_$plugin &#8212; this syntax is fairly standard, and calls your specified function when the &#8220;Plugin Action Links&#8221; are being generated)<\/li>\n<li>Instantiate the admin options page (instantiate the class)<\/li>\n<li>Create the &#8220;Activation&#8221; event hook, to store default options in the database<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"include-settings-file\"><\/span>Include Settings File<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>This is a simple one-liner, that should be the first actual code line in your file, after the Plugin header.<\/p>\n<pre style=\"padding-left: 30px;\">require_once basename( __FILE__,'.php').'_settings.php';<\/pre>\n<p>Explanation:<\/p>\n<ul>\n<li><strong>require_once<\/strong> &#8211; This is a PHP function that includes another file, but ONLY loads it, if it&#8217;s NOT already loaded.\u00a0 From a code standpoint, the code in the second file appears virtually inline, from the point where &#8220;require_once&#8221; appears in the main file &#8212; which is why it needs to appear at the top of the file.\u00a0 Functions and the admin class that are referenced in the main file have to be declared before they are used, and thus they have to appear before they are called.\u00a0 If we put require_once at the end of the file, we&#8217;d get a bunch of errors, because the references would not have been declared yet, relative to the point where they&#8217;re called in the main file.<\/li>\n<li><strong>basename()<\/strong> &#8211; PHP function that returns just the filename portion of a \/path\/filename.ext, and can additionally trim off some portion of the end of the file.\u00a0 In this case, we&#8217;re trimming off the &#8220;.php&#8221; extension.<\/li>\n<li><strong>__FILE__<\/strong> &#8211; PHP macro that expands to the path and filename.\n<ul>\n<li>For our example, __FILE__ equates to \/wp-content\/plugins\/mycoolplugin\/mycoolplugin.php<\/li>\n<li>basename() by itself then returns &#8220;mycoolplugin.php&#8221;, but we&#8217;ve added ,&#8221;.php&#8221; as the second option, telling basename to trim &#8220;.php&#8221; from the end.<\/li>\n<li>All of this returns &#8220;mycoolplugin&#8221;<\/li>\n<\/ul>\n<\/li>\n<li><strong>Dot (&#8220;.&#8221;)<\/strong> concatenates strings in PHP.\u00a0 Here, we use &#8220;dot&#8221; to add &#8220;_settings.php&#8221; to the end, resulting in &#8220;mycoolplugin_settings.php&#8221;, which is the file name included by require_once.<\/li>\n<\/ul>\n<p>This executes everything in &#8220;mycoolplugin_settings.php&#8221;, before anything else executes.\u00a0 This ensures functions and our admin class are declared early, and available when they are called elsewhere.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"read-global-options-array\"><\/span>Read Global Options Array<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<pre style=\"padding-left: 30px;\">$MCP_opts=get_option( MCP_option_name(), MCP_default_all() );<\/pre>\n<p>Explanation:<\/p>\n<ul>\n<li><strong>$MCP_opts<\/strong> &#8211; This variable will hold the array of options<\/li>\n<li><strong>get_option()<\/strong> &#8211; This is a WordPress function, that reads an option (setting) from the database.<\/li>\n<li><strong>MCP_option_name()<\/strong> &#8211; This is a function that we will build later, in the mycoolplugin_settings.php file.\u00a0 Its job is to return the name of the option to read from the database.<\/li>\n<li><strong>MCP_default_all()<\/strong> &#8211; This is a function that we will build later, in the mycoolplugin_settings.php file.\u00a0 Its job is to return an array containing the default values for all fields.<\/li>\n<\/ul>\n<p>This results in an array, $MCP_opts, that has fieldname =&gt; value pairs for each setting.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"callback-hook-for-%e2%80%9csettings%e2%80%9d-link\"><\/span>Callback Hook for &#8220;Settings&#8221; Link<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<pre>$plugin = plugin_basename(__FILE__); \r\nif ( is_admin() )\r\n\u00a0\u00a0 \u00a0add_filter(\"plugin_action_links_$plugin\", 'MCP_settingslink' );<\/pre>\n<p>Explanation:<\/p>\n<ul>\n<li><strong>$plugin<\/strong> &#8211; Stores a string containing the name of the action that will be triggered<\/li>\n<li><strong>plugin_basename()<\/strong> &#8211; WordPress function that returns ONLY the plugin folder and file name.\u00a0 For our example, this is mycoolplugin\/mycoolplugin.php<\/li>\n<li><strong>is_admin()<\/strong> &#8211; WordPress function, that returns true if the user has administrative rights.<\/li>\n<li><strong>if()<\/strong> &#8211; PHP function.\u00a0 If the &#8220;if&#8221; criteria is true, PHP executes the next line of code (add_filter).\n<ul>\n<li>The purpose of <strong>if( is_admin() )<\/strong> is to ONLY display the &#8220;Settings&#8221; link for administrative users.\u00a0 Normal users will see the &#8220;Deactivate&#8221; and &#8220;Edit&#8221; links, assuming they have the proper permissions.<\/li>\n<\/ul>\n<\/li>\n<li><strong>add_filter()<\/strong> &#8211; WordPress generic event hook.\u00a0 A filter is code that is added to the end of existing code, triggered for a particular action.\u00a0 In this case, when the &#8220;Plugin Action Links&#8221; are being generated, we want to attach a function callback, &#8220;MCP_settingslink&#8221; to the end of the existing process.\u00a0 When the &#8220;normal&#8221; process completes, it will now additionally execute the code inside the MCP_settingslink function.<\/li>\n<li><strong>MCP_settingslink()<\/strong> &#8211; This is a function that we will build later, in mycoolplugin_settings.php.\n<ul>\n<li>Using macros, such as <strong>__FILE__<\/strong>, to build the event handler means that the code is portable.\u00a0 To use this in another plugin, only the callback function name (MCP_settingslink) needs to be modified.<\/li>\n<li>The structure of the link is:\n<pre>http:\/\/whateversite.com\/wp-admin\/plugins.php?page=MCP-settings<\/pre>\n<\/li>\n<li>MCP-settings is the Admin options page slug, created when we hook the menu (later)<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>If the current user is an administrator, they will see a &#8220;Settings&#8221; link on the Plugins page, beneath the plugin.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"instantiate-the-admin-options-class\"><\/span>Instantiate the Admin Options Class<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<pre>if( is_admin() )\r\n\u00a0\u00a0 \u00a0$my_settings_page = new MyCoolSettingsPage();<\/pre>\n<p>Explanation:<\/p>\n<ul>\n<li><strong>if( is_admin() )<\/strong> &#8211; As with the &#8220;Settings&#8221; link, the admin page will ONLY be built and instantiated for an administrative user, including the appearance of the Plugin manu hook.<\/li>\n<li><strong>$my_settings_page<\/strong> &#8211; Variable holds the instance of the Admin Options class<\/li>\n<li><strong>MyCoolSettingsPage()<\/strong> &#8211; class defined later in mycoolplugin_settings.php\n<ul>\n<li><strong>new<\/strong> &#8211; keyword that specifies a new instance of the class.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Once instantiated, the &#8220;Plugins&#8221; menu hook will be in place.\u00a0 Administrators will be able to navigate to &#8220;Plugins..My Cool Plugin Settings&#8221;.\u00a0 The actual menu hooks are located in the code for the class &#8212; we will address that later.\u00a0 The manu hook also allows the code to specify a slug for the admin page:\u00a0 &#8220;MCP-settings&#8221; (this is covered later).<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"create-the-activation-event-hook\"><\/span>Create the Activation Event Hook<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<pre style=\"padding-left: 30px;\">function MCP_activate() {\r\n\u00a0\u00a0 \u00a0$MCP_opts=get_option(MCP_option_name(), MCP_default_all() );\r\n\r\n\u00a0\u00a0 \u00a0update_option(MCP_option_name(),$MCP_opts);\r\n}\r\nregister_activation_hook( __FILE__, 'MCP_activate' )<\/pre>\n<p>Explanation:<\/p>\n<ul>\n<li><strong>function<\/strong> &#8211; declare a PHP function, that we call later.\u00a0 In this case, this will be our callback function that gets executed when the administrator clicks &#8220;Activate&#8221; to activate the plugin (or any time the plugin is activated)<\/li>\n<li><strong>MCP_activate()<\/strong> &#8211; our function name, referenced by the activation hook<\/li>\n<li><strong>$MCP_opts<\/strong> &#8211; this line is identical to the second line we added, whose purpose is to read the settings out of the database, optionally supplying proper default values.\u00a0 This ensures that $MCP_opts contains an array of valid settings &#8212; either we read them from the database because they already exist, or we created a set of valid defaults.<\/li>\n<li><strong>update_option()<\/strong> &#8211; A WordPress function to update an existing option, or create it if it doesn&#8217;t exist.\u00a0 The entire array is stored as one row in the database, regardless of the number of fields in the array.<\/li>\n<li><strong>register_activation_hook()<\/strong> &#8211; &#8220;hooks&#8221; (chains) additional code, to execute when the plugin is activated.\u00a0 Note the similar syntax to the Plugin Action Links hook, and also the MCP_activate function name that we declared previously.<\/li>\n<\/ul>\n<p>The purpose of this section is to ensure that one good, clean copy of the options gets stored in the database, allowing the plugin to start work immediately, without requiring admin configuration.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"sample-plugin-file\"><\/span>Sample Plugin File<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>wp-content\/plugins\/mycoolplugin\/mycoolplugin.php<\/p>\n<p>View or Download the file here:\u00a0 <a title=\"MyCoolPlugin Sample File\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wp-content\/uploads\/mycoolplugin.txt\" target=\"_blank\">mycoolplugin.txt<\/a><\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h2><span class=\"ez-toc-section\" id=\"customize-the-settings-file\"><\/span>Customize the Settings File<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The settings file in this example is <strong>mycoolplugin_settings.php<\/strong><\/p>\n<p>Keeping the Admin Options Page class and supporting declarations in a separate file makes the code more modular, and reduces clutter in the main plugin file.<\/p>\n<p>The following must be customized in the settings file:<\/p>\n<ul>\n<li>Customize the fields (field name, label, help text, and input type)<\/li>\n<li>Customize the default value functions<\/li>\n<li>Customize the page (friendly) title function<\/li>\n<li>Customize the input validation<\/li>\n<li>Customize the &#8220;Settings&#8221; link<\/li>\n<\/ul>\n<p>The rest of the file should be OK, left as default.\u00a0 Don&#8217;t worry about the inner workings &#8212; just take it as a &#8220;black box&#8221; for now, and if you want to delve in to it further, check out Otto&#8217;s excellent tutorial (see above).<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"customize-the-fields\"><\/span>Customize the Fields<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>I set this up so that the field definitions are array-driven<\/p>\n<p>This is probably the most complicated step.<\/p>\n<p>There is a function called MCP_field_list() that returns an array containing all field names and parameters.<\/p>\n<p>The list of fields is stored in an array, each element of which is another array containing the form label text, help text, and the type of input.<\/p>\n<p>Let&#8217;s assume we want the following three options on the admin options page (we&#8217;ll review the purpose of &#8220;label&#8221;, &#8220;help&#8221;, and &#8220;type&#8221; in a minute):<\/p>\n<ul>\n<li><strong>age<\/strong>\n<ul>\n<li>label = Your Age<\/li>\n<li>help = Type in your age, in years<\/li>\n<li>type = text<\/li>\n<\/ul>\n<\/li>\n<li><strong>bio<\/strong>\n<ul>\n<li>label = Short Biography<\/li>\n<li>help = Type in a little about yourself<\/li>\n<li>type = textarea<\/li>\n<\/ul>\n<\/li>\n<li><strong>title<\/strong>\n<ul>\n<li>label = Job Title<\/li>\n<li>help = Type in your job title<\/li>\n<li>type = text<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>We need to construct a function that produces a 2D array structure:<\/p>\n<pre style=\"padding-left: 30px;\">$fields = MCP_field_list();\r\n\r\n--- RESULT ---\r\n$fields['age'] =&gt; 'label' =&gt; 'Your Age'\r\n                  'help'  =&gt; 'Type in your age, in years'\r\n                  'type'  =&gt; 'text'\r\n\r\n$fields['age']['type'] == 'text'\r\n$fields['bio']['label'] == 'Short Biography'\r\n\r\nEtc...<\/pre>\n<p>Here is the function that produces the structure above:<\/p>\n<pre style=\"padding-left: 30px;\">function MCP_field_list()\r\n{\r\n\u00a0\u00a0 \u00a0\/\/ *** Field name and label ***\r\n\u00a0\u00a0 \u00a0$fields = <span style=\"color: #00ffff;\">array(<\/span>\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0<span style=\"color: #ffff00;\">'age'<\/span>\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 =&gt; <span style=\"color: #00ff00;\">array(<\/span>\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'label' \u00a0\u00a0 \u00a0=&gt; 'Your Age'<span style=\"color: #ff6600;\">,<\/span>\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'help'\u00a0\u00a0 \u00a0\u00a0 =&gt; 'Type in your age, in years'<span style=\"color: #ff6600;\">,<\/span>\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'type'\u00a0\u00a0 \u00a0\u00a0 =&gt; 'text'\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0)<span style=\"color: #ff6600;\">,<\/span>\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0<span style=\"color: #ffff00;\">'bio'<\/span>\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 =&gt; <span style=\"color: #00ff00;\">array(<\/span>\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'label'\u00a0\u00a0 \u00a0 =&gt; 'Short Biography'<span style=\"color: #ff6600;\">,<\/span>\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'help'\u00a0\u00a0 \u00a0\u00a0 =&gt; 'Type in a little about yourself'<span style=\"color: #ff6600;\">,<\/span>\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'type'\u00a0\u00a0 \u00a0\u00a0 =&gt; 'textarea'\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0)<span style=\"color: #ff6600;\">,<\/span>\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0<span style=\"color: #ffff00;\">'title'<\/span>\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0 =&gt; <span style=\"color: #00ff00;\">array(<\/span>\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'label'\u00a0\u00a0 \u00a0 =&gt; 'Job Title'<span style=\"color: #ff6600;\">,<\/span>\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'help'\u00a0\u00a0 \u00a0\u00a0 =&gt; 'Type in your job title'<span style=\"color: #ff6600;\">,<\/span>\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'type'\u00a0\u00a0 \u00a0\u00a0 =&gt; 'text'\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0)\r\n\u00a0\u00a0 \u00a0)<span style=\"color: #ff0000;\">;<\/span>\r\n\u00a0\u00a0 \u00a0\r\n\u00a0\u00a0 \u00a0return $fields;\r\n}<\/pre>\n<p>You are basically building an array of arrays (a list of lists).\u00a0 Watch the punctuation!\u00a0 A comma (orange) separates each array element, but also separates each array from the next field.\u00a0 Don&#8217;t use a trailing comma after the last item, or you&#8217;ll get a syntax error.\u00a0 If you get frustrated, or the above is too complex, try this simplified syntax:<\/p>\n<pre style=\"padding-left: 30px;\">function MCP_field_list()\r\n{\r\n\u00a0\u00a0 \u00a0$agefields = <span style=\"color: #00ff00;\">array(\r\n<\/span>    \u00a0\u00a0 \u00a0'label' \u00a0\u00a0 \u00a0=&gt; 'Your Age'<span style=\"color: #ff6600;\">,<\/span> \r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'help'\u00a0\u00a0 \u00a0\u00a0 =&gt; 'Type in your age, in years'<span style=\"color: #ff6600;\">,<\/span> \r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'type'\u00a0\u00a0 \u00a0\u00a0 =&gt; 'text'\u00a0 \u00a0\u00a0\r\n \u00a0 \u00a0)<span style=\"color: #ff0000;\">;<\/span>\r\n\r\n    $biofields = <span style=\"color: #00ff00;\">array(<\/span> \r\n \u00a0 \u00a0\u00a0\u00a0 \u00a0'label'\u00a0\u00a0 \u00a0 =&gt; 'Short Biography'<span style=\"color: #ff6600;\">,<\/span> \r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'help'\u00a0\u00a0 \u00a0\u00a0 =&gt; 'Type in a little about yourself'<span style=\"color: #ff6600;\">,<\/span> \u00a0\u00a0 \u00a0\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'type'\u00a0\u00a0 \u00a0\u00a0 =&gt; 'textarea' \u00a0\r\n\u00a0\u00a0  )<span style=\"color: #ff0000;\">;<\/span>\r\n\r\n    $titlefields = <span style=\"color: #00ff00;\">array(<\/span> \r\n    \u00a0\u00a0 \u00a0'label'\u00a0\u00a0 \u00a0 =&gt; 'Job Title'<span style=\"color: #ff6600;\">,<\/span> \u00a0\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'help'\u00a0\u00a0 \u00a0\u00a0 =&gt; 'Type in your job title'<span style=\"color: #ff6600;\">,<\/span> \u00a0\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'type'\u00a0\u00a0 \u00a0\u00a0 =&gt; 'text' \u00a0\r\n\u00a0\u00a0 \u00a0)<span style=\"color: #ff0000;\">;<\/span> \u00a0\r\n    \r\n    <span style=\"color: #00ffff;\">$fields = array(<\/span>\r\n        <span style=\"color: #ffff00;\">'age'<\/span>       =&gt; $agefields<span style=\"color: #ff6600;\">,<\/span>\r\n        <span style=\"color: #ffff00;\">'bio'<\/span>       =&gt; $biofields<span style=\"color: #ff6600;\">,<\/span>\r\n        <span style=\"color: #ffff00;\">'title'<\/span>     =&gt; $titlefields\r\n    )<span style=\"color: #ff0000;\">;<\/span>\r\n\r\n    return <span style=\"color: #00ffff;\">$fields<\/span>; \r\n}<\/pre>\n<p>In either case, to add a field, just copy and paste one section at a time.<\/p>\n<p>Explanation:<\/p>\n<ul>\n<li>Field Name (yellow) &#8211; This is the key name that&#8217;s stored in the database<\/li>\n<li>Label &#8211; This value is displayed to the left of the input area, prompting the user for what type of information is expected<\/li>\n<li>Help &#8211; Help text appears below the input area, providing additional information to the user<\/li>\n<li>Type &#8211; &#8220;text&#8221; makes a standard single-line text input field.\u00a0 &#8220;textarea&#8221; makes a multiline input box.<\/li>\n<\/ul>\n<p>Save and test.\u00a0 Once you have the fields displayed properly, move on to the next section.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"customize-default-values\"><\/span>Customize Default Values<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>There are multiple ways to accomplish creating an array of default values, including simply having a single array() constructor.<\/p>\n<p>I chose to use individual functions for each default value, which provides maximum flexibility.\u00a0 For example, let&#8217;s say you build a default value function that produces a specific pattern or type of data &#8212; most simply, let&#8217;s say you need a random number between 1 and 10:<\/p>\n<pre style=\"padding-left: 30px;\">function default_random() {\r\n    return mt_rand(1,10);\r\n}<\/pre>\n<p>If you have multiple fields that all require the same TYPE of default value, but not necessarily the SAME value, you can invoke the one function multiple times.<\/p>\n<p>If you have a better \/ preferred method, please feel free to do it differently.<\/p>\n<p>The way I have this organized, there is a default value function for each field, and then a default_all function that builds an array from the individual default return values.<\/p>\n<pre style=\"padding-left: 30px;\">function <span style=\"color: #00ff00;\">MCP_default_age()<\/span>\r\n{\r\n\u00a0\u00a0 \u00a0return(mt_rand(10,50));\r\n}\r\n\r\nfunction <span style=\"color: #00ff00;\">MCP_default_bio()<\/span>\r\n{\r\n\u00a0\u00a0 \u00a0return \"I grew up in Awesometown, USA, lived an awesome life, and my interests include being awesome\";\r\n}\r\n\r\nfunction <span style=\"color: #00ff00;\">MCP_default_title()<\/span>\r\n{\r\n\u00a0\u00a0 \u00a0return \"Sr. Awesome Guy\";\r\n}\r\n\r\nfunction <span style=\"color: #00ffff;\">MCP_default_all()<\/span>\r\n{\r\n\u00a0\u00a0 \u00a0$opt=array(\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'age'\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0=&gt; MCP_default_age(),\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'bio'\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0=&gt; MCP_default_bio(),\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0'title'\u00a0\u00a0\u00a0\u00a0 \u00a0=&gt; MCP_default_title()\r\n\u00a0\u00a0 \u00a0);\r\n\r\n\u00a0\u00a0 \u00a0return $opt;\r\n}<\/pre>\n<p>Tasks:<\/p>\n<ul>\n<li>Build a default value function for each field<\/li>\n<li>Add all fields to the array in the MCP_default_all() function<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p>Customize the Page (Friendly) Title Function<\/p>\n<p>The return value from this function appears in the title bar, the page header, the menu option name, and several other places.\u00a0 This should be the &#8220;friendly&#8221; name, that you want the users to see.<\/p>\n<p>For our example above, this should be &#8220;My Cool Plugin&#8221;<\/p>\n<pre style=\"padding-left: 30px;\">function MCP_page_title()\r\n{\r\n\u00a0\u00a0 \u00a0return '<span style=\"color: #00ff00;\">My Cool Plugin<\/span>';\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"customize-the-input-validation\"><\/span>Customize the Input Validation<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>For most purposes, the standard validation process will be sufficient.\u00a0 The standard validation process ensures that each field is defined, and is not blank.\u00a0 If either of these is false, it assigns the default value (from the default_all function)<\/p>\n<p>In this example, we want to make sure &#8216;age&#8217; is non-zero, so we&#8217;ve added the following validation code:<\/p>\n<pre>\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0 \/\/ *** AGE, NUMERIC ***\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0$k='age'\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\/\/ *\u00a0\u00a0 Convert to numeric\u00a0\u00a0 *\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0$new_input[$k] = absint( $new_input[$k] );\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\/\/ *\u00a0\u00a0 Correct zero value\u00a0\u00a0 *\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if ( $new_input[$k]==0 )\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0$new_input[$k]=$defaults[$k];<\/pre>\n<p>Explanation:<\/p>\n<ul>\n<li><strong>$k=&#8217;age&#8217;<\/strong> &#8211; For simplicity, use a variable for the field name.\u00a0 This keeps the code modular, and avoids copy-and-paste errors, if you copy your validation code for other fields.<\/li>\n<li><strong>$new_input<\/strong> &#8211; this array holds the &#8220;sanitized&#8221; input values<\/li>\n<li><strong>absint()<\/strong> &#8211; PHP function to convert a string to an integer.\u00a0 If the string is a number, the resulting integer will be that number.\u00a0 If the string is a string, the resulting integer will be 0.<\/li>\n<li><strong>if()<\/strong> &#8211; Checks for a 0 value, and assigns the default if so.\u00a0 The default_age function produces a random number between 10 and 60 (see above), so the result will be that Age is assigned a new random number between 10 and 60.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"customize-the-settings-link\"><\/span>Customize the Settings Link<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>By default, the only links you get on the Plugins page underneath your plugin are &#8220;Deactivate&#8221; and &#8220;Edit&#8221;.\u00a0 This function, called by our hook in the main file, adds a &#8220;Settings&#8221; link underneath your plugin.<\/p>\n<p>The Settings link should redirect to the slug for your admin options page:<\/p>\n<pre style=\"padding-left: 30px;\">function <span style=\"color: #00ffff;\">MCP_settingslink<\/span>($links) { \r\n\u00a0\u00a0 \u00a0$settings_link = '&lt;a href=\"plugins.php?page='.<span style=\"color: #00ff00;\">MCP_page_name()<\/span>.'\"&gt;Settings&lt;\/a&gt;';\r\n\u00a0\u00a0 \u00a0\r\n\u00a0\u00a0 \u00a0\/\/array_unshift($links, $settings_link); \r\n\u00a0\u00a0 \u00a0$links[]=$settings_link;\r\n\u00a0\u00a0 \u00a0\r\n\u00a0\u00a0 \u00a0return $links; \r\n}<\/pre>\n<p>This function is the callback when we hook the Plugin Action Link event.\u00a0 Since we are using a function, MCP_page_name() to generate the link, this code is very portable.\u00a0 We don&#8217;t actually have to change anything, but the Settings link can be customized as you desire.<\/p>\n<p>There is no real standard, but most plugins that I&#8217;ve seen add the &#8220;Settings&#8221; link to the right of the other links.\u00a0 However, if you uncomment the &#8220;array_unshift&#8221; line, and comment the &#8220;$links[]&#8221; line, then your &#8220;Settings&#8221; link will appear on the left &#8212; unshift prepends a value at the beginning of the array.<\/p>\n<p>If you have multiple option pages, you can add more links, or links to external resources.<\/p>\n<p>You can also change the format of the Settings link &#8211; for example, you could change the color using standard HTML.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"sample-settings-file\"><\/span>Sample Settings File<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>wp-content\/plugins\/mycoolplugin\/mycoolplugin_settings.php<\/p>\n<p>View or Download the file here:\u00a0 <a title=\"MyCoolPlugin Sample Settings File\" href=\"https:\/\/justinparrtech.com\/JustinParr-Tech\/wp-content\/uploads\/mycoolplugin_settings.txt\" target=\"_blank\">mycoolplugin_settings<\/a><\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h2><span class=\"ez-toc-section\" id=\"ok-so-what-now\"><\/span>OK, So What Now?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Now, you have the ability to create persistent option values that you can use to build your own awesome plugins, and an admin options page, through which administrators can edit them.<\/p>\n<p>Assuming our samples above, here is a simple shortcode function that you could drop directly in to your main plugin file:<\/p>\n<pre style=\"padding-left: 30px;\">function bio_func( $atts ){\r\n    global $MCP_opts;\r\n\r\n    $bio = 'Age: '.$MCP_opts['age'].', '.$MCP_opts['title'];\r\n    $bio = $bio.'&lt;BR&gt;'.$MCP_opts['bio'].'&lt;BR&gt;';\r\n\r\n\u00a0\u00a0 \u00a0return $bio;\r\n}\r\nadd_shortcode( 'bio', 'bio_func' );<\/pre>\n<p>Explanation:<\/p>\n<ul>\n<li>global $MCP_opts &#8211; pulls in the global variable, MCP_opts, that we used at the beginning of our plugin file to store options read from the database<\/li>\n<li>add_shortcode &#8211; WordPress function to create shortcodes.\u00a0 Adding [bio] to your post will now print out a short biography<\/li>\n<\/ul>\n<p>Sample:<\/p>\n<blockquote><p><em>Age: 99, Zoo Keeper<\/em><br \/>\n<em>I grew up near zoos, and I love being in zoos, so I became a zookeeper.\u00a0 I&#8217;ve been a zookeeper at 5 different zoos over the past 20 years.<\/em><\/p><\/blockquote>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h2><span class=\"ez-toc-section\" id=\"conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>I pulled together bits and pieces from several sources, and wrote about 150 lines of my own code, to produce an extremely simple, plug-and-play, admin options page that you can use for your plugin.<\/p>\n<p>If you find this useful, or have a better way of doing something I&#8217;ve described above, please feel free to leave me a comment!<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>While trying to write an options page and leverage persistent, admin-configurable settings for &#8220;JP User Registration Blacklist&#8220;, I found a maze of conflicting standards and options. This is my attempt to provide a simple, straightforward, and comprehensive example, with source code that you can copy and implement with your own plugin.<\/p>\n","protected":false},"author":16,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[14],"tags":[],"class_list":["post-985","post","type-post","status-publish","format-standard","hentry","category-wordpress-stuff"],"_links":{"self":[{"href":"https:\/\/justinparrtech.com\/JustinParr-Tech\/wp-json\/wp\/v2\/posts\/985","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/justinparrtech.com\/JustinParr-Tech\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/justinparrtech.com\/JustinParr-Tech\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/justinparrtech.com\/JustinParr-Tech\/wp-json\/wp\/v2\/users\/16"}],"replies":[{"embeddable":true,"href":"https:\/\/justinparrtech.com\/JustinParr-Tech\/wp-json\/wp\/v2\/comments?post=985"}],"version-history":[{"count":10,"href":"https:\/\/justinparrtech.com\/JustinParr-Tech\/wp-json\/wp\/v2\/posts\/985\/revisions"}],"predecessor-version":[{"id":1055,"href":"https:\/\/justinparrtech.com\/JustinParr-Tech\/wp-json\/wp\/v2\/posts\/985\/revisions\/1055"}],"wp:attachment":[{"href":"https:\/\/justinparrtech.com\/JustinParr-Tech\/wp-json\/wp\/v2\/media?parent=985"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/justinparrtech.com\/JustinParr-Tech\/wp-json\/wp\/v2\/categories?post=985"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/justinparrtech.com\/JustinParr-Tech\/wp-json\/wp\/v2\/tags?post=985"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}