PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.2.0
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.2.0
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 3.10.4 All 148 releases
← All changes | classes/Visualizer/Source/Json.php +81 -228 4.0.33.2.0 View file →
@@ -59,28 +59,8 @@
59 59 */
60 60 protected $_paging;
61 61
62 62 /**
63 - * The headers.
64 - *
65 - * @since ?
66 - *
67 - * @access protected
68 - * @var array
69 - */
70 - protected $_headers;
71 -
72 - /**
73 - * The other headers.
74 - *
75 - * @since 3.4.11
76 - *
77 - * @access protected
78 - * @var array
79 - */
80 - protected $_additional_headers;
81 -
82 - /**
83 63 * The array that contains the definition of the data.
84 64 *
85 65 * @since 1.0.0
86 66 *
@@ -110,19 +90,9 @@
110 90 }
111 91 if ( isset( $this->_args['paging'] ) ) {
112 92 $this->_paging = trim( $this->_args['paging'] );
113 93 }
114 - if ( isset( $this->_args['auth'] ) && ! empty( $this->_args['auth'] ) ) {
115 - $this->_headers = array( 'auth' => $this->_args['auth'] );
116 - } elseif ( isset( $this->_args['username'] ) && isset( $this->_args['password'] ) ) {
117 - $this->_headers = array( 'username' => $this->_args['username'], 'password' => $this->_args['password'] );
118 - }
119 - if ( isset( $this->_args['method'] ) ) {
120 - $this->_headers['method'] = $this->_args['method'];
121 - }
122 - if ( isset( $this->_args['additional_headers'] ) ) {
123 - $this->_additional_headers = $this->_args['additional_headers'];
124 - }
94 +
125 95 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Constructor called for params = %s', print_r( $params, true ) ), 'debug', __FILE__, __LINE__ );
126 96 }
127 97
128 98 /**
@@ -136,14 +106,9 @@
136 106 $roots = apply_filters( 'visualizer_json_get_root_elements', false, $this->_url );
137 107 if ( false !== $roots ) {
138 108 return $roots;
139 109 }
140 - $roots = $this->getRootElements( 'root', '', array(), $this->getJSON() );
141 - if ( is_null( $roots ) ) {
142 - $this->_error = esc_html__( 'This does not appear to be a valid JSON feed. Please try again.', 'visualizer' );
143 - return false;
144 - }
145 - return $roots;
110 + return $this->getRootElements( 'root', '', array(), $this->getJSON() );
146 111 }
147 112
148 113 /**
149 114 * Get the name of the elements that are likely to contain the paginated URLs.
@@ -183,15 +148,9 @@
183 148
184 149 $paging = array();
185 150 foreach ( $leaf as $key => $value ) {
186 151 // the paging element's value will most probably contain the url of the feed.
187 - if ( ! is_string( $value ) ) {
188 - continue;
189 - }
190 -
191 - // strip the url off the request parameters e.g. format=json as sometimes the pagination urls may not contain them.
192 - $url = wp_parse_url( $value );
193 - if ( 0 === stripos( $value, $this->_url ) || false !== stripos( $this->_url, $url['path'] ) ) {
152 + if ( is_string( $value ) && 0 === stripos( $value, $this->_url ) ) {
194 153 $paging[] = $key;
195 154 }
196 155 }
197 156
@@ -207,9 +166,9 @@
207 166 * @since ?
208 167 *
209 168 * @access public
210 169 */
211 - public function fetch() {
170 + public function parse() {
212 171 $url = $this->_url;
213 172 $data = array();
214 173 $page = 1;
215 174
@@ -215,39 +174,25 @@
215 174
216 175 while ( ! is_null( $url ) && $page++ < apply_filters( 'visualizer_json_fetch_pages', 5, $this->_url ) ) {
217 176 $array = $this->getJSON( $url );
218 177 if ( is_null( $array ) ) {
219 - break;
178 + return $data;
220 179 }
221 180
222 181 $root = explode( self::TAG_SEPARATOR, $this->_root );
223 - if ( count( $root ) > 1 ) {
224 - // get rid of the first element as that is the faux root element indicator
225 - array_shift( $root );
226 - }
182 + // get rid of the first element as that is the faux root element indicator
183 + array_shift( $root );
227 184 $leaf = $array;
228 185 foreach ( $root as $tag ) {
229 186 if ( array_key_exists( $tag, $leaf ) ) {
230 187 $leaf = $leaf[ $tag ];
231 188 } else {
232 - // if the tag does not exist, we assume it is present in every element of the current array.
233 - $temp = array();
234 - for ( $depth = 0; $depth < count( $leaf ); $depth++ ) {
235 - if ( ! isset( $leaf[ $depth ][ $tag ] ) ) {
236 - do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Element %s not present at depth %d in %d elements. Ignoring.', $tag, $depth, count( $leaf ) ), 'debug', __FILE__, __LINE__ );
237 - continue;
238 - }
239 - $temp[] = $leaf[ $depth ][ $tag ];
240 - }
241 - $leaf = $temp;
189 + // if the tag does not exist, we assume it is present in the 0th element of the current array.
190 + // TODO: we may want to change this to a filter later.
191 + $leaf = $leaf[0][ $tag ];
242 192 }
243 193 }
244 194
245 - // JSONs that do not have a root element may fall into this condition e.g. /wp-json/wp/v2/posts
246 - if ( empty( $leaf ) ) {
247 - $leaf = $array;
248 - }
249 -
250 195 // now that we have got the final array we need to operate on, we will use this as the collection of series.
251 196 // lets check if the series is a flat-series e.g. https://api.exchangeratesapi.io/latest
252 197 // in this, all values of `$leaf` would be a string, not an array.
253 198 $values = array_values( $leaf );
@@ -258,9 +203,8 @@
258 203 $inner_data[ $datum ] = $value;
259 204 }
260 205 $data[] = $inner_data;
261 206 } else {
262 - $row_num = 0;
263 207 // we will filter out all elements of this array that have array as a value.
264 208 foreach ( $leaf as $datum ) {
265 209 $inner_data = array();
266 210 foreach ( $datum as $key => $value ) {
@@ -268,63 +212,24 @@
268 212 continue;
269 213 }
270 214 $inner_data[ $key ] = $value;
271 215 }
272 - // if we want to exclude entire rows on the basis of some data/key or row index.
273 - if ( apply_filters( 'visualizer_json_include_row', true, $inner_data, $this->_root, $this->_url, ++$row_num ) ) {
216 + // if we want to exclude entire rows on the basis of some data/key.
217 + if ( apply_filters( 'visualizer_json_include_row', true, $inner_data, $this->_root, $this->_url ) ) {
274 218 $data[] = $inner_data;
275 219 }
276 220 }
277 221 }
278 222
279 - $data = $this->collectCommonElements( $data );
280 -
281 223 $url = $this->getNextPage( $array );
282 224 }
283 225
284 - $this->_data = $data;
226 + do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Parsed data endpoint %s with rooot %s is %s = ', $this->_url, $this->_root, print_r( $data, true ) ), 'debug', __FILE__, __LINE__ );
285 227
286 - do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Parsed data endpoint %s with root %s is %s', $this->_url, $this->_root, print_r( $data, true ) ), 'debug', __FILE__, __LINE__ );
287 -
288 - return true;
228 + return $data;
289 229 }
290 230
291 231 /**
292 - * Determines which keys are common to all the data and returns an array with only those elements.
293 - * This is to ensure that the data set displayed on the table is consistent.
294 - * Inconsistent data causes the table to not display.
295 - *
296 - * @since ?
297 - *
298 - * @access private
299 - */
300 - private function collectCommonElements( $data ) {
301 - $keys = array();
302 - foreach ( $data as $datum ) {
303 - if ( empty( $keys ) ) {
304 - $keys = array_keys( $datum );
305 - continue;
306 - }
307 - $keys = array_intersect( $keys, array_keys( $datum ) );
308 - }
309 -
310 - do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Extracting data only for the common keys = %s', print_r( $keys, true ) ), 'debug', __FILE__, __LINE__ );
311 -
312 - $rows = array();
313 - foreach ( $data as $datum ) {
314 - $row = array();
315 - foreach ( $datum as $key => $value ) {
316 - if ( in_array( $key, $keys, true ) ) {
317 - $row[ $key ] = $value;
318 - }
319 - }
320 - $rows[] = $row;
321 - }
322 -
323 - return $rows;
324 - }
325 -
326 - /**
327 232 * Get the next page URL.
328 233 *
329 234 * @since ?
330 235 *
@@ -329,9 +234,9 @@
329 234 * @since ?
330 235 *
331 236 * @access private
332 237 */
333 - private function getNextPage( $data ) {
238 + private function getNextPage( $array ) {
334 239 if ( empty( $this->_paging ) ) {
335 240 return null;
336 241 }
337 242
@@ -337,12 +242,12 @@
337 242
338 243 $root = explode( self::TAG_SEPARATOR, $this->_paging );
339 244 // get rid of the first element as that is the faux root element indicator
340 245 array_shift( $root );
341 - $leaf = $data;
246 + $leaf = $array;
342 247 foreach ( $root as $tag ) {
343 248 if ( array_key_exists( $tag, $leaf ) ) {
344 - $leaf = $leaf[ $tag ];
249 + $leaf = $array[ $tag ];
345 250 } else {
346 251 // if the tag does not exist, we assume it is present in the 0th element of the current array.
347 252 // TODO: we may want to change this to a filter later.
348 253 $leaf = $leaf[0][ $tag ];
@@ -360,27 +265,26 @@
360 265 * @since ?
361 266 *
362 267 * @access private
363 268 */
364 - private function getRootElements( $parent_key, $now, $root, $data ) {
365 - if ( is_null( $data ) ) {
366 - return null;
269 + private function getRootElements( $parent, $now, $root, $array ) {
270 + if ( is_null( $array ) ) {
271 + return array();
367 272 }
368 273
369 - $root[] = $parent_key;
370 - foreach ( $data as $key => $value ) {
274 + foreach ( $array as $key => $value ) {
371 275 if ( is_array( $value ) && ! empty( $value ) ) {
372 276 if ( ! is_numeric( $key ) ) {
373 - $now = sprintf( '%s%s%s', $parent_key, self::TAG_SEPARATOR, $key );
277 + $now = sprintf( '%s%s%s', $parent, self::TAG_SEPARATOR, $key );
374 278 $root[] = $now;
375 279 } else {
376 - $now = $parent_key;
280 + $now = $parent;
377 281 }
378 282 $root = $this->getRootElements( $now, $key, $root, $value );
379 283 }
380 284 }
381 - $roots = array_unique( $root );
382 - do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Roots found for %s = %s', $this->_url, print_r( $roots, true ) ), 'debug', __FILE__, __LINE__ );
285 + $roots = array_filter( array_unique( $root ) );
286 + do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Roots found for %s = ', $this->_url, print_r( $roots, true ) ), 'debug', __FILE__, __LINE__ );
383 287 return $roots;
384 288 }
385 289
386 290 /**
@@ -393,21 +297,16 @@
393 297 private function getJSON( $url = null ) {
394 298 if ( is_null( $url ) ) {
395 299 $url = $this->_url;
396 300 }
397 -
398 - $response = $this->connect( $url );
399 - if ( is_wp_error( $response ) || ! in_array( intval( $response['response']['code'] ), array( 200, 201 ), true ) ) {
400 - do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Error while fetching JSON endpoint %s = %s', $url, print_r( $response, true ) ), 'error', __FILE__, __LINE__ );
301 + // allow hooks to use any other args such as method=POST.
302 + $response = wp_remote_request( $url, apply_filters( 'visualizer_json_args', array( 'method' => 'GET' ), $url ) );
303 + if ( is_wp_error( $response ) ) {
304 + do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Error while fetching JSON endpoint %s = ', $url, print_r( $response, true ) ), 'error', __FILE__, __LINE__ );
401 305 return null;
402 306 }
403 307
404 - // this filter can be used to rearrange columns e.g. in candlestick charts
405 - // or to add additional data to the root.
406 - $bom = pack( 'H*', 'EFBBBF' );
407 - $response_body = wp_remote_retrieve_body( $response );
408 - $response_body = preg_replace( "/^$bom/", '', $response_body );
409 - $array = apply_filters( 'visualizer_json_massage_data', json_decode( $response_body, true ), $url );
308 + $array = json_decode( wp_remote_retrieve_body( $response ), true );
410 309
411 310 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'JSON array for the endpoint is %s = ', print_r( $array, true ) ), 'debug', __FILE__, __LINE__ );
412 311
413 312 return $array;
@@ -412,136 +311,89 @@
412 311
413 312 return $array;
414 313 }
415 314
315 +
416 316 /**
417 - * Sets the authentication/authorization headers and connects to the endpoint.
317 + * Fetches series information. This is fetched only through the UI and not while refreshing the chart data.
418 318 *
419 - * @since ?
319 + * @since 1.0.0
420 320 *
421 321 * @access private
422 322 */
423 - private function connect( $url ) {
424 - $args = array( 'method' => 'GET' );
425 - if ( ! empty( $this->_headers ) ) {
426 - $args = array( 'method' => strtoupper( $this->_headers['method'] ) );
427 - if ( array_key_exists( 'auth', $this->_headers ) ) {
428 - if ( ! empty( $this->_headers['auth'] ) ) {
429 - $header_auth = explode( ':', $this->_headers['auth'] );
430 - $header_key = explode( ' ', reset( $header_auth ) );
431 - $args['headers'] = array( 'Authorization' => reset( $header_key ) . ' ' . base64_encode( end( $header_key ) . ':' . end( $header_auth ) ) );
323 + private function _fetchSeries() {
324 + $params = $this->_args;
325 + $headers = array_filter( $params['header'] );
326 + $types = array_filter( $params['type'] );
327 + $header_row = $type_row = array();
328 + if ( $headers ) {
329 + foreach ( $headers as $header ) {
330 + if ( ! empty( $types[ $header ] ) ) {
331 + $this->_series[] = array(
332 + 'label' => $header,
333 + 'type' => $types[ $header ],
334 + );
432 335 }
433 - } elseif ( array_key_exists( 'username', $this->_headers ) && array_key_exists( 'password', $this->_headers ) && ! empty( $this->_headers['username'] ) && ! empty( $this->_headers['password'] ) ) {
434 - $args['headers'] = array( 'Authorization' => 'Basic ' . base64_encode( $this->_headers['username'] . ':' . $this->_headers['password'] ) );
435 336 }
436 337 }
437 338
438 - $args = apply_filters( 'visualizer_json_args', $args, $url );
439 - if ( ! empty( $this->_additional_headers ) ) {
440 - $this->_additional_headers = explode( ',', $this->_additional_headers );
441 - $this->_additional_headers = array_filter( $this->_additional_headers );
442 - if ( ! empty( $this->_additional_headers ) ) {
443 - $this->_additional_headers = array_map(
444 - function ( $headers ) {
445 - $headers = explode( ':', $headers );
446 - $headers = array_map( 'trim', $headers );
447 - return $headers;
448 - },
449 - $this->_additional_headers
450 - );
451 - foreach ( $this->_additional_headers as $additional_headers ) {
452 - $header_key = reset( $additional_headers );
453 - $header_value = end( $additional_headers );
454 - $args['headers'][ $header_key ] = $header_value;
455 - }
456 - }
457 - }
339 + do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Series found for %s = ', $this->_url, print_r( $this->_series, true ) ), 'debug', __FILE__, __LINE__ );
458 340
459 - // Check if this is a WooCommerce endpoint request and add verification token.
460 - if ( $this->is_woocommerce_request( $url ) ) {
461 - // Generate a unique token for this specific request.
462 - $token = wp_generate_password( 32, false );
463 - set_transient( 'visualizer_wc_token_' . $token, time(), 60 );
464 - if ( ! isset( $args['headers'] ) ) {
465 - $args['headers'] = array();
466 - }
467 - $args['headers']['X-Visualizer-Token'] = $token;
468 - }
469 -
470 - do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Connecting to %s with args = %s ', $url, print_r( $args, true ) ), 'debug', __FILE__, __LINE__ );
471 - return wp_remote_request( $url, $args );
341 + return true;
472 342 }
473 343
474 344 /**
475 - * Refresh the data for the provided series.
345 + * Fetches data information.
476 346 *
477 - * @since ?
347 + * @since 1.0.0
478 348 *
479 - * @access public
349 + * @access private
480 350 */
481 - public function refresh( $series ) {
482 - $this->_series = $series;
483 - $this->fetch();
351 + private function _fetchData() {
352 + $params = $this->_args;
484 353
485 354 $headers = wp_list_pluck( $this->_series, 'label' );
486 - $data = array();
487 - foreach ( $this->_data as $line ) {
355 + $data = $this->parse();
356 + foreach ( $data as $line ) {
488 357 $data_row = array();
489 358 foreach ( $line as $header => $value ) {
490 - // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
491 - if ( in_array( $header, $headers ) ) {
359 + if ( in_array( $header, $headers, true ) ) {
492 360 $data_row[] = $value;
493 361 }
494 362 }
495 - $data[] = $this->_normalizeData( $data_row );
363 + $this->_data[] = $this->_normalizeData( $data_row );
496 364 }
497 - $this->_data = $data;
365 +
366 + do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Data found for %s = ', $this->_url, print_r( $this->_data, true ) ), 'debug', __FILE__, __LINE__ );
367 +
498 368 return true;
499 369 }
500 370
501 371 /**
502 - * Check if the URL is a WooCommerce endpoint request.
372 + * Fetches information from source, parses it and builds series and data arrays.
503 373 *
504 - * @access private
505 - * @param string $url The URL to check.
506 - * @return bool True if it's a WooCommerce request, false otherwise.
374 + * @since 1.0.0
375 + *
376 + * @access public
377 + * @return boolean TRUE on success, otherwise FALSE.
507 378 */
508 - private function is_woocommerce_request( $url ) {
509 - if ( empty( $url ) ) {
510 - return false;
511 - }
379 + public function fetch() {
380 + $this->_fetchSeries();
381 + $this->_fetchData();
382 + return true;
383 + }
512 384
513 - $parsed_url = function_exists( 'wp_parse_url' ) ? wp_parse_url( $url ) : parse_url( $url );
514 - if ( empty( $parsed_url ) || empty( $parsed_url['host'] ) || empty( $parsed_url['path'] ) ) {
515 - return false;
516 - }
517 -
518 - $site_url = function_exists( 'home_url' ) ? home_url() : ( function_exists( 'site_url' ) ? site_url() : '' );
519 - $site_parts = $site_url ? ( function_exists( 'wp_parse_url' ) ? wp_parse_url( $site_url ) : parse_url( $site_url ) ) : array();
520 - if ( empty( $site_parts['host'] ) ) {
521 - return false;
522 - }
523 -
524 - $target_host = strtolower( $parsed_url['host'] );
525 - $site_host = strtolower( $site_parts['host'] );
526 - if ( $target_host !== $site_host ) {
527 - return false;
528 - }
529 -
530 - $path = '/' . ltrim( $parsed_url['path'], '/' );
531 - $wc_patterns = array(
532 - '/wp-json/wc/',
533 - '/wp-json/wc-analytics/',
534 - '/wc-analytics/',
535 - );
536 -
537 - foreach ( $wc_patterns as $pattern ) {
538 - if ( strpos( $path, $pattern ) !== false ) {
539 - return true;
540 - }
541 - }
542 -
543 - return false;
385 + /**
386 + * Refresh the data for the provided series.
387 + *
388 + * @since ?
389 + *
390 + * @access public
391 + */
392 + public function refresh( $series ) {
393 + $this->_series = $series;
394 + $this->_fetchData();
395 + return true;
544 396 }
545 397
546 398 /**
547 399 * Returns source name.
@@ -553,5 +405,6 @@
553 405 */
554 406 public function getSourceName() {
555 407 return __CLASS__;
556 408 }
409 +
557 410 }