PluginProbe
Import WP – CSV & XML Import Export for WordPress / 2.15.0
Import WP – CSV & XML Import Export for WordPress v2.15.0
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 2.9.1 All 144 releases
← All changes | class/Common/Importer/Parser/AbstractParser.php +147 -28 2.7.12 → 2.15.0 View file →
@@ -114,15 +114,13 @@
114 114 }
115 115
116 116 if (!empty($delimiter)) {
117 117
118 - if (!empty($output[$field_key])) {
119 - $field_parts = explode($delimiter, $output[$field_key]);
120 - foreach ($field_parts as $k => $field_part) {
121 - $field_parts[$k] = $this->map_field_data($field_part, array_values($data));
122 - }
123 - $output[$field_key] = implode($delimiter, $field_parts);
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));
124 121 }
122 + $output[$field_key] = implode($delimiter, $field_parts);
125 123 } else {
126 124 $output[$field_key] = $this->map_field_data($output[$field_key], array_values($data));
127 125 }
128 126 }
@@ -139,13 +137,12 @@
139 137 * @return string
140 138 */
141 139 public function query_string($query)
142 140 {
143 -
144 - $output = preg_replace_callback('/{(.*?)}/', array($this, 'query_matches'), $query);
145 - $output = $this->handle_custom_methods($output);
146 -
147 - 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));
148 145 }
149 146
150 147 public function query_matches($matches)
151 148 {
@@ -164,34 +161,156 @@
164 161 }
165 162
166 163 public function handle_custom_methods($input)
167 164 {
168 - // m: Multiline modifier
169 - // s: matches all characters including newlines
170 - $input = preg_replace_callback('/\[([\w]+)\((.*?)\)\]/ms', 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 + }
171 169
172 - $method = $matches[1];
170 + $offset = 0;
171 + $output = '';
172 + $length = strlen($input);
173 173
174 - $result = [];
175 - $args = [];
174 + while (($start = strpos($input, '[iwp:', $offset)) !== false) {
175 + $output .= substr($input, $offset, $start - $offset);
176 176
177 - // Dont split comma's if they are inside a double quote
178 - if (preg_match_all('/(?:".*?"|[^",\s]+)(?=\s*,|\s*$)/s', $matches[2], $result) > 0) {
179 - $args = $result[0];
180 - 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 + }
181 183
182 - // Strip commas from start and end of string
183 - $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;
184 269 }
270 + continue;
185 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 + }
186 287
187 - if (is_callable($method)) {
188 - 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);
189 308 }
309 + unset($arg);
310 + }
190 311
191 - return $matches[0];
192 - }, $input);
193 - return $input;
312 + return $args;
194 313 }
195 314
196 315 public function map_field_data($input, $map)
197 316 {