Completed
Pull Request — master (#193)
by
unknown
02:22
created

Helper::prepare_meta_name()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Carbon_Fields\Helper;
4
5
use Carbon_Fields\Datastore\Datastore;
6
use Carbon_Fields\Container\Container;
7
use Carbon_Fields\Container\Container_Validator;
8
use Carbon_Fields\REST\Data_Manager;
9
use Carbon_Fields\REST\Routes;
10
use Carbon_Fields\REST\Decorator;
11
use Carbon_Fields\Templater\Templater;
12
use Carbon_Fields\Libraries\Sidebar_Manager\Sidebar_Manager;
13
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
14
15
/**
16
 * Helper functions and main initialization class.
17
 */
18
class Helper {
19
20
	/**
21
	 * Create a new helper.
22
	 * Hook the main Carbon Fields initialization functionality.
23
	 */
24
	public function __construct() {
25
		# Initialize sidebar manager
26
		Sidebar_Manager::instance();
27
		
28
		add_action( 'init', array( $this, 'trigger_fields_register' ), 0 );
29
		add_action( 'carbon_after_register_fields', array( $this, 'init_containers' ) );
30
		add_action( 'carbon_after_register_fields', array( $this, 'init_rest_routes' ) );
31
		add_action( 'admin_footer', array( $this, 'init_scripts' ), 0 );
32
		add_action( 'admin_print_footer_scripts', array( $this, 'print_json_data_script' ), 9 );
33
		add_action( 'crb_field_activated', array( $this, 'add_templates' ) );
34
		add_action( 'crb_container_activated', array( $this, 'add_templates' ) );
35
		add_action( 'after_setup_theme', array( $this, 'load_textdomain' ), 9999 );
36
		add_action( 'admin_init', array( $this, 'trigger_containers_attach' ), 0 );
37
		add_action( 'carbon_trigger_containers_attach', array( $this, 'trigger_containers_attach' ), 0 );
38
39
		# Initialize templater
40
		new Templater();
41
	}
42
43
	/**
44
	 * Load the plugin textdomain.
45
	 */
46
	public function load_textdomain() {
47
		$dir = dirname( dirname( __DIR__ ) ) . '/languages/';
48
		$domain = 'carbon-fields';
49
		$locale = get_locale();
50
		$path = $dir . $domain . '-' . $locale . '.mo';
51
		load_textdomain( $domain, $path );
52
	}
53
54
	/**
55
	 * Register containers and fields.
56
	 */
57
	public function trigger_fields_register() {
58
		try {
59
			do_action( 'carbon_register_fields' );
60
			do_action( 'carbon_after_register_fields' );
61
		} catch ( Incorrect_Syntax_Exception $e ) {
62
			$callback = '';
63
			foreach ( $e->getTrace() as $trace ) {
64
				$callback .= '<br/>' . ( isset( $trace['file'] ) ? $trace['file'] . ':' . $trace['line'] : $trace['function'] . '()' );
65
			}
66
			wp_die( '<h3>' . $e->getMessage() . '</h3><small>' . $callback . '</small>' );
67
		}
68
	}
69
70
	/**
71
	 * Attach appropriate containers
72
	 * 
73
	 */
74
	public function trigger_containers_attach() {
75
		if ( is_admin() ) {
76
			do_action( 'carbon_containers_attach' );
77
		} else {
78
			$this->init_containers();
79
			do_action( 'carbon_containers_attach_all' );
80
		}
81
	}
82
83
	/**
84
	 * Initialize containers.
85
	 */
86
	public function init_containers() {
87
		Container::init_containers();
88
	}
89
	
90
	/**
91
	 * Initialize REST routes
92
	 */
93
	public function init_rest_routes() {
94
		$validator = new Container_Validator();
95
		$manager   = new Data_Manager( $validator );
96
97
		new Routes( $manager );
98
		new Decorator( $manager );
99
	}
100
101
	/**
102
	 * Initialize main scripts
103
	 */
104
	public function init_scripts() {
105
		wp_enqueue_script( 'carbon-ext', \Carbon_Fields\URL . '/assets/js/ext.js', array( 'jquery' ) );
106
		wp_enqueue_script( 'carbon-app', \Carbon_Fields\URL . '/assets/js/app.js', array( 'jquery', 'backbone', 'underscore', 'jquery-touch-punch', 'jquery-ui-sortable', 'carbon-ext' ) );
107
	}
108
109
	/**
110
	 * Print the carbon JSON data script.
111
	 */
112
	public function print_json_data_script() {
113
		?>
114
<script type="text/javascript">
115
<!--//--><![CDATA[//><!--
116
var carbon_json = <?php echo wp_json_encode( $this->get_json_data() ); ?>;
117
//--><!]]>
118
</script>
119
		<?php
120
	}
121
122
	/**
123
	 * Retrieve containers and sidebars for use in the JS.
124
	 *
125
	 * @return array $carbon_data
126
	 */
127
	public function get_json_data() {
128
		global $wp_registered_sidebars;
1 ignored issue
show
Comprehensibility Naming introduced by
The variable name $wp_registered_sidebars exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
129
130
		$carbon_data = array(
131
			'containers' => array(),
132
			'sidebars' => array(),
133
		);
134
135
		$containers = Container::get_active_containers();
136
137
		foreach ( $containers as $container ) {
138
			$container_data = $container->to_json( true );
139
140
			$carbon_data['containers'][] = $container_data;
141
		}
142
143
		foreach ( $wp_registered_sidebars as $sidebar ) {
144
			// Check if we have inactive sidebars
145
			if ( isset( $sidebar['class'] ) && strpos( $sidebar['class'], 'inactive-sidebar' ) !== false ) {
146
				continue;
147
			}
148
149
			$carbon_data['sidebars'][] = array(
150
				'name' => $sidebar['name'],
151
				'id'   => $sidebar['id'],
152
			);
153
		}
154
155
		return $carbon_data;
156
	}
157
158
	/**
159
	 * Retrieve post meta field for a post.
160
	 *
161
	 * @param  int    $id   Post ID.
162
	 * @param  string $name Custom field name.
163
	 * @param  string $type Custom field type (optional).
164
	 * @return mixed        Meta value.
165
	 */
166
	public static function get_post_meta( $id, $name, $type = null ) {
167
		$name = self::prepare_meta_name( $name );
168
169
		return self::get_field_value( 'post_meta', $name, $type, $id );
170
	}
171
172
	/**
173
	 * Shorthand for get_post_meta().
174
	 * Uses the ID of the current post in the loop.
175
	 *
176
	 * @param  string $name Custom field name.
177
	 * @param  string $type Custom field type (optional).
178
	 * @return mixed        Meta value.
179
	 */
180
	public static function get_the_post_meta( $name, $type = null ) {
181
		return self::get_post_meta( get_the_ID(), $name, $type );
182
	}
183
184
	/**
185
	 * Retrieve theme option field value.
186
	 *
187
	 * @param  string $name Custom field name.
188
	 * @param  string $type Custom field type (optional).
189
	 * @return mixed        Option value.
190
	 */
191
	public static function get_theme_option( $name, $type = null ) {
192
		return self::get_field_value( 'theme_options', $name, $type );
193
	}
194
195
	/**
196
	 * Retrieve term meta field for a term.
197
	 *
198
	 * @param  int    $id   Term ID.
199
	 * @param  string $name Custom field name.
200
	 * @param  string $type Custom field type (optional).
201
	 * @return mixed        Meta value.
202
	 */
203
	public static function get_term_meta( $id, $name, $type = null ) {
204
		$name = self::prepare_meta_name( $name );
205
206
		return self::get_field_value( 'term_meta', $name, $type, $id );
207
	}
208
209
	/**
210
	 * Retrieve user meta field for a user.
211
	 *
212
	 * @param  int    $id   User ID.
213
	 * @param  string $name Custom field name.
214
	 * @param  string $type Custom field type (optional).
215
	 * @return mixed        Meta value.
216
	 */
217
	public static function get_user_meta( $id, $name, $type = null ) {
218
		$name = self::prepare_meta_name( $name );
219
220
		return self::get_field_value( 'user_meta', $name, $type, $id );
221
	}
222
223
	/**
224
	 * Retrieve comment meta field for a comment.
225
	 *
226
	 * @param  int    $id   Comment ID.
227
	 * @param  string $name Custom field name.
228
	 * @param  string $type Custom field type (optional).
229
	 * @return mixed        Meta value.
230
	 */
231
	public static function get_comment_meta( $id, $name, $type = null ) {
232
		$name = self::prepare_meta_name( $name );
233
234
		return self::get_field_value( 'comment_meta', $name, $type, $id );
235
	}
236
237
	/**
238
	 * Add underscore to the name, if missing
239
	 * 
240
	 * @param  string $name Field Name
241
	 * @return string Field Name
242
	 */
243
	public static function prepare_meta_name( $name ) {
244
		return $name = $name[0] === '_' ? $name : '_' . $name;		
0 ignored issues
show
Unused Code introduced by
$name is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
245
	}
246
247
	/**
248
	 * Prepare the data type name
249
	 * 
250
	 * @param  string $data_type 
251
	 * @return string
252
	 */
253
	public static function prepare_data_type_name( $data_type ) {
254
		return str_replace( ' ', '_', ucwords( str_replace( '_', ' ', $data_type ) ) );
255
	}
256
257
	/**
258
	 * Retrieve a certain field value from the database.
259
	 * Handles the logic for different field types.
260
	 *
261
	 * @param  string $data_type Data type.
262
	 * @param  string $name      Custom field name.
263
	 * @param  string $type      Custom field type (optional).
264
	 * @param  int    $id        ID (optional).
265
	 * @return mixed             Meta value.
266
	 */
267
	public static function get_field_value( $data_type, $name, $type = null, $id = null ) {
268
		$datastore_name = self::prepare_data_type_name( $data_type );
269
270
		switch ( $type ) {
271
			case 'complex':
272
				$value = self::get_complex_fields( $datastore_name, $name, $id );
273
			break;
274
275
			case 'map':
276
			case 'map_with_address':
277
				$value = array(
278
					'lat' => (float) self::get_field_value_by_store( $data_type, $name . '-lat', $id ),
279
					'lng' => (float) self::get_field_value_by_store( $data_type, $name . '-lng', $id ),
280
					'address' => self::get_field_value_by_store( $data_type, $name . '-address', $id ),
281
					'zoom' => (int) self::get_field_value_by_store( $data_type, $name . '-zoom', $id ),
282
				);
283
284
				if ( ! array_filter( $value ) ) {
285
					$value = array();
286
				}
287
			break;
288
289
			case 'association':
290
				$raw_value = self::get_field_value_by_store( $data_type, $name, $id );
291
				$value = self::parse_relationship_field( $raw_value, $type );
292
			break;
293
294
			default:
295
				$value = self::get_field_value_by_store( $data_type, $name, $id );
296
297
				// backward compatibility for the old Relationship field
298
				$value = self::maybe_old_relationship_field( $value );
299
		}
300
301
		return $value;
302
	}
303
304
	/**
305
	 * Retrieve a certain field value from the database.
306
	 * Handles the logic for different data stores (containers).
307
	 *
308
	 * @param  string $store_type Data store type.
309
	 * @param  string $name       Custom field name.
310
	 * @param  int    $id         ID (optional).
311
	 * @return mixed              Meta value.
312
	 */
313
	public static function get_field_value_by_store( $store_type, $name, $id = null ) {
314
		$args = array( $id, $name, true );
315
		$function = '';
316
317
		switch ( $store_type ) {
318
			case 'post_meta':
319
				$function = 'get_post_meta';
320
			break;
321
322
			case 'user_meta':
323
				$function = 'get_user_meta';
324
			break;
325
326
			case 'comment_meta':
327
				$function = 'get_comment_meta';
328
			break;
329
330
			case 'term_meta':
331
				$function = 'get_metadata';
332
				$args = array( 'term', $id, $name, true );
333
			break;
334
335
			case 'theme_options':
336
				$function = 'get_option';
337
				$args = array( $name );
338
			break;
339
		}
340
341
		if ( ! empty( $function ) && function_exists( $function ) ) {
342
			return call_user_func_array( $function, $args );
343
		}
344
345
		return false;
346
	}
347
348
	/**
349
	 * Adds the field/container template(s) to the templates stack.
350
	 *
351
	 * @param object $object field or container object
352
	 **/
353
	public function add_templates( $object ) {
354
		$templates = $object->get_templates();
355
356
		if ( ! $templates ) {
357
			return false;
358
		}
359
360
		foreach ( $templates as $name => $callback ) {
361
			ob_start();
362
363
			call_user_func( $callback );
364
365
			$html = ob_get_clean();
366
367
			// Add the template to the stack
368
			Templater::add_template( $name, $html );
369
		}
370
	}
371
372
	/**
373
	 * Build a string of concatenated pieces for an OR regex.
374
	 *
375
	 * @param  array  $pieces Pieces
376
	 * @param  string $glue   Glue between the pieces
377
	 * @return string         Result string
378
	 */
379
	public static function preg_quote_array( $pieces, $glue = '|' ) {
380
		$pieces = array_map( 'preg_quote', $pieces, array( '~' ) );
381
382
		return implode( $glue, $pieces );
383
	}
384
385
	/**
386
	 * Build the regex for parsing a certain complex field.
387
	 *
388
	 * @param  string $field_name  Name of the complex field.
389
	 * @param  array  $group_names Array of group names.
390
	 * @param  array  $field_names Array of subfield names.
391
	 * @return string              Regex
392
	 */
393
	public static function get_complex_field_regex( $field_name, $group_names = array(), $field_names = array() ) {
394
		if ( ! empty( $group_names ) ) {
395
			$group_regex = self::preg_quote_array( $group_names );
396
		} else {
397
			$group_regex = '\w*';
398
		}
399
400
		if ( ! empty( $field_names ) ) {
401
			$field_regex = self::preg_quote_array( $field_names );
402
		} else {
403
			$field_regex = '.*?';
404
		}
405
406
		return '~^' . preg_quote( $field_name, '~' ) . '(?P<group>' . $group_regex . ')-_?(?P<key>' . $field_regex . ')_(?P<index>\d+)_?(?P<sub>\w+)?(-(?P<trailing>.*))?$~';
407
	}
408
409
	/**
410
	 * Retrieve the complex field data for a certain field.
411
	 *
412
	 * @param  string $type Datastore type.
413
	 * @param  string $name Name of the field.
414
	 * @param  int    $id   ID of the entry (optional).
415
	 * @return array        Complex data entries.
416
	 */
417
	public static function get_complex_fields( $type, $name, $id = null ) {
418
		$datastore = Datastore::factory( $type );
419
420
		if ( $id !== null ) {
421
			$datastore->set_id( $id );
422
		}
423
424
		$group_rows = $datastore->load_values( $name );
425
		$input_groups = array();
426
427
		foreach ( $group_rows as $row ) {
428
			if ( ! preg_match( self::get_complex_field_regex( $name ), $row['field_key'], $field_name ) ) {
429
					continue;
430
			}
431
432
			$row['field_value'] = maybe_unserialize( $row['field_value'] );
433
434
			// backward compatibility for Relationship field
435
			$row['field_value'] = self::parse_relationship_field( $row['field_value'] );
436
437
			$input_groups[ $field_name['index'] ]['_type'] = $field_name['group'];
438
			if ( ! empty( $field_name['trailing'] ) ) {
439
				$input_groups = self::expand_nested_field( $input_groups, $row, $field_name );
440 View Code Duplication
			} else if ( ! empty( $field_name['sub'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
441
				$input_groups[ $field_name['index'] ][ $field_name['key'] ][ $field_name['sub'] ] = $row['field_value'];
442
			} else {
443
				$input_groups[ $field_name['index'] ][ $field_name['key'] ] = $row['field_value'];
444
			}
445
		}
446
447
		// create groups list with loaded fields
448
		self::ksort_recursive( $input_groups );
449
450
		return $input_groups;
451
	}
452
453
	/**
454
	 * Recursively expand the subfields of a complex field.
455
	 *
456
	 * @param  array $input_groups Input groups.
457
	 * @param  array $row          Data row (key and value).
458
	 * @param  array $field_name   Field name pieces.
459
	 * @return array               Expanded data.
460
	 */
461
	public static function expand_nested_field( $input_groups, $row, $field_name ) {
462
		$subfield_key_token = $field_name['key'] . '_' . $field_name['sub'] . '-' . $field_name['trailing'];
463
		if ( ! preg_match( self::get_complex_field_regex( $field_name['key'] ), $subfield_key_token, $subfield_name ) ) {
464
			return $input_groups;
465
		}
466
467
		$input_groups[ $field_name['index'] ][ $field_name['key'] ][ $subfield_name['index'] ]['_type'] = $subfield_name['group'];
468
469
		if ( ! empty( $subfield_name['trailing'] ) ) {
470
			$input_groups[ $field_name['index'] ][ $field_name['key'] ] = self::expand_nested_field( $input_groups[ $field_name['index'] ][ $field_name['key'] ], $row, $subfield_name );
471 View Code Duplication
		} else if ( ! empty( $subfield_name['sub'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
472
			$input_groups[ $field_name['index'] ][ $field_name['key'] ][ $subfield_name['index'] ][ $subfield_name['key'] ][ $subfield_name['sub'] ] = $row['field_value'];
473
		} else {
474
			$input_groups[ $field_name['index'] ][ $field_name['key'] ][ $subfield_name['index'] ][ $subfield_name['key'] ] = $row['field_value'];
475
		}
476
477
		return $input_groups;
478
	}
479
480
	/**
481
	 * Parse the raw value of the relationship and association fields.
482
	 *
483
	 * @param  string $raw_value Raw relationship value.
484
	 * @param  string $type      Field type.
485
	 * @return array             Array of parsed data.
486
	 */
487
	public static function parse_relationship_field( $raw_value = '', $type = '' ) {
488
		if ( $raw_value && is_array( $raw_value ) ) {
489
			$value = array();
490
			foreach ( $raw_value as $raw_value_item ) {
491
				if ( is_string( $raw_value_item ) && strpos( $raw_value_item, ':' ) !== false ) {
492
					$item_data = explode( ':', $raw_value_item );
493
					$item = array(
494
						'id' => $item_data[2],
495
						'type' => $item_data[0],
496
					);
497
498
					if ( $item_data[0] === 'post' ) {
499
						$item['post_type'] = $item_data[1];
500
					} elseif ( $item_data[0] === 'term' ) {
501
						$item['taxonomy'] = $item_data[1];
502
					}
503
504
					$value[] = $item;
505
				} elseif ( $type === 'association' ) {
506
					$value[] = array(
507
						'id' => $raw_value_item,
508
						'type' => 'post',
509
						'post_type' => get_post_type( $raw_value_item ),
510
					);
511
				} else {
512
					$value[] = $raw_value_item;
513
				}
514
			}
515
516
			$raw_value = $value;
517
		}
518
519
		return $raw_value;
520
	}
521
522
	/**
523
	 * Detect if using the old way of storing the relationship field values.
524
	 * If so, parse them to the new way of storing the data.
525
	 *
526
	 * @param  mixed $value Old field value.
527
	 * @return mixed        New field value.
528
	 */
529
	public static function maybe_old_relationship_field( $value ) {
530
		if ( is_array( $value ) && ! empty( $value ) && ! empty( $value[0] ) ) {
531
			if ( preg_match( '~^\w+:\w+:\d+$~', $value[0] ) ) {
532
				$new_value = array();
533
				foreach ( $value as $value_entry ) {
534
					$pieces = explode( ':', $value_entry );
535
					$new_value[] = $pieces[2];
536
				}
537
				$value = $new_value;
538
			}
539
		}
540
541
		return $value;
542
	}
543
544
	/**
545
	 * Recursive sorting function by array key.
546
	 * @param  array  &$array     The input array.
547
	 * @param  int    $sort_flags Flags for controlling sorting behavior.
548
	 * @return array              Sorted array.
549
	 */
550
	public static function ksort_recursive( &$array, $sort_flags = SORT_REGULAR ) {
551
		if ( ! is_array( $array ) ) {
552
			return false;
553
		}
554
555
		ksort( $array, $sort_flags );
556
		foreach ( $array as $key => $value ) {
557
			self::ksort_recursive( $array[ $key ], $sort_flags );
558
		}
559
560
		return true;
561
	}
562
}
563