/
home
/
assocoweys
/
test
/
wp-content
/
plugins
/
visualizer
/
classes
/
Visualizer
/
Source
/
Upload File
HOME
<?php // +----------------------------------------------------------------------+ // | Copyright 2013 Madpixels (email : visualizer@madpixels.net) | // +----------------------------------------------------------------------+ // | This program is free software; you can redistribute it and/or modify | // | it under the terms of the GNU General Public License, version 2, as | // | published by the Free Software Foundation. | // | | // | This program is distributed in the hope that it will be useful, | // | but WITHOUT ANY WARRANTY; without even the implied warranty of | // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | // | GNU General Public License for more details. | // | | // | You should have received a copy of the GNU General Public License | // | along with this program; if not, write to the Free Software | // | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, | // | MA 02110-1301 USA | // +----------------------------------------------------------------------+ // | Author: Eugene Manuilov <eugene@manuilov.org> | // +----------------------------------------------------------------------+ /** * Source manager for JSON URLs. * * @category Visualizer * @package Source * * @since 1.0.0 */ class Visualizer_Source_Json extends Visualizer_Source { /** * The url to the data. * * @since 1.0.0 * * @access protected * @var string */ protected $_url; /** * The root to the data. * * @since 1.0.0 * * @access protected * @var string */ protected $_root; /** * The paging element. * * @since 1.0.0 * * @access protected * @var string */ protected $_paging; /** * The headers. * * @since ? * * @access protected * @var array */ protected $_headers; /** * The other headers. * * @since 3.4.11 * * @access protected * @var array */ protected $_additional_headers; /** * The array that contains the definition of the data. * * @since 1.0.0 * * @access protected * @var array */ protected $_args; const TAG_SEPARATOR = '>'; const TAG_SEPARATOR_VIEW = ' ➤ '; /** * Constructor. * * @since 1.0.0 * * @access public * @param array $params The array that contains the definition of the data. */ public function __construct( $params = null ) { $this->_args = $params; if ( isset( $this->_args['url'] ) ) { $this->_url = trim( $this->_args['url'] ); } if ( isset( $this->_args['root'] ) ) { $this->_root = trim( $this->_args['root'] ); } if ( isset( $this->_args['paging'] ) ) { $this->_paging = trim( $this->_args['paging'] ); } if ( isset( $this->_args['auth'] ) && ! empty( $this->_args['auth'] ) ) { $this->_headers = array( 'auth' => $this->_args['auth'] ); } elseif ( isset( $this->_args['username'] ) && isset( $this->_args['password'] ) ) { $this->_headers = array( 'username' => $this->_args['username'], 'password' => $this->_args['password'] ); } if ( isset( $this->_args['method'] ) ) { $this->_headers['method'] = $this->_args['method']; } if ( isset( $this->_args['additional_headers'] ) ) { $this->_additional_headers = $this->_args['additional_headers']; } do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Constructor called for params = %s', print_r( $params, true ) ), 'debug', __FILE__, __LINE__ ); } /** * Get the root elements for JSON-endpoint. * * @since ? * * @access public */ public function fetchRoots() { $roots = apply_filters( 'visualizer_json_get_root_elements', false, $this->_url ); if ( false !== $roots ) { return $roots; } $roots = $this->getRootElements( 'root', '', array(), $this->getJSON() ); if ( is_null( $roots ) ) { $this->_error = esc_html__( 'This does not appear to be a valid JSON feed. Please try again.', 'visualizer' ); return false; } return $roots; } /** * Get the name of the elements that are likely to contain the paginated URLs. * * @since ? * * @access public */ public function getPaginationElements() { $pages = array(); $root = explode( self::TAG_SEPARATOR, $this->_root ); // the pagination element is usually one level above the root element we are going to use // 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. array_pop( $root ); // base of the next element. $base = implode( self::TAG_SEPARATOR, $root ); // get rid of the first element as that is the faux root element indicator array_shift( $root ); $array = $this->getJSON(); if ( is_null( $array ) ) { return $pages; } $leaf = $array; if ( ! empty( $root ) ) { foreach ( $root as $tag ) { if ( array_key_exists( $tag, $leaf ) ) { $leaf = $array[ $tag ]; } else { // if the tag does not exist, we assume it is present in the 0th element of the current array. $leaf = $leaf[0][ $tag ]; } } } $paging = array(); foreach ( $leaf as $key => $value ) { // the paging element's value will most probably contain the url of the feed. if ( ! is_string( $value ) ) { continue; } // strip the url off the request parameters e.g. format=json as sometimes the pagination urls may not contain them. $url = wp_parse_url( $value ); if ( 0 === stripos( $value, $this->_url ) || false !== stripos( $this->_url, $url['path'] ) ) { $paging[] = $key; } } foreach ( array_filter( array_unique( $paging ) ) as $page ) { $pages[] = $base . self::TAG_SEPARATOR . $page; } return $pages; } /** * Parse the JSON-endpoint from the chosen root as the base. * * @since ? * * @access public */ public function fetch() { $url = $this->_url; $data = array(); $page = 1; while ( ! is_null( $url ) && $page++ < apply_filters( 'visualizer_json_fetch_pages', 5, $this->_url ) ) { $array = $this->getJSON( $url ); if ( is_null( $array ) ) { break; } $root = explode( self::TAG_SEPARATOR, $this->_root ); if ( count( $root ) > 1 ) { // get rid of the first element as that is the faux root element indicator array_shift( $root ); } $leaf = $array; foreach ( $root as $tag ) { if ( array_key_exists( $tag, $leaf ) ) { $leaf = $leaf[ $tag ]; } else { // if the tag does not exist, we assume it is present in every element of the current array. $temp = array(); for ( $depth = 0; $depth < count( $leaf ); $depth++ ) { if ( ! isset( $leaf[ $depth ][ $tag ] ) ) { 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__ ); continue; } $temp[] = $leaf[ $depth ][ $tag ]; } $leaf = $temp; } } // JSONs that do not have a root element may fall into this condition e.g. /wp-json/wp/v2/posts if ( empty( $leaf ) ) { $leaf = $array; } // now that we have got the final array we need to operate on, we will use this as the collection of series. // lets check if the series is a flat-series e.g. https://api.exchangeratesapi.io/latest // in this, all values of `$leaf` would be a string, not an array. $values = array_values( $leaf ); if ( ! is_array( $values[0] ) ) { do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Looks like a flat-series = %s', print_r( $leaf, true ) ), 'debug', __FILE__, __LINE__ ); $inner_data = array(); foreach ( $leaf as $datum => $value ) { $inner_data[ $datum ] = $value; } $data[] = $inner_data; } else { $row_num = 0; // we will filter out all elements of this array that have array as a value. foreach ( $leaf as $datum ) { $inner_data = array(); foreach ( $datum as $key => $value ) { if ( is_array( $value ) ) { continue; } $inner_data[ $key ] = $value; } // if we want to exclude entire rows on the basis of some data/key or row index. if ( apply_filters( 'visualizer_json_include_row', true, $inner_data, $this->_root, $this->_url, ++$row_num ) ) { $data[] = $inner_data; } } } $data = $this->collectCommonElements( $data ); $url = $this->getNextPage( $array ); } $this->_data = $data; 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__ ); return true; } /** * Determines which keys are common to all the data and returns an array with only those elements. * This is to ensure that the data set displayed on the table is consistent. * Inconsistent data causes the table to not display. * * @since ? * * @access private */ private function collectCommonElements( $data ) { $keys = array(); foreach ( $data as $datum ) { if ( empty( $keys ) ) { $keys = array_keys( $datum ); continue; } $keys = array_intersect( $keys, array_keys( $datum ) ); } do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Extracting data only for the common keys = %s', print_r( $keys, true ) ), 'debug', __FILE__, __LINE__ ); $rows = array(); foreach ( $data as $datum ) { $row = array(); foreach ( $datum as $key => $value ) { if ( in_array( $key, $keys, true ) ) { $row[ $key ] = $value; } } $rows[] = $row; } return $rows; } /** * Get the next page URL. * * @since ? * * @access private */ private function getNextPage( $array ) { if ( empty( $this->_paging ) ) { return null; } $root = explode( self::TAG_SEPARATOR, $this->_paging ); // get rid of the first element as that is the faux root element indicator array_shift( $root ); $leaf = $array; foreach ( $root as $tag ) { if ( array_key_exists( $tag, $leaf ) ) { $leaf = $array[ $tag ]; } else { // if the tag does not exist, we assume it is present in the 0th element of the current array. // TODO: we may want to change this to a filter later. $leaf = $leaf[0][ $tag ]; } } do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Got next page as %s for paging element %s', $leaf, $this->_paging ), 'debug', __FILE__, __LINE__ ); return $leaf; } /** * Get the root elements for JSON-endpoint. * * @since ? * * @access private */ private function getRootElements( $parent, $now, $root, $array ) { if ( is_null( $array ) ) { return null; } $root[] = $parent; foreach ( $array as $key => $value ) { if ( is_array( $value ) && ! empty( $value ) ) { if ( ! is_numeric( $key ) ) { $now = sprintf( '%s%s%s', $parent, self::TAG_SEPARATOR, $key ); $root[] = $now; } else { $now = $parent; } $root = $this->getRootElements( $now, $key, $root, $value ); } } $roots = array_unique( $root ); do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Roots found for %s = %s', $this->_url, print_r( $roots, true ) ), 'debug', __FILE__, __LINE__ ); return $roots; } /** * Get the JSON for the JSON-endpoint. * * @since ? * * @access private */ private function getJSON( $url = null ) { if ( is_null( $url ) ) { $url = $this->_url; } $response = $this->connect( $url ); if ( is_wp_error( $response ) || ! in_array( intval( $response['response']['code'] ), array( 200, 201 ), true ) ) { do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Error while fetching JSON endpoint %s = %s', $url, print_r( $response, true ) ), 'error', __FILE__, __LINE__ ); return null; } // this filter can be used to rearrange columns e.g. in candlestick charts // or to add additional data to the root. $bom = pack( 'H*', 'EFBBBF' ); $response_body = wp_remote_retrieve_body( $response ); $response_body = preg_replace( "/^$bom/", '', $response_body ); $array = apply_filters( 'visualizer_json_massage_data', json_decode( $response_body, true ), $url ); do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'JSON array for the endpoint is %s = ', print_r( $array, true ) ), 'debug', __FILE__, __LINE__ ); return $array; } /** * Sets the authentication/authorization headers and connects to the endpoint. * * @since ? * * @access private */ private function connect( $url ) { $args = array( 'method' => 'GET' ); if ( ! empty( $this->_headers ) ) { $args = array( 'method' => strtoupper( $this->_headers['method'] ) ); if ( array_key_exists( 'auth', $this->_headers ) ) { if ( ! empty( $this->_headers['auth'] ) ) { $header_auth = explode( ':', $this->_headers['auth'] ); $header_key = explode( ' ', reset( $header_auth ) ); $args['headers'] = array( 'Authorization' => reset( $header_key ) . ' ' . base64_encode( end( $header_key ) . ':' . end( $header_auth ) ) ); } } elseif ( array_key_exists( 'username', $this->_headers ) && array_key_exists( 'password', $this->_headers ) && ! empty( $this->_headers['username'] ) && ! empty( $this->_headers['password'] ) ) { $args['headers'] = array( 'Authorization' => 'Basic ' . base64_encode( $this->_headers['username'] . ':' . $this->_headers['password'] ) ); } } $args = apply_filters( 'visualizer_json_args', $args, $url ); if ( ! empty( $this->_additional_headers ) ) { $this->_additional_headers = explode( ',', $this->_additional_headers ); $this->_additional_headers = array_filter( $this->_additional_headers ); if ( ! empty( $this->_additional_headers ) ) { $this->_additional_headers = array_map( function( $headers ) { $headers = explode( ':', $headers ); $headers = array_map( 'trim', $headers ); return $headers; }, $this->_additional_headers ); foreach ( $this->_additional_headers as $additional_headers ) { $header_key = reset( $additional_headers ); $header_value = end( $additional_headers ); $args['headers'][ $header_key ] = $header_value; } } } do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Connecting to %s with args = %s ', $url, print_r( $args, true ) ), 'debug', __FILE__, __LINE__ ); return wp_remote_request( $url, $args ); } /** * Refresh the data for the provided series. * * @since ? * * @access public */ public function refresh( $series ) { $this->_series = $series; $this->fetch(); $headers = wp_list_pluck( $this->_series, 'label' ); $data = array(); foreach ( $this->_data as $line ) { $data_row = array(); foreach ( $line as $header => $value ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict if ( in_array( $header, $headers ) ) { $data_row[] = $value; } } $data[] = $this->_normalizeData( $data_row ); } $this->_data = $data; return true; } /** * Returns source name. * * @since 1.0.0 * * @access public * @return string The name of source. */ public function getSourceName() { return __CLASS__; } }