PluginProbe
Import WP – CSV & XML Import Export for WordPress / 2.15.2
Import WP – CSV & XML Import Export for WordPress v2.15.2
2.15.2 2.15.1 2.15.0 2.14.24 2.14.23 2.7.0 2.7.1 2.7.10 2.7.11 2.7.12 2.7.13 2.7.14 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.2 2.8.3 2.9.0 All 145 releases
← All changes | class/Common/Importer/Parser/AbstractParser.php +289 -23 2.7.3 → 2.15.2 View file →
@@ -47,13 +47,86 @@
47 47 {
48 48 $output = [];
49 49
50 50 if (isset($group['fields'])) {
51 +
52 + // find files matching ._mapped._index
53 +
54 + $maps = [];
51 55 foreach ($group['fields'] as $field_key => $field_value) {
52 - $output[$field_key] = $this->query_string($field_value);
56 +
57 + $matches = [];
58 + if (
59 + // post.post_name._mapped._index
60 + // custom_fields.0.value._mapped._index
61 + preg_match('/^(.*?)\._mapped\._index$/', $field_key, $matches) === 1 &&
62 + // ignore custom_fields.0._mapped._index old style FieldMapper
63 + preg_match('/^custom_fields\.(?:[0-9]+)\._mapped\._index$/', $field_key) === 0 &&
64 + intval($field_value) > 0
65 + ) {
66 + $maps[$matches[1]] = $field_value;
67 + } else {
68 + $output[$field_key] = $this->query_string($field_value);
69 + }
53 70 }
54 71 }
55 72
73 + if (!empty($maps)) {
74 + foreach ($maps as $field_key => $field_rows) {
75 +
76 + $data = [];
77 + $delimiter = false;
78 +
79 + if (isset($output[$field_key . '._mapped._delimiter']) && !empty($output[$field_key . '._mapped._delimiter'])) {
80 +
81 + // get delimiter from FieldMap delimiter field, this has priority
82 + $delimiter = $output[$field_key . '._mapped._delimiter'];
83 + } else {
84 +
85 + // get delimiter from parent section settings. e.g. taxonomies and attachments
86 + $lastPos = strrpos($field_key, '.');
87 + if ($lastPos !== false) {
88 + $tmp = substr($field_key, 0, $lastPos);
89 + if (isset($output[$tmp . '.settings._delimiter'])) {
90 + $delimiter = !empty($output[$tmp . '.settings._delimiter']) ? $output[$tmp . '.settings._delimiter'] : ',';
91 + }
92 + }
93 + }
94 +
95 + foreach ($output as $item_key => $item_value) {
96 + $matches = [];
97 + if (preg_match('/^' . $field_key . '\._mapped\.([0-9]+)\.(.*?)$/', $item_key, $matches) === 1) {
98 +
99 + // row is to high
100 + if (intval($matches[1]) >= intval($field_rows)) {
101 +
102 + unset($output[$matches[0]]);
103 + continue;
104 + }
105 +
106 + if (!isset($data[$matches[1]])) {
107 + $data[$matches[1]] = [];
108 + }
109 +
110 + $data[$matches[1]][$matches[2]] = $item_value;
111 +
112 + unset($output[$matches[0]]);
113 + }
114 + }
115 +
116 + if (!empty($delimiter)) {
117 +
118 + $field_parts = explode($delimiter, $output[$field_key]);
119 + foreach ($field_parts as $k => $field_part) {
120 + $field_parts[$k] = $this->map_field_data($field_part, array_values($data));
121 + }
122 + $output[$field_key] = implode($delimiter, $field_parts);
123 + } else {
124 + $output[$field_key] = $this->map_field_data($output[$field_key], array_values($data));
125 + }
126 + }
127 + }
128 +
56 129 return $output;
57 130 }
58 131
59 132 /**
@@ -64,13 +137,12 @@
64 137 * @return string
65 138 */
66 139 public function query_string($query)
67 140 {
68 -
69 - $output = preg_replace_callback('/{(.*?)}/', array($this, 'query_matches'), $query);
70 - $output = $this->handle_custom_methods($output);
71 -
72 - return $output;
141 + // Parse [iwp:method(...)] before substituting {column} values, otherwise
142 + // parentheses in the data (e.g. Excel =Hyperlink("url")) can close a
143 + // malformed mapping that is missing ")".
144 + return $this->interpolate_braces($this->handle_custom_methods($query));
73 145 }
74 146
75 147 public function query_matches($matches)
76 148 {
@@ -89,33 +161,227 @@
89 161 }
90 162
91 163 public function handle_custom_methods($input)
92 164 {
93 - // m: Multiline modifier
94 - // s: matches all characters including newlines
95 - // U: non-greedy matching
96 - $input = preg_replace_callback('/\[([\w]+)\((.*?)\)\]/msU', function ($matches) {
165 + // Prefixed [iwp:method(...)] to avoid colliding with shortcodes / Gutenberg content.
166 + if (!is_string($input) || $input === '' || strpos($input, '[iwp:') === false) {
167 + return $input;
168 + }
97 169
98 - $method = $matches[1];
170 + $offset = 0;
171 + $output = '';
172 + $length = strlen($input);
99 173
100 - $result = [];
101 - $args = [];
174 + while (($start = strpos($input, '[iwp:', $offset)) !== false) {
175 + $output .= substr($input, $offset, $start - $offset);
102 176
103 - // Dont split comma's if they are inside a double quote
104 - if (preg_match_all('/(?:".*?"|[^",\s]+)(?=\s*,|\s*$)/s', $matches[2], $result) > 0) {
105 - $args = $result[0];
106 - foreach ($args as &$arg) {
177 + $parsed = $this->parse_custom_method_at($input, $start, $length);
178 + if ($parsed === null) {
179 + $output .= '[iwp:';
180 + $offset = $start + 5;
181 + continue;
182 + }
107 183
108 - // Strip commas from start and end of string
109 - $arg = preg_replace('/^(\'(.*)\'|"(.*)")$/s', '$2$3', $arg);
184 + $output .= $parsed[1];
185 + $offset = $parsed[0];
186 + }
187 +
188 + return $output . substr($input, $offset);
189 + }
190 +
191 + /**
192 + * Substitute {column} / {xpath} selectors.
193 + *
194 + * @param string $query
195 + * @return string
196 + */
197 + private function interpolate_braces($query)
198 + {
199 + if (!is_string($query) || $query === '' || strpos($query, '{') === false) {
200 + return $query;
201 + }
202 +
203 + return preg_replace_callback('/{(.*?)}/', array($this, 'query_matches'), $query);
204 + }
205 +
206 + /**
207 + * @param string $input
208 + * @param int $start
209 + * @param int $length
210 + * @return array{0:int,1:string}|null End offset and replacement, or null if not a complete call.
211 + */
212 + private function parse_custom_method_at($input, $start, $length)
213 + {
214 + $i = $start + 5;
215 + $name_len = strspn($input, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_', $i);
216 + if ($name_len < 1) {
217 + return null;
218 + }
219 +
220 + $method = substr($input, $i, $name_len);
221 + $i += $name_len;
222 + if ($i >= $length || $input[$i] !== '(') {
223 + return null;
224 + }
225 + $i++;
226 +
227 + $extracted = $this->extract_balanced_method_args($input, $i, $length);
228 + if ($extracted === null) {
229 + return null;
230 + }
231 +
232 + list($raw_args, $after_paren) = $extracted;
233 + if ($after_paren >= $length || $input[$after_paren] !== ']') {
234 + return null;
235 + }
236 +
237 + $end = $after_paren + 1;
238 + $original = substr($input, $start, $end - $start);
239 + $args = $this->split_custom_method_args($raw_args);
240 + foreach ($args as &$arg) {
241 + $arg = $this->interpolate_braces($arg);
242 + }
243 + unset($arg);
244 +
245 + if (is_callable($method)) {
246 + return array($end, call_user_func_array($method, $args));
247 + }
248 +
249 + return array($end, $original);
250 + }
251 +
252 + /**
253 + * Read until the method's closing ")", ignoring parentheses inside quotes.
254 + *
255 + * @param string $input
256 + * @param int $start
257 + * @param int $length
258 + * @return array{0:string,1:int}|null Raw args and offset after ")", or null if unterminated.
259 + */
260 + private function extract_balanced_method_args($input, $start, $length)
261 + {
262 + $depth = 1;
263 + $quote = null;
264 + for ($i = $start; $i < $length; $i++) {
265 + $ch = $input[$i];
266 + if ($quote !== null) {
267 + if ($ch === $quote) {
268 + $quote = null;
110 269 }
270 + continue;
111 271 }
272 + if ($ch === '"' || $ch === "'") {
273 + $quote = $ch;
274 + continue;
275 + }
276 + if ($ch === '(') {
277 + $depth++;
278 + continue;
279 + }
280 + if ($ch === ')') {
281 + $depth--;
282 + if ($depth === 0) {
283 + return array(substr($input, $start, $i - $start), $i + 1);
284 + }
285 + }
286 + }
112 287
113 - if (is_callable($method)) {
114 - return call_user_func_array($method, $args);
288 + return null;
289 + }
290 +
291 + /**
292 + * @param string $raw_args
293 + * @return array
294 + */
295 + private function split_custom_method_args($raw_args)
296 + {
297 + $args = [];
298 + if ($raw_args === '') {
299 + return $args;
300 + }
301 +
302 + // Dont split comma's if they are inside a double quote
303 + if (preg_match_all('/(?:".*?"|[^",\s]+)(?=\s*,|\s*$)/s', $raw_args, $result) > 0) {
304 + $args = $result[0];
305 + foreach ($args as &$arg) {
306 + // Strip quotes from start and end of string
307 + $arg = preg_replace('/^(\'(.*)\'|"(.*)")$/s', '$2$3', $arg);
115 308 }
309 + unset($arg);
310 + }
116 311
117 - return $matches[0];
118 - }, $input);
312 + return $args;
313 + }
314 +
315 + public function map_field_data($input, $map)
316 + {
317 + foreach ($map as $map_data) {
318 +
319 + $condition = isset($map_data['_condition']) && !empty($map_data['_condition']) ? $map_data['_condition'] : 'equal';
320 + $key = isset($map_data['key']) ? $map_data['key'] : '';
321 + $output = isset($map_data['value']) ? $map_data['value'] : '';
322 +
323 + switch ($condition) {
324 +
325 + case 'gt':
326 + $left = intval($input);
327 + $right = intval($key);
328 + if ($left > $right) {
329 + return $output;
330 + }
331 + break;
332 + case 'gte':
333 + $left = intval($input);
334 + $right = intval($key);
335 + if ($left >= $right) {
336 + return $output;
337 + }
338 + break;
339 + case 'lt':
340 + $left = intval($input);
341 + $right = intval($key);
342 + if ($left < $right) {
343 + return $output;
344 + }
345 + break;
346 + case 'lte':
347 + $left = intval($input);
348 + $right = intval($key);
349 + if ($left <= $right) {
350 + return $output;
351 + }
352 + break;
353 + case 'contains':
354 + if (stripos($input, trim($key)) !== false) {
355 + return $output;
356 + }
357 + break;
358 + case 'in':
359 + if (in_array($input, explode(',', $key))) {
360 + return $output;
361 + }
362 + break;
363 + case 'not-equal':
364 + if (trim($key) !== trim($input)) {
365 + return $output;
366 + }
367 + break;
368 + case 'not-contains':
369 + if (stripos($input, trim($key)) === false) {
370 + return $output;
371 + }
372 + break;
373 + case 'not-in':
374 + if (!in_array($input, explode(',', $key))) {
375 + return $output;
376 + }
377 + break;
378 + default:
379 + if (trim($key) === trim($input)) {
380 + return $output;
381 + }
382 + break;
383 + }
384 + }
119 385 return $input;
120 386 }
121 387 }