├── .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 = '' . $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 |
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;
--------------------------------------------------------------------------------