Completed
Push — master ( 5db201...585add )
by David
08:20
created

WL_Metabox_Field   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 399
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 399
rs 8.6
c 0
b 0
f 0
wmc 37
lcom 1
cbo 1

14 Methods

Rating   Name   Duplication   Size   Complexity  
A sanitize_data() 0 17 4
C __construct() 0 54 10
A html_nonce() 0 4 1
A verify_nonce() 0 12 2
A get_data() 0 11 2
A sanitize_data_filter() 0 16 4
A save_data() 0 16 2
A html_wrapper_open() 0 4 1
B html() 0 29 2
A get_heading_html() 0 4 1
A get_stored_values_html() 0 17 4
A get_add_button_html() 0 10 2
A html_input() 0 10 1
A html_wrapper_close() 0 4 1
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
	 * Create a {@link WL_Metabox_Field} instance.
89
	 *
90
	 * @param array $args An array of parameters.
91
	 */
92
	public function __construct( $args ) {
93
94
		$this->log = Wordlift_Log_Service::get_logger( 'WL_Metabox_Field' );
95
96
		if ( empty( $args ) ) {
97
			return;
98
		}
99
100
		// Save a copy of the custom field's params.
101
		$this->raw_custom_field = reset( $args );
102
103
		// Extract meta name (post_meta key for the DB).
104
		$this->meta_name = key( $args );
105
106
		// Extract linked data predicate.
107
		if ( isset( $this->raw_custom_field['predicate'] ) ) {
108
			$this->predicate = $this->raw_custom_field['predicate'];
109
		} else {
110
			return;
111
		}
112
113
		// Extract human readable label.
114
		$exploded_predicate = explode( '/', $this->predicate );
115
116
		// Use the label defined for the property if set, otherwise the last part of the schema.org/xyz predicate.
117
		$this->label = isset( $this->raw_custom_field['metabox']['label'] ) ? $this->raw_custom_field['metabox']['label'] : end( $exploded_predicate );
118
119
		// Extract field constraints (numerosity, expected type).
120
		// Default constaints: accept one string..
121
		if ( isset( $this->raw_custom_field['type'] ) ) {
122
			$this->expected_wl_type = $this->raw_custom_field['type'];
123
		} else {
124
			$this->expected_wl_type = Wordlift_Schema_Service::DATA_TYPE_STRING;
125
		}
126
127
		$this->cardinality = 1;
128
		if ( isset( $this->raw_custom_field['constraints'] ) ) {
129
130
			$constraints = $this->raw_custom_field['constraints'];
131
132
			// Extract cardinality.
133
			if ( isset( $constraints['cardinality'] ) ) {
134
				$this->cardinality = $constraints['cardinality'];
135
			}
136
137
			// Which type of entity can we accept (e.g. Place, Event, ecc.)? .
138
			if ( Wordlift_Schema_Service::DATA_TYPE_URI === $this->expected_wl_type && isset( $constraints['uri_type'] ) ) {
139
				$this->expected_uri_type = is_array( $constraints['uri_type'] )
140
					? $constraints['uri_type']
141
					: array( $constraints['uri_type'] );
142
			}
143
		}
144
145
	}
146
147
	/**
148
	 * Return nonce HTML.
149
	 *
150
	 * Overwrite this method in a child class to obtain custom behaviour.
151
	 */
152
	public function html_nonce() {
153
154
		return wp_nonce_field( 'wordlift_' . $this->meta_name . '_entity_box', 'wordlift_' . $this->meta_name . '_entity_box_nonce', true, false );
155
	}
156
157
	/**
158
	 * Verify nonce.
159
	 *
160
	 * Overwrite this method in a child class to obtain custom behaviour.
161
	 *
162
	 * @return boolean Nonce verification.
163
	 */
164
	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...
165
166
		$nonce_name   = 'wordlift_' . $this->meta_name . '_entity_box_nonce';
167
		$nonce_verify = 'wordlift_' . $this->meta_name . '_entity_box';
