Completed
Push — master ( ce5918...20c3c4 )
by David
02:48
created

WL_Metabox_Field::verify_nonce()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 0
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Metaboxes: Field Metabox.
4
 *
5
 * @since      3.0.0
6
 * @package    Wordlift
7
 * @subpackage Wordlift/admin/WL_Metabox
8
 */
9
10
/**
11
 * All custom WL_Metabox_Field(s) must extend this class.
12
 *
13
 * This class deals with saving the most basic data type, strings. Use the
14
 * methods that are useful or overwrite them if you need custom behaviour.
15
 *
16
 * @since      3.0.0
17
 * @package    Wordlift
18
 * @subpackage Wordlift/admin/WL_Metabox
19
 */
20
class WL_Metabox_Field {
21
22
	/**
23
	 * A {@link Wordlift_Log_Service} instance.
24
	 *
25
	 * @since  3.15.0
26
	 * @access protected
27
	 * @var \Wordlift_Log_Service $log A {@link Wordlift_Log_Service} instance.
28
	 */
29
	protected $log;
30
31
	/**
32
	 * The meta name for this field's value.
33
	 *
34
	 * @var string $meta_name The meta name for this field's value.
35
	 */
36
	public $meta_name;
37
38
	/**
39
	 * The custom field settings.
40
	 *
41
	 * @var null|array $raw_custom_field The custom field settings.
42
	 */
43
	public $raw_custom_field;
44
45
	/**
46
	 * The schema.org predicate.
47
	 *
48
	 * @var string $predicate The schema.org predicate.
49
	 */
50
	public $predicate;
51
52
	/**
53
	 * The field's label.
54
	 *
55
	 * @var string $label The field's label.
56
	 */
57
	public $label;
58
59
	/**
60
	 * The WordLift data type.
61
	 *
62
	 * @var string $expected_wl_type The WordLift data type.
63
	 */
64
	public $expected_wl_type;
65
66
	/**
67
	 * The RDF data type.
68
	 *
69
	 * @var string $expected_uri_type The RDF data type.
70
	 */
71
	public $expected_uri_type;
72
73
	/**
74
	 * The cardinality.
75
	 *
76
	 * @var int $cardinality The cardinality.
77
	 */
78
	public $cardinality;
79
80
	/**
81
	 * The current value.
82
	 *
83
	 * @var array $data The current value.
84
	 */
85
	public $data;
86
87
	/**
88
	 * The current {@link WP_Post} id.
89
	 *
90
	 * @since 3.15.3
91
	 *
92
	 * @var int The current {@link WP_Post} id.
93
	 */
94
	private $post_id;
95
96
	/**
97
	 * Create a {@link WL_Metabox_Field} instance.
98
	 *
99
	 * @param array $args An array of parameters.
100
	 */
101
	public function __construct( $args ) {
102
103
		$this->log = Wordlift_Log_Service::get_logger( 'WL_Metabox_Field' );
104
105
		if ( empty( $args ) ) {
106
			return;
107
		}
108
109
		// Save a copy of the custom field's params.
110
		$this->raw_custom_field = reset( $args );
111
112
		// Extract meta name (post_meta key for the DB).
113
		$this->meta_name = key( $args );
114
115
		// Extract linked data predicate.
116
		if ( isset( $this->raw_custom_field['predicate'] ) ) {
117
			$this->predicate = $this->raw_custom_field['predicate'];
118
		} else {
119
			return;
120
		}
121
122
		// Extract human readable label.
123
		$exploded_predicate = explode( '/', $this->predicate );
124
125
		// Use the label defined for the property if set, otherwise the last part of the schema.org/xyz predicate.
126
		$this->label = isset( $this->raw_custom_field['metabox']['label'] ) ? $this->raw_custom_field['metabox']['label'] : end( $exploded_predicate );
127
128
		// Extract field constraints (numerosity, expected type).
129
		// Default constaints: accept one string..
130
		if ( isset( $this->raw_custom_field['type'] ) ) {
131
			$this->expected_wl_type = $this->raw_custom_field['type'];
132
		} else {
133
			$this->expected_wl_type = Wordlift_Schema_Service::DATA_TYPE_STRING;
134
		}
135
136
		$this->cardinality = 1;
137
		if ( isset( $this->raw_custom_field['constraints'] ) ) {
138
139
			$constraints = $this->raw_custom_field['constraints'];
140
141
			// Extract cardinality.
142
			if ( isset( $constraints['cardinality'] ) ) {
143
				$this->cardinality = $constraints['cardinality'];
144
			}
145
146
			// Which type of entity can we accept (e.g. Place, Event, ecc.)? .
147
			if ( Wordlift_Schema_Service::DATA_TYPE_URI === $this->expected_wl_type && isset( $constraints['uri_type'] ) ) {
148
				$this->expected_uri_type = is_array( $constraints['uri_type'] )
149
					? $constraints['uri_type']
150
					: array( $constraints['uri_type'] );
151
			}
152
		}
153
154
		// Save early the post id to avoid other plugins messing up with it.
155
		//
156
		// See https://github.com/insideout10/wordlift-plugin/issues/665.
157
		$this->post_id = get_the_ID();
158
159
	}
160
161
	/**
162
	 * Return nonce HTML.
163
	 *
164
	 * Overwrite this method in a child class to obtain custom behaviour.
165
	 */
166
	public function html_nonce() {
167
168
		return wp_nonce_field( 'wordlift_' . $this->meta_name . '_entity_box', 'wordlift_' . $this->meta_name . '_entity_box_nonce', true, false );
169
	}
170
171
	/**
172
	 * Verify nonce.
173
	 *
174
	 * Overwrite this method in a child class to obtain custom behaviour.
175
	 *
176
	 * @return boolean Nonce verification.
177
	 */
178
	public function verify_nonce() {
0 ignored issues
show
Coding Style introduced by
verify_nonce uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
179
180
		$nonce_name   = 'wordlift_' . $this->meta_name . '_entity_box_nonce';
181
		$nonce_verify = 'wordlift_' . $this->meta_name . '_entity_box';
182
183
		if ( ! isset( $_POST[ $nonce_name ] ) ) {
184
			return false;
185
		}
186
187
		// Verify that the nonce is valid.
188
		return wp_verify_nonce( $_POST[ $nonce_name ], $nonce_verify );
189
	}
190
191
	/**
192
	 * Load data from DB and store the resulting array in $this->data.
193
	 *
194
	 * Overwrite this method in a child class to obtain custom behaviour.
195
	 */
196
	public function get_data() {
197
198
		// Get the post id and load the data.
199
		$post_id    = $this->post_id;
200
		$this->data = get_post_meta( $post_id, $this->meta_name );
201
202
		$this->log->debug( 'Found ' . count( $this->data ) . " value(s) for meta $this->meta_name, post $post_id." );
203
204
	}
205
206
	/**
207
	 * Sanitizes data before saving to DB. Default sanitization trashes empty
208
	 * values.
209
	 *
210
	 * Stores the sanitized values into $this->data so they can be later processed.
211
	 * Overwrite this method in a child class to obtain custom behaviour.
212
	 *
213
	 * @param array $values Array of values to be sanitized and then stored into
214
	 *                      $this->data.
215
	 */
216
	public function sanitize_data( $values ) {
217
218
		$sanitized_data = array();
219
220
		if ( ! is_array( $values ) ) {
221
			$values = array( $values );
222
		}
223
224
		foreach ( $values as $value ) {
225
			$sanitized_value = $this->sanitize_data_filter( $value );
226
			if ( ! is_null( $sanitized_value ) ) {
227
				$sanitized_data[] = $sanitized_value;
228
			}
229
		}
230
231
		$this->data = $sanitized_data;
232
	}
233
234
	/**
235
	 * Sanitize a single value. Called from $this->sanitize_data. Default
236
	 * sanitization excludes empty values.
237
	 *
238
	 * Overwrite this method in a child class to obtain custom behaviour.
239
	 *
240
	 * @param string $value The value to sanitize.
241
	 *
242
	 * @return mixed Returns sanitized value, or null.
243
	 */
244
	public function sanitize_data_filter( $value ) {
245
246
		// TODO: all fields should provide their own sanitize which shouldn't
247
		// be part of a UI class.
248
249
		// If the field provides its own validation, use it.
250
		if ( isset( $this->raw_custom_field['sanitize'] ) ) {
251
			return call_user_func( $this->raw_custom_field['sanitize'], $value );
252
		}
253
254
		if ( ! is_null( $value ) && '' !== $value ) {         // do not use 'empty()' -> https://www.virendrachandak.com/techtalk/php-isset-vs-empty-vs-is_null/ .
255
			return $value;
256
		}
257
258
		return null;
259
	}
260
261
	/**
262
	 * Save data to DB.
263
	 *
264
	 * Overwrite this method in a child class to obtain custom behaviour.
265
	 *
266
	 * @param array $values Array of values to be sanitized and then stored into $this->data.
267
	 */
268
	public function save_data( $values ) {
0 ignored issues
show
Coding Style introduced by
save_data uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
269
270
		// Will sanitize data and store them in $field->data.
271
		$this->sanitize_data( $values );
272
273
		// Bail out, if the post id isn't set in the request or isn't numeric.
274
		//
275
		// See https://github.com/insideout10/wordlift-plugin/issues/665.
276
		if ( ! isset( $_POST['post_ID'] ) || ! is_numeric( $_POST['post_ID'] ) ) {
277
			return;
278
		}
279
280
		$entity_id = intval( $_POST['post_ID'] );
281
282
		// Take away old values.
283
		delete_post_meta( $entity_id, $this->meta_name );
284
285
		// insert new values, respecting cardinality.
286
		$single = ( 1 === $this->cardinality );
287
		foreach ( $this->data as $value ) {
288
			$this->log->trace( "Saving $value to $this->meta_name for entity $entity_id..." );
289
			$meta_id = add_post_meta( $entity_id, $this->meta_name, $value, $single );
290
			$this->log->debug( "$value to $this->meta_name for entity $entity_id saved with id $meta_id." );
291
		}
292
	}
293
294
	/**
295
	 * Returns the HTML tag that will contain the Field. By default the we
296
	 * return a <div> with data- attributes on cardinality and expected types.
297
	 *
298
	 * It is useful to provide data- attributes for the JS scripts.
299
	 *
300
	 * Overwrite this method in a child class to obtain custom behaviour.
301
	 */
302
	public function html_wrapper_open() {
303
304
		return "<div class='wl-field' data-cardinality='$this->cardinality'>";
305
	}
306
307
	/**
308
	 * Returns Field HTML (nonce included).
309
	 *
310
	 * Overwrite this method (or methods called from this method) in a child
311
	 * class to obtain custom behaviour.
312
	 *
313
	 * The HTML fragment includes the following parts:
314
	 * * html wrapper open.
315
	 * * heading.
316
	 * * nonce.
317
	 * * stored values.
318
	 * * an empty input when there are no stored values.
319
	 * * an add button to add more values.
320
	 * * html wrapper close.
321
	 */
322
	public function html() {
323
324
		// Open main <div> for the Field.
325
		$html = $this->html_wrapper_open();
326
327
		// Label.
328
		$html .= $this->get_heading_html();
329
330
		// print nonce.
331
		$html .= $this->html_nonce();
332
333
		// print data loaded from DB.
334
		$count = 0;
335
		$html  .= $this->get_stored_values_html( $count );
336
337
		// Print the empty <input> to add new values.
338
		if ( 0 === $count ) { // } || $count < $this->cardinality ) { DO NOT print empty inputs unless requested by the editor since fields might support empty strings.
339
			$this->log->debug( 'Going to print an empty HTML input...' );
340
			$html .= $this->html_input( '' );    // Will print an empty <input>.
341
			$count ++;
342
		}
343
344
		// If cardinality allows it, print button to add new values.
345
		$html .= $this->get_add_button_html( $count );
346
347
		// Close the HTML wrapper.
348
		$html .= $this->html_wrapper_close();
349
350
		return $html;
351
	}
352
353
	/**
354
	 * Print the heading with the label for the metabox.
355
	 *
356
	 * @since 3.15.0
357
	 * @return string The heading html fragment.
358
	 */
359
	protected function get_heading_html() {
360
361
		return "<h3>$this->label</h3>";
362
	}
363
364
	/**
365
	 * Print the stored values.
366
	 *
367
	 * @since 3.15.0
368
	 *
369
	 * @param int $count An output value: the number of printed values.
370
	 *
371
	 * @return string The html fragment.
372
	 */
373
	protected function get_stored_values_html( &$count ) {
374
375
		$html = '';
376
377
		// print data loaded from DB.
378
		$count = 0;
379
		if ( $this->data ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
380
			foreach ( $this->data as $value ) {
381
				if ( $count < $this->cardinality ) {
382
					$this->log->debug( "Going to print an HTML input #$count with $value..." );
383
					$html .= $this->html_input( $value );
384
				}
385
				$count ++;
386
			}
387
		}
388
389
		return $html;
390
	}
391
392
	/**
393
	 * Get the add button html.
394
	 *
395
	 * This function is protected, allowing extending class to further customize
396
	 * the add button html code.
397
	 *
398
	 * @since 3.15.0
399
	 *
400
	 * @param int $count The current number of values.
401
	 *
402
	 * @return string The add button html code.
403
	 */
404
	protected function get_add_button_html( $count ) {
405
406
		// If cardinality allows it, print button to add new values.
407
		if ( $count < $this->cardinality ) {
408
			return '<button class="button wl-add-input wl-button" type="button">' . esc_html__( 'Add' ) . '</button>';
409
		}
410
411
		// Return an empty string.
412
		return '';
413
	}
414
415
	/**
416
	 * Return a single <input> tag for the Field.
417
	 *
418
	 * @param mixed $value Input value.
419
	 *
420
	 * @return string The html code fragment.
421
	 */
422
	public function html_input( $value ) {
423
		$html = <<<EOF
424
			<div class="wl-input-wrapper">
425
				<input type="text" id="$this->meta_name" name="wl_metaboxes[$this->meta_name][]" value="$value" style="width:88%" />
426
				<button class="button wl-remove-input wl-button" type="button">Remove</button>
427
			</div>
428
EOF;
429
430
		return $html;
431
	}
432
433
	/**
434
	 * Returns closing for the wrapper HTML tag.
435
	 */
436
	public function html_wrapper_close() {
437
438
		return '</div><hr>';
439
	}
440
441
}
442