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 +143 -22 2.14.23 → 2.15.0 View file →
@@ -137,13 +137,12 @@
137 137 * @return string
138 138 */
139 139 public function query_string($query)
140 140 {
141 -
142 - $output = preg_replace_callback('/{(.*?)}/', array($this, 'query_matches'), $query);
143 - $output = $this->handle_custom_methods($output);
144 -
145 - 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));
146 145 }
147 146
148 147 public function query_matches($matches)
149 148 {
@@ -162,34 +161,156 @@
162 161 }
163 162
164 163 public function handle_custom_methods($input)
165 164 {
166 - // m: Multiline modifier
167 - // s: matches all characters including newlines
168 - $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 + }
169 169
170 - $method = $matches[1];
170 + $offset = 0;
171 + $output = '';
172 + $length = strlen($input);
171 173
172 - $result = [];
173 - $args = [];
174 + while (($start = strpos($input, '[iwp:', $offset)) !== false) {
175 + $output .= substr($input, $offset, $start - $offset);
174 176
175 - // Dont split comma's if they are inside a double quote
176 - if (preg_match_all('/(?:".*?"|[^",\s]+)(?=\s*,|\s*$)/s', $matches[2], $result) > 0) {
177 - $args = $result[0];
178 - 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 + }
179 183
180 - // Strip commas from start and end of string
181 - $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;
182 269 }
270 + continue;
183 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 + }
184 287
185 - if (is_callable($method)) {
186 - 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);
187 308 }
309 + unset($arg);
310 + }
188 311
189 - return $matches[0];
190 - }, $input);
191 - return $input;
312 + return $args;
192 313 }
193 314
194 315 public function map_field_data($input, $map)
195 316 {