├── .gitignore ├── README.md ├── assets ├── javascripts │ └── admin.js └── stylesheets │ └── admin.css └── quilt.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quilt 2 | 3 | Yet another WordPress Settings API wrapper. [Why](http://zanematthew.com/a-wordpress-settings-api-wrapper/)? 4 | 5 | # Description 6 | 7 | This library provides a simplified way to interact with the [overly verbose](http://codex.wordpress.org/images/7/7e/editing-settings-api-example.png) WordPress [settings API](http://codex.wordpress.org/Settings_API) via an array. 8 | 9 | This will handle; 10 | 11 | 1. Form submission. 12 | 1. Admin notices. 13 | 1. Display settings as a single page or tab interface. 14 | 1. Sanitizing. 15 | 1. Tabbed based settings, or single page based settings. 16 | 17 | 18 | # Supported Fields 19 | 20 | Quilt allows you to easily create the following WordPress admin settings field types. 21 | 22 | * button 23 | * canadaStateSelect 24 | * checkbox 25 | * checkboxes 26 | * email 27 | * fancyText 28 | * fancySelect 29 | * fieldset 30 | * hidden 31 | * html 32 | * license 33 | * multiselect 34 | * mexicoStateSelect 35 | * number 36 | * radio 37 | * roleToPage 38 | * section 39 | * select 40 | * text 41 | * textDisabled 42 | * textarea 43 | * css 44 | * emails 45 | * ips 46 | * thickbox 47 | * touchtime 48 | * url 49 | * upload 50 | * usStateSelect 51 | 52 | *Each field is sanitized accordingly using the WordPress settings API with custom sanitize callbacks.* 53 | 54 | # Usage 55 | 56 | **Full working example** 57 | 58 | 1. Download the [zip](https://github.com/zanematthew/quilt-sample-plugin/archive/master.zip) file from the [Quilt Sample Plugin](https://github.com/zanematthew/quilt-sample-plugin) repository. 59 | 2. Install the plugin. 60 | 4. Under "Settings" see the sub-menu link named "Quilt Plugin", click that. 61 | 62 | ## Usage -- A single form field 63 | 64 | A single form field entry can consists of: 65 | 66 | ``` 67 | $field = array( 68 | 'id' => HTML ID 69 | 'title' => HTML Label 70 | 'type' => HTML field type 71 | 'value' => Value to set 72 | 'std' => The default value 73 | 'placeholder' => HTML Placeholder 74 | 'options' => An array of options 75 | ) 76 | ``` 77 | 78 | At minimum an entry only needs type, and title. 79 | 80 | ``` 81 | array( 82 | 'title' => HTML Label 83 | 'type' => HTML field type 84 | ) 85 | ``` 86 | 87 | 88 | ## Usage -- A full example 89 | 90 | 1. Require the two classes 91 | 2. Call the class 92 | 3. Assign your settings 93 | 94 | This is a full working example. 95 | 96 | 1. Create a folder in your `wp-content/plugins/` directory 97 | 2. Create a PHP file. 98 | 3. Copy/paste the below code into that file 99 | 4. Verify that you have the correct path for the additional files 100 | 5. Activate the plugin 101 | 6. Under "Settings" see the sub-menu link named "Quilt Plugin", click that. 102 | 103 | ``` 104 | /** 105 | * You can obtain the classes here: 106 | * https://github.com/zanematthew/lumber 107 | * https://github.com/zanematthew/quilt 108 | */ 109 | require plugin_dir_path( __FILE__ ) . 'lib/lumber/lumber.php'; 110 | require plugin_dir_path( __FILE__ ) . 'lib/quilt/quilt.php'; 111 | 112 | 113 | /** 114 | * This is best set as a constant. We will use it later. 115 | * Filters are derived from this. 116 | */ 117 | define( 'MY_SAMPLE_NAMESPACE', 'quilt_plugin' ); 118 | 119 | 120 | /** 121 | * This function shows an example of settings as a single page (not tabbed). 122 | * The settings are defined in the $settings array. 123 | */ 124 | function quilt_plugin_init(){ 125 | 126 | 127 | // You define settings here. Refer to above for each field "type". 128 | $settings = array( 129 | 'default_field_types' => array( 130 | 'title' => 'Default Fields Types', 131 | 'fields' => array( 132 | array( 133 | 'title' => 'Sample Text', 134 | 'type' => 'text' 135 | ), 136 | array( 137 | 'title' => 'Sample FancyText', 138 | 'type' => 'fancyText', 139 | 'desc' => 'Any "fancy" field type has a description with it.' 140 | ), 141 | array( 142 | 'title' => 'Sample Email', 143 | 'type' => 'email', 144 | 'desc' => 'Only allow a single email.', 145 | 'std' => get_option( 'admin_email' ) 146 | ), 147 | array( 148 | 'title' => 'Sample Number', 149 | 'type' => 'number', 150 | 'desc' => 'Only allow a single number, also shows usage of placeholder.', 151 | 'placeholder' => 'Enter a number' 152 | ), 153 | array( 154 | 'title' => 'Sample Hidden', 155 | 'type' => 'hidden', 156 | 'desc' => 'Yes, its hidden, sometimes this is good for passing values between JS.' 157 | ), 158 | array( 159 | 'title' => 'Sample URL', 160 | 'type' => 'url', 161 | 'desc' => 'Only a valid URL.' 162 | ), 163 | array( 164 | 'title' => 'Sample Button', 165 | 'type' => 'button', 166 | 'desc' => 'A button.', 167 | 'std' => 'Button' 168 | ), 169 | array( 170 | 'title' => 'Sample TextDisabled', 171 | 'type' => 'textDisabled', 172 | 'desc' => 'This text is disabled.', 173 | 'std' => 'You cannot edit this.' 174 | ), 175 | array( 176 | 'title' => 'Sample Checkbox', 177 | 'type' => 'checkbox', 178 | 'desc' => '' 179 | ), 180 | array( 181 | 'title' => 'Sample Group of Checkboxes', 182 | 'type' => 'checkboxes', 183 | 'desc' => 'Choose a transportation type.', 184 | 'options' => array( 185 | 'car' => 'Car', 186 | 'bike' => 'Bike' 187 | ) 188 | ), 189 | array( 190 | 'title' => 'Sample Radio', 191 | 'type' => 'radio', 192 | 'desc' => 'Yes, or no.', 193 | 'options' => array( 194 | 'yes' => 'Yes', 195 | 'no' => 'No' 196 | ) 197 | ), 198 | array( 199 | 'title' => 'Sample Select', 200 | 'type' => 'select', 201 | 'options' => array( 202 | 'maybe' => 'Maybe', 203 | 'yes' => 'Yes', 204 | 'no' => 'No' 205 | ) 206 | ), 207 | array( 208 | 'title' => 'Sample FancySelect', 209 | 'type' => 'fancySelect', 210 | 'desc' => 'Which one?', 211 | 'options' => array( 212 | 'maybe' => 'Maybe', 213 | 'yes' => 'Yes', 214 | 'no' => 'No' 215 | ) 216 | ), 217 | array( 218 | 'title' => 'Sample Multiselect', 219 | 'type' => 'multiselect', 220 | 'desc' => 'A multi-select', 221 | 'options' => array( 222 | 'maybe' => 'Maybe', 223 | 'yes' => 'Yes', 224 | 'no' => 'No' 225 | ) 226 | ), 227 | array( 228 | 'title' => 'Sample UsStateSelect', 229 | 'type' => 'usStateSelect', 230 | 'desc' => 'No options are needed.' 231 | ), 232 | array( 233 | 'title' => 'Sample MexicoStateSelect', 234 | 'type' => 'mexicoStateSelect', 235 | 'desc' => 'No options are needed.' 236 | ), 237 | array( 238 | 'title' => 'Sample CanadaStateSelect', 239 | 'type' => 'canadaStateSelect', 240 | 'desc' => 'No options are needed.' 241 | ), 242 | array( 243 | 'title' => 'Sample Textarea', 244 | 'type' => 'textarea' 245 | ), 246 | array( 247 | 'title' => 'Sample CSS', 248 | 'type' => 'css', 249 | 'desc' => 'Allow for only CSS (sanitized with wp_kses).' 250 | ), 251 | array( 252 | 'title' => 'Sample Emails', 253 | 'type' => 'emails', 254 | 'desc' => 'Allow for only emails. Enter each email on a new line.', 255 | 'placeholder' => 'Enter each email on a new line.' 256 | ), 257 | array( 258 | 'title' => 'Sample Ips', 259 | 'type' => 'ips', 260 | 'desc' => 'Allow for only emails. Enter each email on a new line.', 261 | 'placeholder' => 'Enter each email on a new line.' 262 | ), 263 | array( 264 | 'title' => 'Sample Upload', 265 | 'type' => 'upload', 266 | 'desc' => 'A WordPress upload field' 267 | ), 268 | array( 269 | 'title' => 'Sample HTML', 270 | 'type' => 'html', 271 | 'desc' => 'Any HTML you want', 272 | 'std' => '
This is my custom HTML.
' 273 | ), 274 | array( 275 | 'title' => 'Sample Thickbox', 276 | 'type' => 'thickbox', 277 | 'desc' => 'Yes, a thickbox. Via WordPress native Thickbox. Currently ONLY supports iFrame.', 278 | 'std' => 'http://zanematthew.com/', 279 | 'placeholder' => 'View Entries' 280 | ), 281 | array( 282 | 'title' => 'Sample Touchtime', 283 | 'type' => 'touchtime', 284 | 'desc' => 'Another built in WordPress type.' 285 | ), 286 | array( 287 | 'title' => 'Sample RoleToPage', 288 | 'type' => 'roleToPage', 289 | 'desc' => '', 290 | 'options' => array( 291 | 'administrator' => 'Administrator', 292 | 'editor' => 'Editor', 293 | 'author' => 'Author', 294 | 'contributor' => 'Contributor', 295 | 'subscriber' => 'Subscriber' 296 | ) 297 | ) 298 | ) 299 | ) 300 | ); 301 | 302 | 303 | // Instantiate the class 304 | $quilt_plugin = new Quilt( 305 | MY_SAMPLE_NAMESPACE, 306 | $settings, 307 | 'plugin' 308 | ); 309 | 310 | // Set our global used to retrieve settings 311 | global $quilt_plugin_settings; 312 | 313 | // Assign the option. This allows us to use the standard option 314 | // values WITHOUT having to set any options in the database. 315 | $quilt_plugin_settings = $quilt_plugin->getSaneOptions(); 316 | 317 | } 318 | add_action( 'init', 'quilt_plugin_init' ); 319 | ``` 320 | 321 | ## Usage – Retrieving the Settings 322 | 323 | You can retrieve your settings via the `get_options()` method, and assign this to a global variable. 324 | 325 | ``` 326 | $my_settigns_obj = Quilt(); 327 | 328 | global $my_settings; 329 | echo $my_settings_obj->get_options( 'some_text_field' ); 330 | ``` 331 | 332 | # Uninstall Settings 333 | 334 | Do it the [WordPress way](http://codex.wordpress.org/Function_Reference/register_uninstall_hook#uninstall.php). 335 | 336 | # Hooks 337 | 338 | All **filters** and **actions** are dynamically create via your namespace using the following format. Filters are listed below. 339 | 340 | ``` 341 | apply_filters( 'quilt_{$my_namespace}_settings' ); 342 | apply_filters( 'quilt_{$my_namespace}_admin_menu' ); 343 | apply_filters( 'quilt_{$my_namespace}_below_tabs' ); 344 | apply_filters( 'quilt_{$my_namespace}_below_title' ); 345 | apply_filters( 'quilt_{$my_namespace}_footer' ); 346 | apply_filters( 'quilt_{$my_namespace}_all_default_options' ); 347 | apply_filters( 'quilt_{$my_namespace}_get_option' ); 348 | apply_filters( 'quilt_{$my_namespace}_get_setting_{$key}' ); 349 | apply_filters( 'quilt_{$my_namespace}_sanitize_{$type}' ); 350 | apply_filters( 'quilt_{$my_namespace}_sanitize_{$key}' ); 351 | apply_filters( 'quilt_{$my_namespace}_sanitize_{$field_id}' ); 352 | apply_filters( 'quilt_{$my_namespace}_admin_styles' ); 353 | apply_filters( 'quilt_{$my_namespace}_admin_style' ); 354 | apply_filters( 'quilt_{$my_namespace}_admin_script' ); 355 | apply_filters( 'quilt_{$my_namespace}_default_tab' ); 356 | apply_filters( 'quilt_{$my_namespace}_page_title' ); 357 | apply_filters( 'quilt_{$my_namespace}_menu_title' ); 358 | 359 | do_action( 'quilt_{$my_namespace}_above_form' ); 360 | do_action( 'quilt_{$my_namespace}_after_left_buttons' ); 361 | ``` 362 | 363 | **Sample Filter usage** 364 | 365 | ``` 366 | // Note 'my_plugin' matches the namespace passed into the Quilt class. 367 | function my_plugin_change_footer( $text ){ 368 | 369 | return 'My custom footer'; 370 | 371 | } 372 | add_filter( 'quilt_my_plugin_footer', 'my_plugin_change_footer' ); 373 | ``` 374 | 375 | --- 376 | 377 | *At its core Quilt handles interacting with the WordPress settings API; assigning the sections, registering settings, setting [sane options](https://make.wordpress.org/themes/2014/07/09/using-sane-defaults-in-themes/), and sanitizing. It actually does not create form fields. Lumber does that.* 378 | -------------------------------------------------------------------------------- /assets/javascripts/admin.js: -------------------------------------------------------------------------------- 1 | jQuery( document ).ready(function( $ ){ 2 | 3 | $('.button-restore-default').on('click', function( e ){ 4 | 5 | r = confirm( _quilt.restore_default_message ); 6 | 7 | if ( r == true){ 8 | 9 | var $this = $(this); 10 | 11 | $.ajax({ 12 | url: ajaxurl, 13 | dataType: 'json', 14 | type: 'POST', 15 | data: { 16 | action: 'restoreDefaultsAjax', 17 | namespace: $this.data('namespace'), 18 | _wpnonce: $this.data('quilt_restore_default_nonce') 19 | }, 20 | success: function( msg ){ 21 | location.reload(); 22 | } 23 | }); 24 | 25 | } 26 | 27 | }); 28 | 29 | }); -------------------------------------------------------------------------------- /assets/stylesheets/admin.css: -------------------------------------------------------------------------------- 1 | .form-table th, 2 | .form-table td {} 3 | 4 | .log-container { 5 | border: 1px solid #CCC; 6 | margin-bottom: 1em; 7 | } 8 | 9 | .log-title { 10 | background: #EEE; 11 | border-bottom: 1px solid #CCC; 12 | text-shadow: 0 1px 1px #fff; 13 | margin: 0; 14 | text-align: left; 15 | padding: 10px 15px; 16 | } 17 | 18 | .log-data { 19 | background: #eee; 20 | max-height: 700px; 21 | overflow-y: auto; 22 | } 23 | 24 | .log-data ol { 25 | margin: 0 0 0 40px; 26 | border-left: 1px solid #CCC; 27 | background: white; 28 | } 29 | 30 | .log-data ol li { 31 | color: #888; 32 | margin: 0; 33 | padding: 2px 0; 34 | font-size: 12px; 35 | border-bottom: 1px solid rgba(136, 136, 136, 0.10); 36 | } 37 | 38 | .log-data ol li:hover { 39 | background: rgba(253, 255, 192, 0.67); 40 | } 41 | 42 | .log-data ol li p { 43 | margin: 0; 44 | color: #333; 45 | font-size: 12px; 46 | line-height: 24px; 47 | padding-left: 10px; 48 | } 49 | 50 | .wp-core-ui .button-restore-default { 51 | margin-left: 20px; 52 | float: right; 53 | } 54 | 55 | .footer .description { 56 | margin-bottom: 30px; 57 | } -------------------------------------------------------------------------------- /quilt.php: -------------------------------------------------------------------------------- 1 | namespace = $this->sanitizeNamespace( $_POST['namespace'] ); 72 | } else { 73 | $this->namespace = $this->sanitizeNamespace( $namespace ); 74 | } 75 | 76 | $this->action_prefix = $this->app . '_' . $this->namespace; 77 | $this->filter_prefix = $this->app . '_' . $this->namespace; 78 | $this->setting_type = $type; 79 | 80 | // @todo huh? 81 | if ( isset( $paths['dir_url_form_fields'] ) ){ 82 | $this->dir_url_form_fields = trailingslashit( $paths['dir_url_form_fields'] ); 83 | add_filter( 'lumber_dir_url', array( &$this, 'zmFormFieldsDirUrl' ) ); 84 | } 85 | 86 | $this->settings = $settings; 87 | $this->app_url = plugin_dir_url( __FILE__ ); 88 | 89 | add_action( 'admin_menu', array( &$this, 'adminMenu' ) ); 90 | add_action( 'admin_init', array( &$this, 'registerSettings' ) ); 91 | add_action( 'admin_enqueue_scripts', array( &$this, 'adminEnqueueScripts') ); 92 | add_action( 'admin_notices', array( &$this, 'adminNoticesAction' ) ); 93 | add_action( 'wp_ajax_restoreDefaultsAjax', array( &$this, 'restoreDefaultsAjax' ) ); 94 | 95 | } 96 | 97 | 98 | /** 99 | * Displays the setting error. 100 | * 101 | * @since 1.0.0 102 | * @return void 103 | */ 104 | public function adminNoticesAction() { 105 | 106 | global $pagenow; 107 | 108 | if ( $pagenow == 'themes.php' ){ 109 | 110 | $args = wp_parse_args( $_SERVER['REQUEST_URI'] ); 111 | 112 | if ( isset( $args['settings-updated'] ) && $args['settings-updated'] == true ){ 113 | add_settings_error( 114 | $this->namespace, 115 | 'noon', 116 | __( 'Options saved.', $this->namespace ), 117 | 'updated' 118 | ); 119 | } 120 | } 121 | 122 | } 123 | 124 | 125 | 126 | // @todo huh? 127 | public function zmFormFieldsDirUrl(){ 128 | 129 | return $this->dir_url_form_fields; 130 | 131 | } 132 | 133 | 134 | /** 135 | * Return our settings 136 | * 137 | * @since 1.0.0 138 | * @return $settings (array) The settings as a multi-dimensional array 139 | */ 140 | public function settings(){ 141 | 142 | return apply_filters( $this->filter_prefix . '_settings', $this->settings ); 143 | 144 | } 145 | 146 | 147 | /** 148 | * This function adds all our settings sections, settings form fields, and registers 149 | * a single setting, which holds all of our settings, i.e., get_option( 'my_product' ) will 150 | * contain the raw settings as seen in the *_options table. 151 | * 152 | * @since 1.0.0 153 | * @return void 154 | */ 155 | public function registerSettings(){ 156 | 157 | $settings = $this->settings(); 158 | $options = $this->getSaneOptions( $settings ); 159 | 160 | foreach( $settings as $id => $section ) { 161 | 162 | add_settings_section( 163 | $this->namespace . '_' . $id, // ID 164 | __return_null(), // Title 165 | '__return_false', // Callback 166 | $this->namespace . '_' . $id // Page 167 | ); 168 | 169 | foreach ( $section['fields'] as $field ) { 170 | 171 | $field_id = $this->getFieldId( $field ); 172 | 173 | if ( isset( $field['value'] ) ) { 174 | $value = $options[ $field_id ]; 175 | } else if ( isset( $field_id ) && isset( $options[ $field_id ] ) ){ 176 | $value = $options[ $field_id ]; 177 | } elseif ( isset( $field['std'] ) ) { 178 | $value = $field['std']; 179 | } else { 180 | $value = null; 181 | } 182 | 183 | 184 | // Determine the callback 185 | // @todo document, this also allows for method overloading 186 | $method_name = 'do' . ucwords( $field['type'] ); 187 | 188 | if ( method_exists( $this, $method_name ) ){ 189 | $callback = array( $this, $method_name ); 190 | } else { 191 | $callback = array( $this, 'missingCallback' ); 192 | } 193 | 194 | $field['namespace'] = $this->namespace; 195 | 196 | // These are extra params passed into our function/method 197 | $params = array_merge( $this->getAttributes( $field ), array( 198 | 'echo' => true, 199 | 'id' => $this->getFieldHtmlId( $field ), 200 | 'value' => $value, 201 | 'options' => isset( $field['options'] ) ? $field['options'] : '', 202 | 'name' => $this->namespace . '[' . $field_id . ']', 203 | 'title' => '', 204 | 'namespace' => $this->namespace, 205 | 'settings_id' => $field_id 206 | ) ); 207 | 208 | $title = isset( $field['title'] ) ? $field['title'] : ''; 209 | 210 | add_settings_field( 211 | $this->namespace . '[' . $field_id . ']', // ID 212 | $title, // Title 213 | $callback, // Callback 214 | $this->namespace . '_' . $id, // Page 215 | $this->namespace . '_' . $id, // Section 216 | $params // Params 217 | ); 218 | } 219 | } 220 | 221 | register_setting( $this->namespace, $this->namespace, array( &$this, 'sanitizeSingle' ) ); 222 | 223 | } 224 | 225 | 226 | /** 227 | * Build our admin menu 228 | * 229 | * @since 1.0.0 230 | */ 231 | public function adminMenu(){ 232 | 233 | $params = apply_filters( $this->filter_prefix . '_admin_menu', array( 234 | 'title' => $this->namespaceToPageTitle(), 235 | 'menu_title' => $this->namespaceToMenuTitle(), 236 | 'permission' => 'manage_options', 237 | 'namespace' => $this->namespace, 238 | 'template' => array( &$this, 'loadTemplate' ), 239 | 'submenu' => 'options-general.php' 240 | ) ); 241 | 242 | if ( $this->setting_type == 'theme' ){ 243 | 244 | add_theme_page( 245 | $params['title'], 246 | $params['menu_title'], 247 | $params['permission'], 248 | $params['namespace'], 249 | $params['template'] 250 | ); 251 | 252 | } elseif ( $this->setting_type == 'plugin' ) { 253 | 254 | add_submenu_page( 255 | $params['submenu'], 256 | $params['title'], 257 | $params['menu_title'], 258 | $params['permission'], 259 | $params['namespace'], 260 | $params['template'] 261 | ); 262 | 263 | } else { 264 | 265 | wp_die('Invalid setting_type'); 266 | 267 | } 268 | 269 | } 270 | 271 | 272 | /** 273 | * Call back function which is fired when the admin menu page is loaded. 274 | * 275 | * @since 1.0.0 276 | */ 277 | public function loadTemplate(){ 278 | 279 | // If we wanted to we can set a current tab 280 | $tab = isset( $_GET['tab'] ) ? $_GET['tab'] : null; 281 | $current_tab = false; 282 | $tab_ids = array(); 283 | $settings = $this->settings(); 284 | 285 | foreach( $settings as $id => $section ){ 286 | if ( isset( $tab ) && $tab == $id ){ 287 | $current_tab = $id; 288 | } 289 | $tab_ids[] = $id; 290 | } 291 | 292 | // If we do not have a current tab set we assign the first ID in our array of IDs 293 | // to be the first/active tab 294 | $current_tab = empty( $current_tab ) ? $tab_ids[0] : $current_tab; 295 | 296 | // We have a single tab instance, no need to use tabs 297 | if ( count( $tab_ids ) == 1 ) { 298 | $tabs = $settings; 299 | 300 | $title = '

' . $tabs[ $current_tab ]['title'] . '

'; 301 | $desc = empty( $tabs[ $current_tab ]['desc'] ) ? null : '

' . $tabs[ $current_tab ]['desc'] . '

'; 302 | 303 | $tabs = $title . $desc; 304 | 305 | } 306 | 307 | // We have multiple settings, lets build tabs 308 | else { 309 | $tabs = null; 310 | foreach( $settings as $id => $section ){ 311 | $tab_url = add_query_arg( array( 312 | 'settings-updated' => false, 313 | 'tab' => $id 314 | ) ); 315 | 316 | $active = $current_tab == $id ? ' nav-tab-active' : ''; 317 | 318 | $tabs .= ''; 319 | $tabs .= esc_html( $section['title'] ); 320 | $tabs .= ''; 321 | } 322 | 323 | $tabs = ''; 324 | } 325 | 326 | global $pagenow; 327 | if ( $pagenow == 'themes.php' ){ 328 | settings_errors( $this->namespace ); 329 | } 330 | 331 | $below_tabs = apply_filters( $this->filter_prefix . '_below_tabs', null ); 332 | $below_title = apply_filters( $this->filter_prefix . '_below_title', null ); 333 | $description = apply_filters( $this->filter_prefix . '_footer', __( 'Thank you for using Quilt.', $this->namespace ) ); 334 | 335 | ?> 336 |
337 |

338 |

namespaceToPageTitle(); ?>

339 | 340 | filter_prefix . '_above_form' ); ?> 341 |
342 | 343 | 344 | 345 | namespace ); ?> 346 | namespace . '_' . $current_tab, $this->namespace . '_' . $current_tab ); ?> 347 |
348 | 349 | namespace . '_' . $current_tab ); ?> 350 | 351 |
352 | 362 |
363 |
364 | namespace ); 376 | 377 | return $settings; 378 | } 379 | 380 | 381 | /** 382 | * Get the default options as set settings array 383 | * These options are formatted into an associative array. Since being 384 | * mapped as an associative array each key MUST be unique! 385 | * 386 | * @filter {namespace}_all_default_options $defaults 387 | * @return The formatted and filtered array 388 | * 389 | */ 390 | public function getDefaultOptions( $settings=null ){ 391 | 392 | $defaults = array(); 393 | 394 | if ( empty( $settings ) ) 395 | return $defaults; 396 | 397 | foreach( $settings as $k => $v ){ 398 | foreach( $v['fields'] as $field ){ 399 | $field_id = $this->getFieldId( $field ); 400 | if ( isset( $field['std'] ) ){ 401 | $defaults[ $field_id ] = $field['std']; 402 | } 403 | } 404 | } 405 | 406 | return apply_filters( $this->filter_prefix . '_all_default_options', $defaults ); 407 | 408 | } 409 | 410 | 411 | /** 412 | * Get all values in the key of 'std' from the settings array 413 | * 414 | * @since 1.0.0 415 | * 416 | * @return $values (array) All values as an array. 417 | */ 418 | public function getStdValues(){ 419 | 420 | foreach( $this->settings as $k => $v ){ 421 | foreach( $v['fields'] as $field ){ 422 | $field_id = $this->getFieldId( $field ); 423 | if ( isset( $field['options'] ) ){ 424 | $defaults[ $field_id ] = $field['options']; 425 | } 426 | } 427 | } 428 | 429 | return $defaults; 430 | } 431 | 432 | 433 | /** 434 | * Get a SINGLE value in the key of 'std' from the settings array 435 | * 436 | * @since 1.0.0 437 | * @param $key (string) The key to retrieve the value for. 438 | * @return $values (array) All values as an array. 439 | */ 440 | public function getStdValue( $key=null ){ 441 | 442 | $values = $this->getStdValues(); 443 | 444 | return ( array_key_exists( $key, $values ) ) ? $values[ $key ] : false; 445 | 446 | } 447 | 448 | 449 | /** 450 | * Merge the default options with the options array 451 | * This allows us to use settings from the settings array, as apposed 452 | * to having the user visit the settings, press "save" and save the 453 | * defaults to the db. More info can be found on 454 | * [using sane defaults in themes](https://make.wordpress.org/themes/2014/07/09/using-sane-defaults-in-themes/). 455 | * 456 | * @since 1.0.0 457 | * @return Associative array containing options from DB and defaults. 458 | */ 459 | public function getSaneOptions( $settings=null ){ 460 | 461 | $options = $this->getOptions(); 462 | 463 | if ( empty( $options ) ){ 464 | $options = $this->getDefaultOptions( $settings ); 465 | } else { 466 | $options = array_merge( $this->getDefaultOptions( $settings ), $options ); 467 | } 468 | 469 | return $options; 470 | 471 | } 472 | 473 | 474 | /** 475 | * Get a single option value from the settings array 476 | * 477 | * @since 1.0.0 478 | * @param $key The option key to get, $default, the default if any 479 | * @return Option from database 480 | */ 481 | public function getSaneOption( $key='', $default=false ) { 482 | 483 | $options = $this->getSaneOptions( $this->settings() ); 484 | 485 | $value = ! empty( $options[ $key ] ) ? $options[ $key ] : $default; 486 | $value = apply_filters( $this->filter_prefix . '_get_option', $value, $key, $default ); 487 | 488 | return apply_filters( $this->filter_prefix . '_get_setting_' . $key, $value, $key, $default ); 489 | 490 | } 491 | 492 | 493 | /** 494 | * This is the first stop when settings are being saved. 495 | * Each setting is then filtered through a "type" specific filter, and then 496 | * an "id" specific filter. 497 | * 498 | * i.e., {$this->namespace}_{$type}_sanitize( $input ), $type=text,select,checkbox,etc. 499 | * i.e., {$this->namespace}_{$id}_sanitize( $input ), $id=my_custom_field,etc. 500 | * 501 | * @since 1.0.0 502 | */ 503 | public function sanitizeSingle( $input=array() ){ 504 | 505 | $settings = $this->settings(); 506 | $tab = $this->getTab(); 507 | $input = $input ? $input : array(); 508 | $tmp = array(); 509 | 510 | foreach( $settings[ $tab ]['fields'] as $field ){ 511 | 512 | 513 | $field_id = $this->getFieldId( $field ); 514 | 515 | $key = $field_id; 516 | $value = isset( $input[ $field_id ] ) ? $input[ $field_id ] : null; 517 | $type = $field['type']; 518 | 519 | if ( array_key_exists( $key, $input ) ){ 520 | 521 | switch( $type ) { 522 | case 'select' : 523 | case 'us_state' : 524 | case 'textarea' : 525 | case 'textarea_email_template' : 526 | case 'checkbox' : 527 | case 'radio' : 528 | $input[ $key ] = $this->sanitizeDefault( $value ); 529 | break; 530 | 531 | case 'checkboxes' : 532 | $tmp = array(); 533 | foreach( $field['options'] as $k => $v ){ 534 | if ( is_array( $v ) ){ 535 | if ( in_array($v['id'], $value) ){ 536 | if ( ! empty( $v['title'] ) ){ 537 | $tmp[ $v['id'] ] = array( 538 | 'id' => $v['id'], 539 | 'title' => $v['title'] 540 | ); 541 | } 542 | } 543 | } elseif ( in_array( $k, $value ) ){ 544 | $input[ $key ] = $value; 545 | } 546 | } 547 | if ( ! empty( $tmp ) ){ 548 | $input[ $key ] = $tmp; 549 | } 550 | break; 551 | 552 | case 'multiselect' : 553 | $input[ $key ] = $this->sanitizeMultiselect( $value ); 554 | break; 555 | 556 | case 'emails' : 557 | $input[ $key ] = $this->sanitizeEmails( $value ); 558 | break; 559 | 560 | // Yes, ips 561 | case 'ips' : 562 | $input[ $key ] = $this->sanitizeIps( $value ); 563 | break; 564 | 565 | case 'touchtime' : 566 | $input[ $key ] = $this->sanitizeTouchtime( $value ); 567 | break; 568 | 569 | case 'roleToPage' : 570 | $input[ $key ] = $this->sanitizeRoleToPage( $value ); 571 | break; 572 | 573 | default: 574 | $input[ $key ] = $this->sanitizeDefault( $value ); 575 | break; 576 | } 577 | 578 | 579 | // Sanitize by type 580 | if ( ! empty( $input[ $key ] ) ){ 581 | $input[ $key ] = apply_filters( $this->filter_prefix . '_sanitize_' . $type, $input[ $key ], $field_id ); 582 | } 583 | } 584 | 585 | // sanitize by key here via filter, i.e., all type="text" fields 586 | if ( ! empty( $input[ $key ] ) ){ 587 | $input[ $key ] = apply_filters( $this->filter_prefix . '_sanitize_' . $key, $input[ $key ], $field_id ); 588 | } 589 | 590 | // Sanitize by field id 591 | // $input[ $key ] = apply_filters( $this->filter_prefix . '_sanitize_' . $field_id, $input[ $key ], $field_id ); 592 | 593 | } 594 | 595 | // Loop through the whitelist and unset any that are empty for the tab being saved 596 | $options = $this->getSaneOptions( $settings ); 597 | 598 | if ( ! empty( $settings[ $tab ] ) ) { 599 | foreach ( $settings[ $tab ]['fields'] as $field ) { 600 | $key = $this->getFieldId( $field ); 601 | 602 | if ( empty( $input[ $key ] ) ) { 603 | unset( $options[ $key ] ); 604 | } 605 | } 606 | } 607 | 608 | // Merge our new settings with the existing 609 | $output = array_merge( $options, $input ); 610 | 611 | return $output; 612 | } 613 | 614 | 615 | /** 616 | * Just return the input being saved. 617 | * 618 | * @since 1.0.0 619 | */ 620 | public function sanitizeDefault( $input=null ){ 621 | 622 | return esc_attr( trim( $input ) ); 623 | 624 | } 625 | 626 | 627 | /** 628 | * Sanitize the role page ID to only be an absolute integer 629 | * 630 | * @since 1.0.1 631 | * @param $input (array) An array of values 632 | * @return The array with any non-absolute integers removed 633 | */ 634 | public function sanitizeRoleToPage( $input=null ){ 635 | 636 | foreach( $input as $k => $v ){ 637 | if ( ! empty( $v ) ){ 638 | $input[ $k ] = absint( $v ); 639 | } 640 | } 641 | 642 | return $input; 643 | } 644 | 645 | /** 646 | * Missing callback 647 | * 648 | * @since 1.0.0 649 | */ 650 | public function missingCallback(){ 651 | 652 | echo 'No callback'; 653 | 654 | } 655 | 656 | 657 | /** 658 | * Renders header 659 | * 660 | * @since 1.0.0 661 | * @param array $args Arguments passed by the setting 662 | * @return void 663 | */ 664 | public function doHeader(){ 665 | 666 | echo '
'; 667 | 668 | } 669 | 670 | 671 | /** 672 | * Renders description fields. 673 | * 674 | * @since 1.0.0 675 | * @param array $args Arguments passed by the setting 676 | * @return void 677 | */ 678 | public function doDesc( $args ) { 679 | 680 | echo '

' . $args['desc'] . '

'; 681 | 682 | } 683 | 684 | 685 | /** 686 | * Renders license fields. 687 | * 688 | * @since 1.0.0 689 | * @param array $args Arguments passed by the setting 690 | * @return void 691 | */ 692 | public function doLicense( $args ) { 693 | 694 | // Use this to pass in the license data 695 | // Namespace specific 696 | $args = apply_filters( $this->app . '_' . $args['settings_id'] . '_license_args', $args ); 697 | 698 | 699 | if ( empty( $args['store_info'] ) ){ 700 | $data = ''; 701 | } else { 702 | $data = json_encode( $args['store_info'] ); 703 | } 704 | 705 | $button_text = __('Activate', $this->namespace ); 706 | $status_text = null; 707 | $action = 'license_activate'; 708 | 709 | // Handle displaying of different license status' here, 710 | // currently we just handle "valid" 711 | if ( ! empty( $args['extra']['license_data'] ) ){ 712 | if ( $args['extra']['license_data']['license'] == 'valid' ) { 713 | $status_text = ' ' . __('Active', $this->namespace ) . ''; 714 | $button_text = __('Deactivate', $this->namespace ); 715 | $action = 'license_deactivate'; 716 | } else { 717 | $status_text = ' ' . __('Inactive', $this->namespace ) . ''; 718 | } 719 | } 720 | 721 | /** 722 | * Display our input field 723 | */ 724 | ?> 725 | 726 | 727 | 728 | 735 | 736 | 737 | 738 | 747 | 748 | app . '_' . $args['settings_id'] . '_below_license', $args['settings_id'] ); ?> 749 | 750 | 751 | filter_prefix . '_admin_styles', array( 767 | array( 768 | 'handle' => $this->app . '-admin-style', 769 | 'src' => apply_filters( $this->filter_prefix . '_admin_style', $this->app_url . 'assets/stylesheets/admin.css' ), 770 | 'deps' => '', 771 | 'ver' => $this->version, 772 | 'media' => '' 773 | ) 774 | ) ); 775 | 776 | foreach( $styles as $style ){ 777 | wp_enqueue_style( $style['handle'], $style['src'], $style['deps'], $style['ver'], $style['media'] ); 778 | } 779 | 780 | $scripts = array( 781 | array( 782 | 'handle' => $this->app . '-admin-script', 783 | 'src' => apply_filters( $this->filter_prefix . '_admin_script', $this->app_url . 'assets/javascripts/admin.js' ), 784 | 'deps' => array('jquery'), 785 | 'ver' => $this->version, 786 | 'in_footer' => true, 787 | ) 788 | ); 789 | 790 | foreach( $scripts as $script ){ 791 | wp_enqueue_script( $script['handle'], $script['src'], $script['deps'], $script['ver'], $script['in_footer'] ); 792 | } 793 | 794 | wp_localize_script( $this->app . '-admin-script', '_' . $this->app, array( 795 | 'restore_default_message' => __( "This will delete your current settings and restore the defaults.", $this->namespace ) 796 | ) ); 797 | } 798 | 799 | 800 | /** 801 | * Determine which tab to use to save the settings/options 802 | * 803 | * @since 1.0.0 804 | * @param 805 | * @return $tab (string) The default tab to use for saving settings/options 806 | */ 807 | public function getTab(){ 808 | 809 | // If we have a referrer tab 810 | if ( isset( $_POST['_wp_http_referer'] ) ) 811 | parse_str( $_POST['_wp_http_referer'], $referrer ); 812 | 813 | if ( isset( $referrer['tab'] ) ){ 814 | $tab = $referrer['tab']; 815 | } 816 | 817 | else { 818 | $tab = key( $this->settings() ); 819 | } 820 | 821 | return apply_filters( $this->filter_prefix . '_default_tab', $tab ); 822 | } 823 | 824 | 825 | /** 826 | * Should return a string that is safe to be used in function names, as a variable, etc. 827 | * free of illegal characters. 828 | * 829 | * @since 1.0.0 830 | * @param $namespace (string) The namespace to sanitize 831 | * @return $namespace (string) The namespace free of illegal characters. 832 | */ 833 | public function sanitizeNamespace( $namespace=null ){ 834 | 835 | return str_replace( array('-', ' ' ), '_', $namespace ); 836 | 837 | } 838 | 839 | 840 | /** 841 | * Converts a sanitized namespace to be used a page title. 842 | * 843 | * @since 1.0.0 844 | * @param 845 | * @return $string A string to be used a the page title 846 | */ 847 | public function namespaceToPageTitle(){ 848 | 849 | return apply_filters( $this->filter_prefix . '_page_title', $this->namespaceToString(), $this->namespace ); 850 | 851 | } 852 | 853 | 854 | /** 855 | * Converts a sanitized namespace to be used a menu title. 856 | * 857 | * @since 1.0.0 858 | * @param 859 | * @return $string A string to be used a the menu title 860 | */ 861 | public function namespaceToMenuTitle(){ 862 | 863 | return apply_filters( $this->filter_prefix . '_menu_title', $this->namespaceToString(), $this->namespace ); 864 | 865 | } 866 | 867 | 868 | /** 869 | * Converts a sanitized namespace to be used as a string 870 | * 871 | * @since 1.0.0 872 | * @param $namespace 873 | * @return $string A string to be used a the menu title 874 | */ 875 | public function namespaceToString(){ 876 | 877 | return ucwords( str_replace( array('-','_'), ' ', $this->sanitizeNamespace( $this->namespace ) ) ); 878 | 879 | } 880 | 881 | 882 | /** 883 | * Sets the defaults by deleting the option and allowing the sane options to be used. 884 | * 885 | * @since 1.0.0 886 | * @return bool 887 | */ 888 | public function restoreDefaults( $namespace=null ){ 889 | 890 | return delete_option( $namespace ); 891 | 892 | } 893 | 894 | 895 | /** 896 | * Processes the ajax request, checking the needed security values 897 | * then sends it to restoreDefaults 898 | * 899 | * @since 1.0.0 900 | * @return mixed 901 | */ 902 | public function restoreDefaultsAjax(){ 903 | 904 | check_admin_referer( 'restoreDefaultsAjax' ); 905 | 906 | return wp_send_json( array( 'message' => 'Restoring defaults', 'status' => $this->restoreDefaults( esc_attr( $_POST['namespace'] ) ) ) ); 907 | 908 | } 909 | 910 | } 911 | endif; --------------------------------------------------------------------------------