Completed
Pull Request — master (#1047)
by David
02:34
created

Jsonld_Converter::process_nested_properties()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 6
nop 4
dl 0
loc 35
rs 8.7377
c 0
b 0
f 0
1
<?php
2
/**
3
 * Define the Wordlift_Mapping_Jsonld_Converter class to add JSON-LD generated from mappings.
4
 *
5
 * @since   3.25.0
6
 * @package Wordlift
7
 * @subpackage Wordlift\Mappings
8
 */
9
10
namespace Wordlift\Mappings;
11
12
/**
13
 * This class takes the output from json-ld service and alter depends on the
14
 * rule and properties defined in sync mappings.
15
 *
16
 * @since 3.25.0
17
 */
18
class Jsonld_Converter {
19
	/**
20
	 * Enumerations for the field types.
21
	 * Enumerations for the field types.
22
	 */
23
	const FIELD_TYPE_TEXT_FIELD = 'text';
24
	const FIELD_TYPE_CUSTOM_FIELD = 'custom_field';
25
	const FIELD_TYPE_ACF = 'acf';
26
	/**
27
	 * The {@link Mappings_Validator} instance to test.
28
	 *
29
	 * @since  3.25.0
30
	 * @access private
31
	 * @var Mappings_Validator $validator The {@link Mappings_Validator} instance.
32
	 */
33
	private $validator;
34
35
	/**
36
	 * The {@link Mappings_Transform_Functions_Registry} instance.
37
	 *
38
	 * @since  3.25.0
39
	 * @access private
40
	 * @var Mappings_Transform_Functions_Registry $transform_functions_registry The {@link Mappings_Transform_Functions_Registry} instance.
41
	 */
42
	private $transform_functions_registry;
43
44
	/**
45
	 * Initialize all dependencies required.
46
	 *
47
	 * @param Mappings_Validator $validator A {@link Mappings_Validator} instance.
48
	 * @param Mappings_Transform_Functions_Registry $transform_functions_registry
49
	 */
50
	public function __construct( $validator, $transform_functions_registry ) {
51
52
		$this->validator                    = $validator;
53
		$this->transform_functions_registry = $transform_functions_registry;
54
55
		// Hook to refactor the JSON-LD.
56
		add_filter( 'wl_post_jsonld_array', array( $this, 'wl_post_jsonld_array' ), 11, 2 );
57
		add_filter( 'wl_entity_jsonld_array', array( $this, 'wl_post_jsonld_array' ), 11, 3 );
58
59
	}
60
61
	/**
62
	 * Hook to `wl_post_jsonld_array` and `wl_entity_jsonld_array`.
63
	 *
64
	 * Receive the JSON-LD and the references in the array along with the post ID and transform them according to
65
	 * the configuration.
66
	 *
67
	 * @param array $value {
68
	 *      The array containing the JSON-LD and the references.
69
	 *
70
	 * @type array $jsonld The JSON-LD array.
71
	 * @type int[] $references An array of post ID referenced by the JSON-LD (will be expanded by the converter).
72
	 * }
73
	 *
74
	 * @param int $post_id The post ID.
75
	 *
76
	 * @return array An array with the updated JSON-LD and references.
77
	 */
78
	public function wl_post_jsonld_array( $value, $post_id ) {
79
80
		$jsonld     = $value['jsonld'];
81
		$references = $value['references'];
82
83
		return array(
84
			'jsonld'     => $this->wl_post_jsonld( $jsonld, $post_id, $references ),
85
			'references' => $references,
86
		);
87
	}
88
89
	/**
90
	 * Returns JSON-LD data after applying transformation functions.
91
	 *
92
	 * @param array $jsonld The JSON-LD structure.
93
	 * @param int $post_id The {@link WP_Post} id.
94
	 * @param array $references An array of post references.
95
	 *
96
	 * @return array the new refactored array structure.
97
	 * @since 3.25.0
98
	 */
99
	private function wl_post_jsonld( $jsonld, $post_id, &$references ) {
100
101
		// @@todo I think there's an issue here with the Validator, because you're changing the instance state and the
102
		// instance may be reused afterwards.
103
104
		$properties        = $this->validator->validate( $post_id );
105
		$nested_properties = array();
106
107
		foreach ( $properties as $property ) {
108
			// If the property has the character '/' in the property name then it is a nested property.
109
			if ( strpos( $property['property_name'], '/' ) !== false ) {
110
				$nested_properties[] = $property;
111
				continue;
112
			}
113
			$property_transformed_data = $this->get_property_data( $property, $jsonld, $post_id, $references );
114
			if ( false !== $property_transformed_data ) {
115
				$jsonld[ $property['property_name'] ] = $property_transformed_data;
116
			}
117
		}
118
119
		$jsonld = $this->process_nested_properties( $nested_properties, $jsonld, $post_id, $references );
120
121
		return $jsonld;
122
	}
123
124
	/**
125
	 * Get the property data by applying the transformation function
126
	 *
127
	 * @param $property
128
	 * @param $jsonld
129
	 * @param $post_id
130
	 * @param $references
131
	 *
132
	 * @return array|bool|null
133
	 */
134
	public function get_property_data( $property, $jsonld, $post_id, &$references ) {
135
		$transform_instance = $this->transform_functions_registry->get_transform_function( $property['transform_function'] );
136
		$data               = $this->get_data_from_data_source( $post_id, $property );
137
		if ( null !== $transform_instance ) {
138
			$transform_data = $transform_instance->transform_data( $data, $jsonld, $references, $post_id );
139
			if ( null !== $transform_data ) {
140
				return $this->make_single( $transform_data );
141
			}
142
		} else {
143
			return $this->make_single( $data );
144
		}
145
146
		return false;
147
	}
148
149
	/**
150
	 * Process all the nested properties.
151
	 *
152
	 * @param $nested_properties array
153
	 * @param $jsonld array
154
	 *
155
	 * @return array
156
	 */
157
	public function process_nested_properties( $nested_properties, $jsonld, $post_id, &$references ) {
158
		foreach ( $nested_properties as $property ) {
159
			$property_data = $this->get_property_data( $property, $jsonld, $post_id, $references );
160
			if ( false === $property_data ) {
161
				// No need to create nested levels.
162
				continue;
163
			}
164
165
			$keys = explode( '/', $property['property_name'] );
166
			// end is the last level of the nested property.
167
			$end                      = array_pop( $keys );
168
			$current_property_pointer = &$jsonld;
169
170
			/**
171
			 * Once we find all the nested levels from the property name
172
			 * loop through it and create associative array if the levels
173
			 * didnt exist.
174
			 */
175
			while ( count( $keys ) > 0 ) {
176
				$key = array_shift( $keys );
177
				if ( $key === "" ) {
178
					continue;
179
				}
180
				if ( ! array_key_exists( $key, $current_property_pointer ) ) {
181
					$current_property_pointer[ $key ] = array();
182
				}
183
				// We are setting the pointer to the current key, so that at the end
184
				// we can add the data at last level.
185
				$current_property_pointer = &$current_property_pointer[ $key ];
186
			}
187
			$current_property_pointer[ $end ] = $property_data;
188
		}
189
190
		return $jsonld;
191
	}
192
193
194
	/**
195
	 * Returns data from data source.
196
	 *
197
	 * @param int $post_id Id of the post.
198
	 * @param array $property_data The property data for the post_id.
199
	 *
200
	 * @return array Returns key, value array, if the value is not found, then it returns null.
201
	 */
202
	final public function get_data_from_data_source( $post_id, $property_data ) {
203
		$value = $property_data['field_name'];
204
205
		// Do 1 to 1 mapping and return result.
206
		switch ( $property_data['field_type'] ) {
207
			case self::FIELD_TYPE_ACF:
208
				if ( ! function_exists( 'get_field' ) || ! function_exists( 'get_field_object' ) ) {
209
					return array();
210
				}
211
212
				return $this->get_data_for_acf_field( $property_data['field_name'], $post_id );
213
214
			case self::FIELD_TYPE_CUSTOM_FIELD:
215
216
				return array_map( 'wp_strip_all_tags', get_post_meta( $post_id, $value ) );
217
218
			default:
219
				return $value;
220
		}
221
222
	}
223
224
	/**
225
	 * Gets data from acf, format the data if it is a repeater field.
226
	 *
227
	 * @param $field_name
228
	 * @param $post_id
229
	 *
230
	 * @return array|mixed
231
	 */
232
	public function get_data_for_acf_field( $field_name, $post_id ) {
233
		$field_data = get_field_object( $field_name, $post_id );
234
		$data       = get_field( $field_name, $post_id );
235
236
		// only process if it is a repeater field, else return the data.
237
		if ( is_array( $field_data ) && array_key_exists( 'type', $field_data )
238
		     && $field_data['type'] === 'repeater' ) {
239
			// check if we have only one sub field, currently we only support one subfield.
240
			if ( is_array( $data ) && count( $data ) > 0 && count( array_keys( $data[0] ) === 1 ) ) {
241
				$repeater_formatted_data = array();
242
				foreach ( $data as $item ) {
243
					$repeater_formatted_data = array_merge( $repeater_formatted_data, array_values( $item ) );
244
				}
245
				// Remove non unique values.
246
				$repeater_formatted_data = array_unique( $repeater_formatted_data );
247
				// Remove empty values
248
				$repeater_formatted_data = array_filter( $repeater_formatted_data, 'strlen' );
249
250
				// re-index all the values.
251
				return array_values( $repeater_formatted_data );
252
			}
253
		}
254
255
		// Return normal acf data if it is not a repeater field.
256
		return $data;
257
	}
258
259
	private function make_single( $value ) {
260
261
		$values = (array) $value;
262
263
		if ( empty( $values ) ) {
264
			return null;
265
		}
266
267
		if ( 1 === count( $values ) && 0 === key( $values ) ) {
268
			return current( $values );
269
		}
270
271
		return $values;
272
	}
273
274
}
275