168
169
		if ( ! isset( $_POST[ $nonce_name ] ) ) {
170
			return false;
171
		}
172
173
		// Verify that the nonce is valid.
174
		return wp_verify_nonce( $_POST[ $nonce_name ], $nonce_verify );
175
	}
176
177
	/**
178
	 * Load data from DB and store the resulting array in $this->data.
179
	 *
180
	 * Overwrite this method in a child class to obtain custom behaviour.
181
	 */
182
	public function get_data() {
183
184
		$data = get_post_meta( get_the_ID(), $this->meta_name );
185
186
		// Values are always contained in an array (makes it easier to manage cardinality).
187
		if ( ! is_array( $data ) ) {
188
			$data = array( $data );
189
		}
190
191
		$this->data = $data;
192
	}
193
194
	/**
195
	 * Sanitizes data before saving to DB. Default sanitization trashes empty
196
	 * values.
197
	 *
198
	 * Stores the sanitized values into $this->data so they can be later processed.
199
	 * Overwrite this method in a child class to obtain custom behaviour.
200
	 *
201
	 * @param array $values Array of values to be sanitized and then stored into
202
	 *                      $this->data.
203
	 */
204
	public function sanitize_data( $values ) {
205
206
		$sanitized_data = array();
207
208
		if ( ! is_array( $values ) ) {
209
			$values = array( $values );
210
		}
211
212
		foreach ( $values as $value ) {
213
			$sanitized_value = $this->sanitize_data_filter( $value );
214
			if ( ! is_null( $sanitized_value ) ) {
215
				$sanitized_data[] = $sanitized_value;
216
			}
217
		}
218
219
		$this->data = $sanitized_data;
220
	}
221
222
	/**
223
	 * Sanitize a single value. Called from $this->sanitize_data. Default
224
	 * sanitization excludes empty values.
225
	 *
226
	 * Overwrite this method in a child class to obtain custom behaviour.
227
	 *
228
	 * @param string $value The value to sanitize.
229
	 *
230
	 * @return mixed Returns sanitized value, or null.
231
	 */
232
	public function sanitize_data_filter( $value ) {
233
234
		// TODO: all fields should provide their own sanitize which shouldn't
235
		// be part of a UI class.
236
237
		// If the field provides its own validation, use it.
238
		if ( isset( $this->raw_custom_field['sanitize'] ) ) {
239
			return call_user_func( $this->raw_custom_field['sanitize'], $value );
240
		}
241
242
		if ( ! is_null( $value ) && '' !== $value ) {         // do not use 'empty()' -> https://www.virendrachandak.com/techtalk/php-isset-vs-empty-vs-is_null/ .
243
			return $value;
244
		}
245
246
		return null;
247
	}
248
249
	/**
250
	 * Save data to DB.
251
	 *
252
	 * Overwrite this method in a child class to obtain custom behaviour.
253
	 *
254
	 * @param array $values Array of values to be sanitized and then stored into $this->data.
255
	 */
256
	public function save_data( $values ) {
257
258
		// Will sanitize data and store them in $field->data.
259
		$this->sanitize_data( $values );
260
261
		$entity_id = get_the_ID();
262
263
		// Take away old values.
264
		delete_post_meta( $entity_id, $this->meta_name );
265
266
		// insert new values, respecting cardinality.
267
		$single = ( 1 === $this->cardinality );
268
		foreach ( $this->data as $value ) {
269
			add_post_meta( $entity_id, $this->meta_name, $value, $single );
270
		}
271
	}
272
273
	/**
274
	 * Returns the HTML tag that will contain the Field. By default the we
275
	 * return a <div> with data- attributes on cardinality and expected types.
276
	 *
277
	 * It is useful to provide data- attributes for the JS scripts.
278
	 *
279
	 * Overwrite this method in a child class to obtain custom behaviour.
280
	 */
281
	public function html_wrapper_open() {
282
283
		return "<div class='wl-field' data-cardinality='$this->cardinality'>";
284
	}
