Completed
Push — master ( f3fec3...c7133e )
by Nazar
04:51
created

Data_model_processing::is_multilingual()   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
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 6
rs 10
1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2013-2016, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs\CRUD;
9
use
10
	cs\Event,
11
	cs\Language,
12
	cs\Text;
13
14
/**
15
 * @property array  $data_model
16
 * @property string $data_model_ml_group
17
 * @property string $data_model_files_tag_prefix
18
 * @property string $table
19
 */
20
trait Data_model_processing {
21
	/**
22
	 * @return int
23
	 */
24
	abstract protected function cdb ();
25
	/**
26
	 * @param callable[]|string[] $data_model
27
	 * @param array               $arguments
28
	 *
29
	 * @return array
30
	 */
31
	private function fix_arguments_order ($data_model, $arguments) {
32
		if (is_array_indexed($arguments)) {
33
			return $arguments;
34
		}
35
		$arguments_ = [];
36
		foreach (array_keys($data_model) as $argument) {
37
			if (array_key_exists($argument, $arguments)) {
38
				$arguments_[] = $arguments[$argument];
39
			}
40
		}
41
		return $arguments_;
42
	}
43
	/**
44
	 * @param callable[]|string[] $data_model
45
	 * @param array               $arguments
46
	 * @param false|int           $id            On update id should be specified to work properly with multilingual fields
47
	 * @param bool                $update_needed If on creation request without specified primary key and multilingual fields present - update needed
48
	 *                                           after creation (there is no id before creation)
49
	 *
50
	 * @return array[]
51
	 */
52
	private function crud_arguments_preparation ($data_model, $arguments, $id = false, &$update_needed = false) {
53
		$arguments     = array_combine(array_keys($data_model), $arguments);
54
		$joined_tables = [];
55
		foreach ($arguments as $item => &$argument) {
56
			$model = $data_model[$item];
57
			if (is_callable($model)) {
58
				$argument = $model($argument);
59
				continue;
60
			}
61
			if (isset($model['data_model'])) {
62
				$joined_tables[$item] = $this->prepare_joined_tables_model($model, $argument);
63
				unset($arguments[$item]);
64
				continue;
65
			}
66
			$model              = explode(':', $model, 2);
67
			$type               = $model[0];
68
			$multilingual_field = false;
69
			/**
70
			 * If field is multilingual
71
			 */
72
			if ($type == 'ml') {
73
				$multilingual_field = true;
74
				$model              = explode(':', $model[1], 2);
75
				$type               = $model[0];
76
			}
77
			$argument = $this->crud_argument_preparation(
78
				$type,
79
				isset($model[1]) ? $model[1] : null,
80
				$argument
81
			);
82
			/**
83
			 * If field is multilingual - handle multilingual storing of value automatically
84
			 */
85
			if ($multilingual_field && $this->is_multilingual()) {
86
				if ($id !== false) {
87
					$argument = Text::instance()->set($this->cdb(), "$this->data_model_ml_group/$item", $id, $argument);
88
				} else {
89
					$update_needed = true;
90
				}
91
			}
92
		}
93
		return [
94
			$arguments,
95
			$joined_tables
96
		];
97
	}
98
	/**
99
	 * @param array $structure
100
	 * @param array $arguments
101
	 *
102
	 * @return array
103
	 */
104
	private function prepare_joined_tables_model ($structure, $arguments) {
105
		if (!$arguments) {
106
			return [];
107
		}
108
		$new_structure = [
109
			'id_field' => array_keys($structure['data_model'])[0],
110
			'fields'   => array_slice($structure['data_model'], 1),
111
			'data'     => []
112
		];
113
		if (isset($structure['language_field'])) {
114
			$new_structure['language_field'] = $structure['language_field'];
115
		}
116
		$arguments       = is_array_assoc($arguments) ? [$arguments] : _array((array)$arguments);
117
		$arguments_assoc = is_array_assoc($arguments[0]);
118
		foreach ($new_structure['fields'] as $field_name => $field_model) {
119
			/**
120
			 * Both associative and indexed arrays are supported - that is why we determine key for array
121
			 */
122
			$key = $arguments_assoc ? $field_name : array_search($field_name, array_keys($new_structure['fields']));
123
			if (is_callable($field_model)) {
124
				foreach ($arguments as $index => $arguments_local) {
125
					$new_structure['data'][$index][$field_name] = $field_model($arguments_local[$key]);
126
				}
127
				continue;
128
			}
129
			$field_model = explode(':', $field_model, 2);
130
			$type        = $field_model[0];
131
			$format      = isset($field_model[1]) ? $field_model[1] : null;
132
			foreach ($arguments as $index => $arguments_local) {
133
				$new_structure['data'][$index][$field_name] = $this->crud_argument_preparation($type, $format, $arguments_local[$key]);
134
			}
135
		}
136
		return $new_structure;
137
	}
138
	/**
139
	 * @param string $type
140
	 * @param mixed  $format
141
	 * @param mixed  $argument
142
	 *
143
	 * @return float|int|mixed|string|\string[]
144
	 */
145
	private function crud_argument_preparation ($type, $format, $argument) {
146
		switch ($type) {
147
			case 'int':
148
			case 'float':
149
				$argument = $type == 'int' ? (int)$argument : (float)$argument;
150
				/**
151
				 * Ranges processing
152
				 */
153
				if ($format !== null) {
154
					/**
155
					 * After this `$format[0]` will contain minimum and `$format[1]` if exists - maximum
156
					 */
157
					$format   = explode('..', $format);
158
					$argument = max($argument, $format[0]);
159
					if (isset($format[1])) {
160
						$argument = min($argument, $format[1]);
161
					}
162
				}
163
				break;
164
			case 'text':
165
			case 'html':
166
			case 'html_iframe':
167
				$argument = xap(
168
					$argument,
169
					$type == 'text' ? 'text' : true,
170
					$type == 'html_iframe'
171
				);
172
				/**
173
				 * Truncation
174
				 */
175
				if ($format !== null) {
176
					/**
177
					 * After this `$format[0]` will contain length to truncation and `$format[1]` if exists - ending
178
					 */
179
					$format   = explode(':', $format);
180
					$argument = truncate($argument, $format[0], isset($format[1]) ? $format[1] : '...', true);
181
				}
182
				break;
183
			case 'set':
184
				$allowed_arguments = explode(',', $format);
185
				if (!in_array($argument, $allowed_arguments)) {
186
					$argument = $allowed_arguments[0];
187
				}
188
				break;
189
			case 'json':
190
				$argument = _json_encode($argument);
191
				break;
192
		}
193
		return $argument;
194
	}
195
	/**
196
	 * @return bool
197
	 */
198
	private function is_multilingual () {
199
		return isset($this->data_model_ml_group) && $this->data_model_ml_group;
200
	}
201
	/**
202
	 * @return bool
203
	 */
204
	private function with_files_support () {
205
		return isset($this->data_model_files_tag_prefix) && $this->data_model_files_tag_prefix;
206
	}
207
	/**
208
	 * @param int|string     $id
209
	 * @param int[]|string[] $data_before
210
	 * @param int[]|string[] $data_after
211
	 */
212
	private function find_update_files_tags ($id, $data_before, $data_after) {
213
		if (!$this->with_files_support()) {
214
			return;
215
		}
216
		$prefix = $this->data_model_files_tag_prefix;
217
		$clang  = Language::instance()->clang;
218
		$this->update_files_tags(
219
			"$prefix/$id/$clang",
220
			$this->find_urls($data_before ?: []),
221
			$this->find_urls($data_after ?: [])
222
		);
223
	}
224
	/**
225
	 * Find URLs (any actually) in attributes values (wrapped with `"`, other quotes are not supported) or if field itself is URL
226
	 *
227
	 * @param array $data
228
	 *
229
	 * @return string[]
230
	 */
231
	protected function find_urls ($data) {
232
		/**
233
		 * At first we search URLs among attributes values, then whether some field looks like URL itself, and lastly do recursive scan
234
		 */
235
		return array_merge(
236
			preg_match_all('/"((http[s]?:)?\/\/.+)"/Uims', $this->recursive_implode(' ', $data), $files)
237
				? array_unique($files[1])
238
				: [],
239
			array_filter(
240
				$data,
241
				function ($data) {
242
					return !is_array($data) && preg_match('/^(http[s]?:)?\/\/.+$/Uims', $data);
243
				}
244
			),
245
			$data ? array_merge(
246
				... array_map(
247
						function ($data) {
248
							return is_array($data) ? $this->find_urls($data) : [];
249
						},
250
						array_values($data)
251
					)
252
			) : []
253
		);
254
	}
255
	/**
256
	 * @param string $glue
257
	 * @param array  $pieces
258
	 *
259
	 * @return string
260
	 */
261
	protected function recursive_implode ($glue, $pieces) {
262
		foreach ($pieces as &$p) {
263
			if (is_array($p)) {
264
				$p = $this->recursive_implode($glue, $p);
265
			}
266
		}
267
		return implode($glue, $pieces);
268
	}
269
	/**
270
	 * @param string   $tag
271
	 * @param string[] $old_files
272
	 * @param string[] $new_files
273
	 */
274
	protected function update_files_tags ($tag, $old_files, $new_files) {
275
		if ($old_files || $new_files) {
276
			foreach (array_diff($old_files, $new_files) as $file) {
277
				Event::instance()->fire(
278
					'System/upload_files/del_tag',
279
					[
280
						'tag' => $tag,
281
						'url' => $file
282
					]
283
				);
284
			}
285
			foreach (array_diff($new_files, $old_files) as $file) {
286
				Event::instance()->fire(
287
					'System/upload_files/add_tag',
288
					[
289
						'tag' => $tag,
290
						'url' => $file
291
					]
292
				);
293
			}
294
		}
295
	}
296
	/**
297
	 * @param int|string $id
298
	 */
299
	private function delete_files_tags ($id) {
300
		if (!$this->with_files_support()) {
301
			return;
302
		}
303
		Event::instance()->fire(
304
			'System/upload_files/del_tag',
305
			[
306
				'tag' => "$this->data_model_files_tag_prefix/$id%"
307
			]
308
		);
309
	}
310
}
311