PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.4.10
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.4.10
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
visualizer / classes / Visualizer / Source / Json.php

Json.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.4.10, at classes/Visualizer/Source/Json.php

465 lines 13.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // +----------------------------------------------------------------------+
4 // | Copyright 2013 Madpixels (email : visualizer@madpixels.net) |
5 // +----------------------------------------------------------------------+
6 // | This program is free software; you can redistribute it and/or modify |
7 // | it under the terms of the GNU General Public License, version 2, as |
8 // | published by the Free Software Foundation. |
9 // | |
10 // | This program is distributed in the hope that it will be useful, |
11 // | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 // | GNU General Public License for more details. |
14 // | |
15 // | You should have received a copy of the GNU General Public License |
16 // | along with this program; if not, write to the Free Software |
17 // | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, |
18 // | MA 02110-1301 USA |
19 // +----------------------------------------------------------------------+
20 // | Author: Eugene Manuilov <eugene@manuilov.org> |
21 // +----------------------------------------------------------------------+
22 /**
23 * Source manager for JSON URLs.
24 *
25 * @category Visualizer
26 * @package Source
27 *
28 * @since 1.0.0
29 */
30 class Visualizer_Source_Json extends Visualizer_Source {
31
32 /**
33 * The url to the data.
34 *
35 * @since 1.0.0
36 *
37 * @access protected
38 * @var string
39 */
40 protected $_url;
41
42 /**
43 * The root to the data.
44 *
45 * @since 1.0.0
46 *
47 * @access protected
48 * @var string
49 */
50 protected $_root;
51
52 /**
53 * The paging element.
54 *
55 * @since 1.0.0
56 *
57 * @access protected
58 * @var string
59 */
60 protected $_paging;
61
62 /**
63 * The headers.
64 *
65 * @since ?
66 *
67 * @access protected
68 * @var array
69 */
70 protected $_headers;
71
72 /**
73 * The array that contains the definition of the data.
74 *
75 * @since 1.0.0
76 *
77 * @access protected
78 * @var array
79 */
80 protected $_args;
81
82 const TAG_SEPARATOR = '>';
83 const TAG_SEPARATOR_VIEW = ' &#x27A4; ';
84
85 /**
86 * Constructor.
87 *
88 * @since 1.0.0
89 *
90 * @access public
91 * @param array $params The array that contains the definition of the data.
92 */
93 public function __construct( $params = null ) {
94 $this->_args = $params;
95 if ( isset( $this->_args['url'] ) ) {
96 $this->_url = trim( $this->_args['url'] );
97 }
98 if ( isset( $this->_args['root'] ) ) {
99 $this->_root = trim( $this->_args['root'] );
100 }
101 if ( isset( $this->_args['paging'] ) ) {
102 $this->_paging = trim( $this->_args['paging'] );
103 }
104 if ( isset( $this->_args['auth'] ) && ! empty( $this->_args['auth'] ) ) {
105 $this->_headers = array( 'auth' => $this->_args['auth'] );
106 } elseif ( isset( $this->_args['username'] ) && isset( $this->_args['password'] ) ) {
107 $this->_headers = array( 'username' => $this->_args['username'], 'password' => $this->_args['password'] );
108 }
109 if ( isset( $this->_args['method'] ) ) {
110 $this->_headers['method'] = $this->_args['method'];
111 }
112 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Constructor called for params = %s', print_r( $params, true ) ), 'debug', __FILE__, __LINE__ );
113 }
114
115 /**
116 * Get the root elements for JSON-endpoint.
117 *
118 * @since ?
119 *
120 * @access public
121 */
122 public function fetchRoots() {
123 $roots = apply_filters( 'visualizer_json_get_root_elements', false, $this->_url );
124 if ( false !== $roots ) {
125 return $roots;
126 }
127 $roots = $this->getRootElements( 'root', '', array(), $this->getJSON() );
128 if ( is_null( $roots ) ) {
129 $this->_error = esc_html__( 'This does not appear to be a valid JSON feed. Please try again.', 'visualizer' );
130 return false;
131 }
132 return $roots;
133 }
134
135 /**
136 * Get the name of the elements that are likely to contain the paginated URLs.
137 *
138 * @since ?
139 *
140 * @access public
141 */
142 public function getPaginationElements() {
143 $pages = array();
144 $root = explode( self::TAG_SEPARATOR, $this->_root );
145 // the pagination element is usually one level above the root element we are going to use
146 // e.g. if the data is in root > results, the pagination element (let's call it "next") would be root > next and not root > results > next.
147 array_pop( $root );
148
149 // base of the next element.
150 $base = implode( self::TAG_SEPARATOR, $root );
151 // get rid of the first element as that is the faux root element indicator
152 array_shift( $root );
153
154 $array = $this->getJSON();
155 if ( is_null( $array ) ) {
156 return $pages;
157 }
158
159 $leaf = $array;
160 if ( ! empty( $root ) ) {
161 foreach ( $root as $tag ) {
162 if ( array_key_exists( $tag, $leaf ) ) {
163 $leaf = $array[ $tag ];
164 } else {
165 // if the tag does not exist, we assume it is present in the 0th element of the current array.
166 $leaf = $leaf[0][ $tag ];
167 }
168 }
169 }
170
171 $paging = array();
172 foreach ( $leaf as $key => $value ) {
173 // the paging element's value will most probably contain the url of the feed.
174 if ( ! is_string( $value ) ) {
175 continue;
176 }
177
178 // strip the url off the request parameters e.g. format=json as sometimes the pagination urls may not contain them.
179 $url = wp_parse_url( $value );
180 if ( 0 === stripos( $value, $this->_url ) || false !== stripos( $this->_url, $url['path'] ) ) {
181 $paging[] = $key;
182 }
183 }
184
185 foreach ( array_filter( array_unique( $paging ) ) as $page ) {
186 $pages[] = $base . self::TAG_SEPARATOR . $page;
187 }
188 return $pages;
189 }
190
191 /**
192 * Parse the JSON-endpoint from the chosen root as the base.
193 *
194 * @since ?
195 *
196 * @access public
197 */
198 public function fetch() {
199 $url = $this->_url;
200 $data = array();
201 $page = 1;
202
203 while ( ! is_null( $url ) && $page++ < apply_filters( 'visualizer_json_fetch_pages', 5, $this->_url ) ) {
204 $array = $this->getJSON( $url );
205 if ( is_null( $array ) ) {
206 break;
207 }
208
209 $root = explode( self::TAG_SEPARATOR, $this->_root );
210 if ( count( $root ) > 1 ) {
211 // get rid of the first element as that is the faux root element indicator
212 array_shift( $root );
213 }
214 $leaf = $array;
215 foreach ( $root as $tag ) {
216 if ( array_key_exists( $tag, $leaf ) ) {
217 $leaf = $leaf[ $tag ];
218 } else {
219 // if the tag does not exist, we assume it is present in every element of the current array.
220 $temp = array();
221 for ( $depth = 0; $depth < count( $leaf ); $depth++ ) {
222 if ( ! isset( $leaf[ $depth ][ $tag ] ) ) {
223 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__ );
224 continue;
225 }
226 $temp[] = $leaf[ $depth ][ $tag ];
227 }
228 $leaf = $temp;
229 }
230 }
231
232 // JSONs that do not have a root element may fall into this condition e.g. /wp-json/wp/v2/posts
233 if ( empty( $leaf ) ) {
234 $leaf = $array;
235 }
236
237 // now that we have got the final array we need to operate on, we will use this as the collection of series.
238 // lets check if the series is a flat-series e.g. https://api.exchangeratesapi.io/latest
239 // in this, all values of `$leaf` would be a string, not an array.
240 $values = array_values( $leaf );
241 if ( ! is_array( $values[0] ) ) {
242 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Looks like a flat-series = %s', print_r( $leaf, true ) ), 'debug', __FILE__, __LINE__ );
243 $inner_data = array();
244 foreach ( $leaf as $datum => $value ) {
245 $inner_data[ $datum ] = $value;
246 }
247 $data[] = $inner_data;
248 } else {
249 $row_num = 0;
250 // we will filter out all elements of this array that have array as a value.
251 foreach ( $leaf as $datum ) {
252 $inner_data = array();
253 foreach ( $datum as $key => $value ) {
254 if ( is_array( $value ) ) {
255 continue;
256 }
257 $inner_data[ $key ] = $value;
258 }
259 // if we want to exclude entire rows on the basis of some data/key or row index.
260 if ( apply_filters( 'visualizer_json_include_row', true, $inner_data, $this->_root, $this->_url, ++$row_num ) ) {
261 $data[] = $inner_data;
262 }
263 }
264 }
265
266 $data = $this->collectCommonElements( $data );
267
268 $url = $this->getNextPage( $array );
269 }
270
271 $this->_data = $data;
272
273 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__ );
274
275 return true;
276 }
277
278 /**
279 * Determines which keys are common to all the data and returns an array with only those elements.
280 * This is to ensure that the data set displayed on the table is consistent.
281 * Inconsistent data causes the table to not display.
282 *
283 * @since ?
284 *
285 * @access private
286 */
287 private function collectCommonElements( $data ) {
288 $keys = array();
289 foreach ( $data as $datum ) {
290 if ( empty( $keys ) ) {
291 $keys = array_keys( $datum );
292 continue;
293 }
294 $keys = array_intersect( $keys, array_keys( $datum ) );
295 }
296
297 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Extracting data only for the common keys = %s', print_r( $keys, true ) ), 'debug', __FILE__, __LINE__ );
298
299 $rows = array();
300 foreach ( $data as $datum ) {
301 $row = array();
302 foreach ( $datum as $key => $value ) {
303 if ( in_array( $key, $keys, true ) ) {
304 $row[ $key ] = $value;
305 }
306 }
307 $rows[] = $row;
308 }
309
310 return $rows;
311
312 }
313
314 /**
315 * Get the next page URL.
316 *
317 * @since ?
318 *
319 * @access private
320 */
321 private function getNextPage( $array ) {
322 if ( empty( $this->_paging ) ) {
323 return null;
324 }
325
326 $root = explode( self::TAG_SEPARATOR, $this->_paging );
327 // get rid of the first element as that is the faux root element indicator
328 array_shift( $root );
329 $leaf = $array;
330 foreach ( $root as $tag ) {
331 if ( array_key_exists( $tag, $leaf ) ) {
332 $leaf = $array[ $tag ];
333 } else {
334 // if the tag does not exist, we assume it is present in the 0th element of the current array.
335 // TODO: we may want to change this to a filter later.
336 $leaf = $leaf[0][ $tag ];
337 }
338 }
339
340 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Got next page as %s for paging element %s', $leaf, $this->_paging ), 'debug', __FILE__, __LINE__ );
341
342 return $leaf;
343 }
344
345 /**
346 * Get the root elements for JSON-endpoint.
347 *
348 * @since ?
349 *
350 * @access private
351 */
352 private function getRootElements( $parent, $now, $root, $array ) {
353 if ( is_null( $array ) ) {
354 return null;
355 }
356
357 $root[] = $parent;
358 foreach ( $array as $key => $value ) {
359 if ( is_array( $value ) && ! empty( $value ) ) {
360 if ( ! is_numeric( $key ) ) {
361 $now = sprintf( '%s%s%s', $parent, self::TAG_SEPARATOR, $key );
362 $root[] = $now;
363 } else {
364 $now = $parent;
365 }
366 $root = $this->getRootElements( $now, $key, $root, $value );
367 }
368 }
369 $roots = array_unique( $root );
370 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Roots found for %s = %s', $this->_url, print_r( $roots, true ) ), 'debug', __FILE__, __LINE__ );
371 return $roots;
372 }
373
374 /**
375 * Get the JSON for the JSON-endpoint.
376 *
377 * @since ?
378 *
379 * @access private
380 */
381 private function getJSON( $url = null ) {
382 if ( is_null( $url ) ) {
383 $url = $this->_url;
384 }
385
386 $response = $this->connect( $url );
387 if ( is_wp_error( $response ) || ! in_array( intval( $response['response']['code'] ), array( 200, 201 ), true ) ) {
388 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Error while fetching JSON endpoint %s = %s', $url, print_r( $response, true ) ), 'error', __FILE__, __LINE__ );
389 return null;
390 }
391
392 // this filter can be used to rearrange columns e.g. in candlestick charts
393 // or to add additional data to the root.
394 $array = apply_filters( 'visualizer_json_massage_data', json_decode( wp_remote_retrieve_body( $response ), true ), $url );
395
396 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'JSON array for the endpoint is %s = ', print_r( $array, true ) ), 'debug', __FILE__, __LINE__ );
397
398 return $array;
399 }
400
401 /**
402 * Sets the authentication/authorization headers and connects to the endpoint.
403 *
404 * @since ?
405 *
406 * @access private
407 */
408 private function connect( $url ) {
409 $args = array( 'method' => 'GET' );
410 if ( ! empty( $this->_headers ) ) {
411 $args = array( 'method' => strtoupper( $this->_headers['method'] ) );
412 if ( array_key_exists( 'auth', $this->_headers ) ) {
413 $args['headers'] = array( 'Authorization' => $this->_headers['auth'] );
414 } elseif ( array_key_exists( 'username', $this->_headers ) && array_key_exists( 'password', $this->_headers ) && ! empty( $this->_headers['username'] ) && ! empty( $this->_headers['password'] ) ) {
415 $args['headers'] = array( 'Authorization' => 'Basic ' . base64_encode( $this->_headers['username'] . ':' . $this->_headers['password'] ) );
416 }
417 }
418
419 $args = apply_filters( 'visualizer_json_args', $args, $url );
420 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Connecting to %s with args = %s ', $url, print_r( $args, true ) ), 'debug', __FILE__, __LINE__ );
421
422 return wp_remote_request( $url, $args );
423 }
424
425 /**
426 * Refresh the data for the provided series.
427 *
428 * @since ?
429 *
430 * @access public
431 */
432 public function refresh( $series ) {
433 $this->_series = $series;
434 $this->fetch();
435
436 $headers = wp_list_pluck( $this->_series, 'label' );
437 $data = array();
438 foreach ( $this->_data as $line ) {
439 $data_row = array();
440 foreach ( $line as $header => $value ) {
441 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
442 if ( in_array( $header, $headers ) ) {
443 $data_row[] = $value;
444 }
445 }
446 $data[] = $this->_normalizeData( $data_row );
447 }
448 $this->_data = $data;
449 return true;
450 }
451
452 /**
453 * Returns source name.
454 *
455 * @since 1.0.0
456 *
457 * @access public
458 * @return string The name of source.
459 */
460 public function getSourceName() {
461 return __CLASS__;
462 }
463
464 }
465