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

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

462 lines 12.9 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 array that contains the definition of the data.
64 *
65 * @since 1.0.0
66 *
67 * @access protected
68 * @var array
69 */
70 protected $_args;
71
72 const TAG_SEPARATOR = '>';
73 const TAG_SEPARATOR_VIEW = ' &#x27A4; ';
74
75 /**
76 * Constructor.
77 *
78 * @since 1.0.0
79 *
80 * @access public
81 * @param array $params The array that contains the definition of the data.
82 */
83 public function __construct( $params = null ) {
84 $this->_args = $params;
85 if ( isset( $this->_args['url'] ) ) {
86 $this->_url = trim( $this->_args['url'] );
87 }
88 if ( isset( $this->_args['root'] ) ) {
89 $this->_root = trim( $this->_args['root'] );
90 }
91 if ( isset( $this->_args['paging'] ) ) {
92 $this->_paging = trim( $this->_args['paging'] );
93 }
94
95 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Constructor called for params = %s', print_r( $params, true ) ), 'debug', __FILE__, __LINE__ );
96 }
97
98 /**
99 * Get the root elements for JSON-endpoint.
100 *
101 * @since ?
102 *
103 * @access public
104 */
105 public function fetchRoots() {
106 $roots = apply_filters( 'visualizer_json_get_root_elements', false, $this->_url );
107 if ( false !== $roots ) {
108 return $roots;
109 }
110 $roots = $this->getRootElements( 'root', '', array(), $this->getJSON() );
111 if ( empty( $roots ) ) {
112 $this->_error = esc_html__( 'This does not appear to be a valid JSON feed. Please try again.', 'visualizer' );
113 return false;
114 }
115 return $roots;
116 }
117
118 /**
119 * Get the name of the elements that are likely to contain the paginated URLs.
120 *
121 * @since ?
122 *
123 * @access public
124 */
125 public function getPaginationElements() {
126 $pages = array();
127 $root = explode( self::TAG_SEPARATOR, $this->_root );
128 // the pagination element is usually one level above the root element we are going to use
129 // 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.
130 array_pop( $root );
131
132 // base of the next element.
133 $base = implode( self::TAG_SEPARATOR, $root );
134 // get rid of the first element as that is the faux root element indicator
135 array_shift( $root );
136
137 $array = $this->getJSON();
138 if ( is_null( $array ) ) {
139 return $pages;
140 }
141
142 $leaf = $array;
143 if ( ! empty( $root ) ) {
144 foreach ( $root as $tag ) {
145 if ( array_key_exists( $tag, $leaf ) ) {
146 $leaf = $array[ $tag ];
147 } else {
148 // if the tag does not exist, we assume it is present in the 0th element of the current array.
149 $leaf = $leaf[0][ $tag ];
150 }
151 }
152 }
153
154 $paging = array();
155 foreach ( $leaf as $key => $value ) {
156 // the paging element's value will most probably contain the url of the feed.
157 if ( is_string( $value ) && 0 === stripos( $value, $this->_url ) ) {
158 $paging[] = $key;
159 }
160 }
161
162 foreach ( array_filter( array_unique( $paging ) ) as $page ) {
163 $pages[] = $base . self::TAG_SEPARATOR . $page;
164 }
165 return $pages;
166 }
167
168 /**
169 * Parse the JSON-endpoint from the chosen root as the base.
170 *
171 * @since ?
172 *
173 * @access public
174 */
175 public function parse() {
176 $url = $this->_url;
177 $data = array();
178 $page = 1;
179
180 while ( ! is_null( $url ) && $page++ < apply_filters( 'visualizer_json_fetch_pages', 5, $this->_url ) ) {
181 $array = $this->getJSON( $url );
182 if ( is_null( $array ) ) {
183 return $data;
184 }
185
186 $root = explode( self::TAG_SEPARATOR, $this->_root );
187 // get rid of the first element as that is the faux root element indicator
188 array_shift( $root );
189 $leaf = $array;
190 foreach ( $root as $tag ) {
191 if ( array_key_exists( $tag, $leaf ) ) {
192 $leaf = $leaf[ $tag ];
193 } else {
194 // if the tag does not exist, we assume it is present in every element of the current array.
195 $temp = array();
196 for ( $depth = 0; $depth < count( $leaf ); $depth++ ) {
197 if ( ! isset( $leaf[ $depth ][ $tag ] ) ) {
198 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__ );
199 continue;
200 }
201 $temp[] = $leaf[ $depth ][ $tag ];
202 }
203 $leaf = $temp;
204 }
205 }
206
207 // now that we have got the final array we need to operate on, we will use this as the collection of series.
208 // lets check if the series is a flat-series e.g. https://api.exchangeratesapi.io/latest
209 // in this, all values of `$leaf` would be a string, not an array.
210 $values = array_values( $leaf );
211 if ( ! is_array( $values[0] ) ) {
212 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Looks like a flat-series = %s', print_r( $leaf, true ) ), 'debug', __FILE__, __LINE__ );
213 $inner_data = array();
214 foreach ( $leaf as $datum => $value ) {
215 $inner_data[ $datum ] = $value;
216 }
217 $data[] = $inner_data;
218 } else {
219 $row_num = 0;
220 // we will filter out all elements of this array that have array as a value.
221 foreach ( $leaf as $datum ) {
222 $inner_data = array();
223 foreach ( $datum as $key => $value ) {
224 if ( is_array( $value ) ) {
225 continue;
226 }
227 $inner_data[ $key ] = $value;
228 }
229 // if we want to exclude entire rows on the basis of some data/key or row index.
230 if ( apply_filters( 'visualizer_json_include_row', true, $inner_data, $this->_root, $this->_url, ++$row_num ) ) {
231 $data[] = $inner_data;
232 }
233 }
234 }
235
236 $data = $this->collectCommonElements( $data );
237
238 $url = $this->getNextPage( $array );
239 }
240
241 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__ );
242
243 return $data;
244 }
245
246 /**
247 * Determines which keys are common to all the data and returns an array with only those elements.
248 * This is to ensure that the data set displayed on the table is consistent.
249 * Inconsistent data causes the table to not display.
250 *
251 * @since ?
252 *
253 * @access private
254 */
255 private function collectCommonElements( $data ) {
256 $keys = array();
257 foreach ( $data as $datum ) {
258 if ( empty( $keys ) ) {
259 $keys = array_keys( $datum );
260 continue;
261 }
262 $keys = array_intersect( $keys, array_keys( $datum ) );
263 }
264
265 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Extracting data only for the common keys = %s', print_r( $keys, true ) ), 'debug', __FILE__, __LINE__ );
266
267 $rows = array();
268 foreach ( $data as $datum ) {
269 foreach ( $datum as $key => $value ) {
270 if ( in_array( $key, $keys, true ) ) {
271 $row[ $key ] = $value;
272 }
273 }
274 $rows[] = $row;
275 }
276
277 return $rows;
278
279 }
280
281 /**
282 * Get the next page URL.
283 *
284 * @since ?
285 *
286 * @access private
287 */
288 private function getNextPage( $array ) {
289 if ( empty( $this->_paging ) ) {
290 return null;
291 }
292
293 $root = explode( self::TAG_SEPARATOR, $this->_paging );
294 // get rid of the first element as that is the faux root element indicator
295 array_shift( $root );
296 $leaf = $array;
297 foreach ( $root as $tag ) {
298 if ( array_key_exists( $tag, $leaf ) ) {
299 $leaf = $array[ $tag ];
300 } else {
301 // if the tag does not exist, we assume it is present in the 0th element of the current array.
302 // TODO: we may want to change this to a filter later.
303 $leaf = $leaf[0][ $tag ];
304 }
305 }
306
307 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Got next page as %s for paging element %s', $leaf, $this->_paging ), 'debug', __FILE__, __LINE__ );
308
309 return $leaf;
310 }
311
312 /**
313 * Get the root elements for JSON-endpoint.
314 *
315 * @since ?
316 *
317 * @access private
318 */
319 private function getRootElements( $parent, $now, $root, $array ) {
320 if ( is_null( $array ) ) {
321 return array();
322 }
323
324 foreach ( $array as $key => $value ) {
325 if ( is_array( $value ) && ! empty( $value ) ) {
326 if ( ! is_numeric( $key ) ) {
327 $now = sprintf( '%s%s%s', $parent, self::TAG_SEPARATOR, $key );
328 $root[] = $now;
329 } else {
330 $now = $parent;
331 }
332 $root = $this->getRootElements( $now, $key, $root, $value );
333 }
334 }
335 $roots = array_filter( array_unique( $root ) );
336 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Roots found for %s = ', $this->_url, print_r( $roots, true ) ), 'debug', __FILE__, __LINE__ );
337 return $roots;
338 }
339
340 /**
341 * Get the JSON for the JSON-endpoint.
342 *
343 * @since ?
344 *
345 * @access private
346 */
347 private function getJSON( $url = null ) {
348 if ( is_null( $url ) ) {
349 $url = $this->_url;
350 }
351 // allow hooks to use any other args such as method=POST.
352 $response = wp_remote_request( $url, apply_filters( 'visualizer_json_args', array( 'method' => 'GET' ), $url ) );
353 if ( is_wp_error( $response ) ) {
354 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Error while fetching JSON endpoint %s = ', $url, print_r( $response, true ) ), 'error', __FILE__, __LINE__ );
355 return null;
356 }
357
358 $array = json_decode( wp_remote_retrieve_body( $response ), true );
359
360 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'JSON array for the endpoint is %s = ', print_r( $array, true ) ), 'debug', __FILE__, __LINE__ );
361
362 return $array;
363 }
364
365
366 /**
367 * Fetches series information. This is fetched only through the UI and not while refreshing the chart data.
368 *
369 * @since 1.0.0
370 *
371 * @access private
372 */
373 private function _fetchSeries() {
374 $params = $this->_args;
375 $headers = array_filter( $params['header'] );
376 $types = array_filter( $params['type'] );
377 $header_row = $type_row = array();
378 if ( $headers ) {
379 foreach ( $headers as $header ) {
380 if ( ! empty( $types[ $header ] ) ) {
381 $this->_series[] = array(
382 'label' => $header,
383 'type' => $types[ $header ],
384 );
385 }
386 }
387 }
388
389 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Series found for %s = ', $this->_url, print_r( $this->_series, true ) ), 'debug', __FILE__, __LINE__ );
390
391 return true;
392 }
393
394 /**
395 * Fetches data information.
396 *
397 * @since 1.0.0
398 *
399 * @access private
400 */
401 private function _fetchData() {
402 $params = $this->_args;
403
404 $headers = wp_list_pluck( $this->_series, 'label' );
405 $data = $this->parse();
406 foreach ( $data as $line ) {
407 $data_row = array();
408 foreach ( $line as $header => $value ) {
409 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
410 if ( in_array( $header, $headers ) ) {
411 $data_row[] = $value;
412 }
413 }
414 $this->_data[] = $this->_normalizeData( $data_row );
415 }
416
417 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Data found for %s = ', $this->_url, print_r( $this->_data, true ) ), 'debug', __FILE__, __LINE__ );
418
419 return true;
420 }
421
422 /**
423 * Fetches information from source, parses it and builds series and data arrays.
424 *
425 * @since 1.0.0
426 *
427 * @access public
428 * @return boolean TRUE on success, otherwise FALSE.
429 */
430 public function fetch() {
431 $this->_fetchSeries();
432 $this->_fetchData();
433 return true;
434 }
435
436 /**
437 * Refresh the data for the provided series.
438 *
439 * @since ?
440 *
441 * @access public
442 */
443 public function refresh( $series ) {
444 $this->_series = $series;
445 $this->_fetchData();
446 return true;
447 }
448
449 /**
450 * Returns source name.
451 *
452 * @since 1.0.0
453 *
454 * @access public
455 * @return string The name of source.
456 */
457 public function getSourceName() {
458 return __CLASS__;
459 }
460
461 }
462