285
286
	/**
287
	 * Returns Field HTML (nonce included).
288
	 *
289
	 * Overwrite this method (or methods called from this method) in a child
290
	 * class to obtain custom behaviour.
291
	 *
292
	 * The HTML fragment includes the following parts:
293
	 * * html wrapper open.
294
	 * * heading.
295
	 * * nonce.
296
	 * * stored values.
297
	 * * an empty input when there are no stored values.
298
	 * * an add button to add more values.
299
	 * * html wrapper close.
300
	 */
301
	public function html() {
302
303
		// Open main <div> for the Field.
304
		$html = $this->html_wrapper_open();
305
306
		// Label.
307
		$html .= $this->get_heading_html();
308
309
		// print nonce.
310
		$html .= $this->html_nonce();
311
312
		// print data loaded from DB.
313
		$count = 0;
314
		$html  .= $this->get_stored_values_html( $count );
315
316
		// Print the empty <input> to add new values.
317
		if ( 0 === $count ) { // } || $count < $this->cardinality ) { DO NOT print empty inputs unless requested by the editor since fields might support empty strings.
318
			$html .= $this->html_input( '' );    // Will print an empty <input>.
319
			$count ++;
320
		}
321
322
		// If cardinality allows it, print button to add new values.
323
		$html .= $this->get_add_button_html( $count );
324
325
		// Close the HTML wrapper.
326
		$html .= $this->html_wrapper_close();
327
328
		return $html;
329
	}
330
331
	/**
332
	 * Print the heading with the label for the metabox.
333
	 *
334
	 * @since 3.15.0
335
	 * @return string The heading html fragment.
336
	 */
337
	protected function get_heading_html() {
338
339
		return "<h3>$this->label</h3>";
340
	}
341
342
	/**
343
	 * Print the stored values.
344
	 *
345
	 * @since 3.15.0
346
	 *
347
	 * @param int $count An output value: the number of printed values.
348
	 *
349
	 * @return string The html fragment.
350
	 */
351
	protected function get_stored_values_html( &$count ) {
352
353
		$html = '';
354
355
		// print data loaded from DB.
356
		$count = 0;
357
		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...
358
			foreach ( $this->data as $value ) {
359
				if ( $count < $this->cardinality ) {
360
					$html .= $this->html_input( $value );
361
				}
362
				$count ++;
363
			}
364
		}
365
366
		return $html;
367
	}
368
369
	/**
370
	 * Get the add button html.
371
	 *
372
	 * This function is protected, allowing extending class to further customize
373
	 * the add button html code.
374
	 *
375
	 * @since 3.15.0
376
	 *
377
	 * @param int $count The current number of values.
378
	 *
379
	 * @return string The add button html code.
380
	 */
381
	protected function get_add_button_html( $count ) {
382
383
		// If cardinality allows it, print button to add new values.
384
		if ( $count < $this->cardinality ) {
385
			return '<button class="button wl-add-input wl-button" type="button">' . esc_html__( 'Add' ) . '</button>';
386
		}
387
388
		// Return an empty string.
389
		return '';
390
	}
391
392
	/**
393
	 * Return a single <input> tag for the Field.
394
	 *
395
	 * @param mixed $value Input value.
396
	 *
397
	 * @return string The html code fragment.
398
	 */
399
	public function html_input( $value ) {
400
		$html = <<<EOF
401
			<div class="wl-input-wrapper">
402
				<input type="text" id="$this->meta_name" name="wl_metaboxes[$this->meta_name][]" value="$value" style="width:88%" />
403
				<button class="button wl-remove-input wl-button" type="button">Remove</button>
404
			</div>
405
EOF;
406
407
		return $html;
408
	}
409
410
	/**
411
	 * Returns closing for the wrapper HTML tag.
412
	 */
413
	public function html_wrapper_close() {
414
415
		return '</div><hr>';
416
	}
417
418
}
419