Completed
Push — develop ( dd2c91...e970a6 )
by David
02:48
created

WL_Metabox_Field::get_add_button_html()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 10
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
	 * 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
	public function html() {
293
294
		// Open main <div> for the Field.
295
		$html = $this->html_wrapper_open();
296
297
		// Label.
298
		$html .= "<h3>$this->label</h3>";
299
300
		// print nonce.
301
		$html .= $this->html_nonce();
302
303
		// print data loaded from DB.
304
		$count = 0;
305
		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...
306
			foreach ( $this->data as $value ) {
307
				if ( $count < $this->cardinality ) {
308
					$html .= $this->html_input( $value );
309
				}
310
				$count ++;
311
			}
312
		}
313
314
		// Print the empty <input> to add new values.
315
		if ( 0 === $count ) { // } || $count < $this->cardinality ) { DO NOT print empty inputs unless requested by the editor since fields might support empty strings.
316
			$html .= $this->html_input( '' );    // Will print an empty <input>.
317
			$count ++;
318
		}
319
320
		// If cardinality allows it, print button to add new values.
321
		$html .= $this->get_add_button_html( $count );
322
323
		// Close the HTML wrapper.
324
		$html .= $this->html_wrapper_close();
325
326
		return $html;
327
	}
328
329
	/**
330
	 * Get the add button html.
331
	 *
332
	 * This function is protected, allowing extending class to further customize
333
	 * the add button html code.
334
	 *
335
	 * @since 3.15.0
336
	 *
337
	 * @param int $count The current number of values.
338
	 *
339
	 * @return string The add button html code.
340
	 */
341
	protected function get_add_button_html( $count ) {
342
343
		// If cardinality allows it, print button to add new values.
344
		if ( $count < $this->cardinality ) {
345
			return '<button class="button wl-add-input wl-button" type="button">' . esc_html__( 'Add' ) . '</button>';
346
		}
347
348
		// Return an empty string.
349
		return '';
350
	}
351
352
	/**
353
	 * Return a single <input> tag for the Field.
354
	 *
355
	 * @param mixed $value Input value.
356
	 *
357
	 * @return string The html code fragment.
358
	 */
359
	public function html_input( $value ) {
360
		$html = <<<EOF
361
			<div class="wl-input-wrapper">
362
				<input type="text" id="$this->meta_name" name="wl_metaboxes[$this->meta_name][]" value="$value" style="width:88%" />
363
				<button class="button wl-remove-input wl-button" type="button">Remove</button>
364
			</div>
365
EOF;
366
367
		return $html;
368
	}
369
370
	/**
371
	 * Returns closing for the wrapper HTML tag.
372
	 */
373
	public function html_wrapper_close() {
374
375
		return '</div><hr>';
376
	}
377
378
}
379