balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-api.php (35573B)


      1 <?php // phpcs:ignore WordPress.Files.FileName
      2 
      3 /**
      4  * Redux templates API class.
      5  *
      6  * @since   4.0.0
      7  * @package Redux Framework
      8  */
      9 
     10 namespace ReduxTemplates;
     11 
     12 use ReduxTemplates;
     13 use WP_Patterns_Registry;
     14 
     15 if ( ! defined( 'ABSPATH' ) ) {
     16 	exit; // Exit if accessed directly.
     17 }
     18 
     19 /**
     20  * ReduxTemplates API class.
     21  *
     22  * @since 4.0.0
     23  */
     24 class Api {
     25 
     26 	/**
     27 	 * Seconds to cache the local files.
     28 	 *
     29 	 * @var int
     30 	 */
     31 	private $cache_time = 24 * 3600; // 24 hours
     32 	/**
     33 	 * Timeout for requests.
     34 	 *
     35 	 * @var int
     36 	 */
     37 	private $timeout = 145;
     38 	/**
     39 	 * API URL.
     40 	 *
     41 	 * @var string
     42 	 */
     43 	protected $api_base_url = 'https://api.redux.io/';
     44 	/**
     45 	 * License API URL.
     46 	 *
     47 	 * @var string
     48 	 */
     49 	protected $license_base_url = 'https://redux.io/';
     50 	/**
     51 	 * Default headers array.
     52 	 *
     53 	 * @var array
     54 	 */
     55 	protected $default_request_headers = array();
     56 	/**
     57 	 * Filesystem object instance.
     58 	 *
     59 	 * @var Filesystem
     60 	 */
     61 	protected $filesystem;
     62 	/**
     63 	 * Cache folder location.
     64 	 *
     65 	 * @var string
     66 	 */
     67 	protected $cache_folder;
     68 
     69 	/**
     70 	 * Constructor.
     71 	 */
     72 	public function __construct() {
     73 
     74 		add_filter( 'redux_templates_api_headers', array( $this, 'request_verify' ) );
     75 		$this->default_request_headers = apply_filters( 'redux_templates_api_headers', $this->default_request_headers );
     76 
     77 		add_action( 'rest_api_init', array( $this, 'register_api_hooks' ), 0 );
     78 
     79 	}
     80 
     81 	/**
     82 	 * Get the filesystem.
     83 	 *
     84 	 * @return Filesystem
     85 	 */
     86 	private function get_filesystem() {
     87 		if ( empty( $this->filesystem ) ) {
     88 			$this->filesystem = \Redux_Filesystem::get_instance();
     89 
     90 		}
     91 
     92 		return $this->filesystem;
     93 	}
     94 
     95 	/**
     96 	 * Process the registered blocks from the library.
     97 	 *
     98 	 * @param array $parameters Array to be returned if no response, or to have data appended to.
     99 	 *
    100 	 * @return array Array of properly processed blocks and supported plugins.
    101 	 */
    102 	private function process_registered_blocks( $parameters ) {
    103 		$data = $this->api_cache_fetch( array(), array(), 'library.json' );
    104 
    105 		if ( empty( $data ) || ( ! empty( $data ) && ! isset( $data['plugins'] ) ) ) {
    106 			return $parameters;
    107 		}
    108 		$supported = ReduxTemplates\Supported_Plugins::instance();
    109 		$supported->init( $data['plugins'] );
    110 		$plugins           = $supported::get_plugins();
    111 		$installed_plugins = array();
    112 		if ( ! isset( $parameters['registered_blocks'] ) ) {
    113 			$parameters['registered_blocks'] = array();
    114 		}
    115 
    116 		foreach ( $plugins as $key => $value ) {
    117 			if ( isset( $value['version'] ) ) {
    118 				array_push( $installed_plugins, $key . '~' . $value['version'] );
    119 				$found_already = array_search( $key, $parameters['registered_blocks'], true );
    120 				if ( false !== $found_already ) {
    121 					unset( $parameters['registered_blocks'][ $found_already ] );
    122 				}
    123 				if ( isset( $value['namespace'] ) && $key !== $value['namespace'] ) {
    124 					$found = array_search( $value['namespace'], $parameters['registered_blocks'], true );
    125 					if ( false !== $found ) {
    126 						unset( $parameters['registered_blocks'][ $found ] );
    127 					}
    128 				}
    129 			}
    130 		}
    131 		$parameters['registered_blocks'] = array_values( array_merge( $installed_plugins, $parameters['registered_blocks'] ) );
    132 
    133 		return $parameters;
    134 	}
    135 
    136 	/**
    137 	 * Process the dependencies.
    138 	 *
    139 	 * @param array  $data Data array.
    140 	 * @param string $key  Key param.
    141 	 *
    142 	 * @return array Data array with dependencies outlined.
    143 	 */
    144 	private function process_dependencies( $data, $key ) {
    145 
    146 		foreach ( $data[ $key ] as $kk => $pp ) {
    147 			$debug = false;
    148 			// Add debug if statement if key matches here.
    149 
    150 			if ( isset( $pp['dependencies'] ) ) {
    151 				foreach ( $pp['dependencies'] as $dep ) {
    152 					if ( isset( $data['plugins'][ $dep ] ) ) {
    153 						if ( isset( $data['plugins'][ $dep ]['no_plugin'] ) || 'core' === $dep ) {
    154 							continue;
    155 						}
    156 						if ( isset( $data['plugins'][ $dep ]['free_slug'] ) ) {
    157 							if ( isset( $data['plugins'][ $data['plugins'][ $dep ]['free_slug'] ] ) ) {
    158 								$plugin = $data['plugins'][ $data['plugins'][ $dep ]['free_slug'] ];
    159 								if ( ! isset( $plugin['is_pro'] ) ) {
    160 									if ( ! isset( $data[ $key ][ $kk ]['proDependenciesMissing'] ) ) {
    161 										$data[ $key ][ $kk ]['proDependenciesMissing'] = array();
    162 									}
    163 									$data[ $key ][ $kk ]['proDependenciesMissing'][] = $dep;
    164 								}
    165 								if ( ! isset( $data[ $key ][ $kk ]['proDependencies'] ) ) {
    166 									$data[ $key ][ $kk ]['proDependencies'] = array();
    167 								}
    168 								$data[ $key ][ $kk ]['proDependencies'][] = $dep;
    169 							}
    170 						} else {
    171 							if ( ! isset( $data['plugins'][ $dep ]['version'] ) ) {
    172 								if ( ! isset( $data[ $key ][ $kk ]['installDependenciesMissing'] ) ) {
    173 									$data[ $key ][ $kk ]['installDependenciesMissing'] = array();
    174 								}
    175 								$data[ $key ][ $kk ]['installDependenciesMissing'][] = $dep;
    176 							}
    177 							if ( ! isset( $data[ $key ][ $kk ]['installDependencies'] ) ) {
    178 								$data[ $key ][ $kk ]['installDependencies'] = array();
    179 							}
    180 							$data[ $key ][ $kk ]['installDependencies'][] = $dep;
    181 						}
    182 					}
    183 				}
    184 			}
    185 
    186 			// Print the debug here if the key exists. Use `print_r( $data[ $key ][ $kk ] );exit();`.
    187 
    188 		}
    189 
    190 		return $data;
    191 
    192 	}
    193 
    194 	/**
    195 	 * Get the last cache time.
    196 	 *
    197 	 * @param string $abs_path Absolute path to a file.
    198 	 *
    199 	 * @return string|bool Last modified time.
    200 	 */
    201 	private function get_cache_time( $abs_path ) {
    202 		$filesystem    = $this->get_filesystem();
    203 		$last_modified = false;
    204 		if ( $filesystem->file_exists( $abs_path ) ) {
    205 			$last_modified = filemtime( $abs_path );
    206 		}
    207 
    208 		return $last_modified;
    209 	}
    210 
    211 	/**
    212 	 * Fetch from the cache if had.
    213 	 *
    214 	 * @param array  $parameters Absolute path to a file.
    215 	 * @param array  $config     Absolute path to a file.
    216 	 * @param string $path       URL path perform a request to a file.
    217 	 * @param bool   $cache_only Set to only fetch from the local cache.
    218 	 *
    219 	 * @return array Response and possibly the template if recovered.
    220 	 */
    221 	public function api_cache_fetch( $parameters, $config, $path, $cache_only = false ) {
    222 		$filesystem = $this->get_filesystem();
    223 
    224 		$this->cache_folder = trailingslashit( $filesystem->cache_folder ) . 'templates/';
    225 		if ( ! $filesystem->file_exists( $this->cache_folder ) ) {
    226 			$filesystem->mkdir( $this->cache_folder );
    227 		}
    228 
    229 		if ( strpos( $path, $this->cache_folder ) === false ) {
    230 			$path = $this->cache_folder . $path;
    231 		}
    232 		if ( ! $filesystem->file_exists( dirname( $path ) ) ) {
    233 			$filesystem->mkdir( dirname( $path ) );
    234 		}
    235 
    236 		$last_modified = file_exists( $path ) ? $this->get_cache_time( $path ) : false;
    237 
    238 		$use_cache = true;
    239 		if ( isset( $parameters['no_cache'] ) ) {
    240 			$use_cache = false;
    241 		}
    242 		if ( ! empty( $last_modified ) ) {
    243 			$config['headers']['Redux-Cache-Time'] = $last_modified;
    244 			if ( time() > $last_modified + $this->cache_time ) {
    245 				$use_cache = false;
    246 			}
    247 		}
    248 
    249 		if ( $cache_only ) {
    250 			$use_cache = true;
    251 		}
    252 
    253 		$data = array();
    254 		if ( file_exists( $path ) && $use_cache ) {
    255 			// phpcs:ignore WordPress.PHP.NoSilencedErrors
    256 			$data = @json_decode( $filesystem->get_contents( $path ), true );
    257 		}
    258 		// If somehow we got an invalid response, let's not persist it.
    259 		if ( isset( $data['message'] ) && false !== strpos( $data['message'], 'meta http-equiv="refresh" content="0;URL=' ) ) {
    260 			$data = false;
    261 		}
    262 
    263 		if ( $cache_only ) {
    264 			return $data;
    265 		}
    266 
    267 		if ( ! $use_cache && isset( $config['headers']['Redux-Cache-Time'] ) ) {
    268 			unset( $config['headers']['Redux-Cache-Time'] );
    269 		}
    270 
    271 		if ( empty( $data ) ) {
    272 
    273 			if ( isset( $parameters['registered_blocks'] ) ) {
    274 				$config['headers']['Redux-Registered-Blocks'] = implode( ',', $parameters['registered_blocks'] );
    275 			}
    276 
    277 			$results = $this->api_request( $config );
    278 
    279 			if ( ! empty( $results ) ) {
    280 				// phpcs:ignore WordPress.PHP.NoSilencedErrors
    281 				$data = @json_decode( $results, true );
    282 
    283 				if ( isset( $data['use_cache'] ) ) {
    284 					// phpcs:ignore WordPress.PHP.NoSilencedErrors
    285 					$data          = @json_decode( $filesystem->get_contents( $path ), true );
    286 					$data['cache'] = 'used';
    287 				} else {
    288 					if ( empty( $data ) ) {
    289 						$data = array( 'message' => $results );
    290 					}
    291 					if ( isset( $data['status'] ) && 'error' === $data['status'] ) {
    292 						wp_send_json_error( array( 'message' => $data['message'] ) );
    293 					}
    294 					$filesystem->put_contents( $path, wp_json_encode( $data ) );
    295 				}
    296 			} else {
    297 				wp_send_json_error( array( 'message' => __( 'API fetch failure.', 'redux-framework' ) ) );
    298 			}
    299 		}
    300 
    301 		if ( empty( $data ) ) {
    302 			// phpcs:ignore WordPress.PHP.NoSilencedErrors
    303 			$data = @json_decode( $filesystem->get_contents( $path ), true );
    304 			if ( $data ) {
    305 				$data['status']  = 'error';
    306 				$data['message'] = __( 'Fetching failed, used a cached version.', 'redux-framework' );
    307 			} else {
    308 				$data = array(
    309 					'message' => 'Error Fetching',
    310 				);
    311 			}
    312 		} else {
    313 			if ( ! $use_cache ) {
    314 				$data['cache'] = 'cleared';
    315 			}
    316 		}
    317 
    318 		return $data;
    319 	}
    320 
    321 	/**
    322 	 * Get library index. Support for library, collections, pages, sections all in a single request.
    323 	 *
    324 	 * @param \WP_REST_Request $request WP Rest request.
    325 	 *
    326 	 * @since 4.0.0
    327 	 */
    328 	public function get_index( \WP_REST_Request $request ) {
    329 
    330 		$parameters = $request->get_params();
    331 		$attributes = $request->get_attributes();
    332 
    333 		$type = $request->get_route();
    334 		$type = explode( '/', $type );
    335 		$type = end( $type );
    336 
    337 		if ( empty( $type ) ) {
    338 			wp_send_json_error( 'No type specified.' );
    339 		}
    340 
    341 		$config    = array(
    342 			'path' => 'library/',
    343 		);
    344 		$test_path = dirname( __FILE__ ) . '/library.json';
    345 
    346 		if ( file_exists( $test_path ) ) {
    347 			$data = json_decode( ReduxTemplates\Init::get_local_file_contents( $test_path ), true );
    348 		} else {
    349 			$parameters['no_cache'] = 1;
    350 			$data                   = $this->api_cache_fetch( $parameters, $config, 'library/' );
    351 		}
    352 
    353 		if ( isset( $data['plugins'] ) ) {
    354 			$supported = ReduxTemplates\Supported_Plugins::instance();
    355 			$supported->init( $data['plugins'] );
    356 			$data['plugins']                               = $supported::get_plugins();
    357 			$data['plugins']['redux-framework']['version'] = \Redux_Core::$version;
    358 			if ( \Redux_Helpers::mokama() ) {
    359 				if ( class_exists( 'Redux_Pro' ) ) {
    360 					$data['plugins']['redux-framework']['is_pro'] = \Redux_Pro::$version;
    361 				} else {
    362 					$data['plugins']['redux-framework']['is_pro'] = \Redux_Core::$version;
    363 				}
    364 			}
    365 			$data = $this->process_dependencies( $data, 'sections' );
    366 			$data = $this->process_dependencies( $data, 'pages' );
    367 		}
    368 
    369 		if ( class_exists( 'WP_Patterns_Registry' ) ) {
    370 			$patterns = \WP_Patterns_Registry::get_instance()->get_all_registered();
    371 			foreach ( $patterns as $k => $p ) {
    372 				$id                      = 'wp_block_pattern_' . $k;
    373 				$data['sections'][ $id ] = array(
    374 					'name'       => $p['title'],
    375 					'categories' => array( 'WP Block Patterns' ),
    376 					'source'     => 'wp_block_patterns',
    377 					'id'         => $id,
    378 				);
    379 			}
    380 		}
    381 
    382 		wp_send_json_success( $data );
    383 	}
    384 
    385 	/**
    386 	 * Filter an array recursively.
    387 	 *
    388 	 * @param array $input Array to filter.
    389 	 *
    390 	 * @return array Filtered array.
    391 	 */
    392 	private function array_filter_recursive( $input ) {
    393 		foreach ( $input as &$value ) {
    394 			if ( is_array( $value ) ) {
    395 				$value = $this->array_filter_recursive( $value );
    396 			}
    397 		}
    398 
    399 		return array_filter( $input );
    400 	}
    401 
    402 	/**
    403 	 * Method for transmitting a template the user is sharing remotely.
    404 	 *
    405 	 * @param \WP_REST_Request $request WP Rest request.
    406 	 *
    407 	 * @since 4.0.0
    408 	 */
    409 	public function share_template( \WP_REST_Request $request ) {
    410 		$parameters = $request->get_params();
    411 		$attributes = $request->get_attributes();
    412 		$parameters = $this->process_registered_blocks( $parameters );
    413 
    414 		if ( empty( $parameters ) ) {
    415 			wp_send_json_error( 'No template data found.' );
    416 		}
    417 
    418 		$config = array(
    419 			'path'           => 'template_share/',
    420 			'uid'            => get_current_user_id(),
    421 			'editor_content' => isset( $parameters['editor_content'] ) ? (string) $parameters['editor_content'] : '',
    422 			'editor_blocks'  => isset( $parameters['editor_blocks'] ) ? $parameters['editor_blocks'] : '',
    423 			'postID'         => isset( $parameters['postID'] ) ? (string) sanitize_text_field( $parameters['postID'] ) : '',
    424 			'title'          => isset( $parameters['title'] ) ? (string) sanitize_text_field( $parameters['title'] ) : 'The Title',
    425 			'type'           => isset( $parameters['type'] ) ? (string) sanitize_text_field( $parameters['type'] ) : 'page',
    426 			'categories'     => isset( $parameters['categories'] ) ? (string) sanitize_text_field( $parameters['categories'] ) : '',
    427 			'description'    => isset( $parameters['description'] ) ? (string) sanitize_text_field( $parameters['description'] ) : '',
    428 			'headers'        => array(
    429 				'Redux-Registered-Blocks' => isset( $parameters['registered_blocks'] ) ? (string) sanitize_text_field( implode( ',', $parameters['registered_blocks'] ) ) : '',
    430 			),
    431 		);
    432 
    433 		$config = $this->array_filter_recursive( $config );
    434 
    435 		if ( ! isset( $config['title'] ) ) {
    436 			wp_send_json_error( array( 'messages' => 'A title is required.' ) );
    437 		}
    438 		if ( ! isset( $config['type'] ) ) {
    439 			wp_send_json_error( array( 'messages' => 'A type is required.' ) );
    440 		}
    441 
    442 		$response = $this->api_request( $config );
    443 
    444 		// phpcs:ignore WordPress.PHP.NoSilencedErrors
    445 		$data = @json_decode( $response, true );
    446 
    447 		if ( 'success' === $data['status'] && isset( $data['url'] ) ) {
    448 			wp_send_json_success( array( 'url' => $data['url'] ) );
    449 		}
    450 
    451 		wp_send_json_error( $data );
    452 
    453 	}
    454 
    455 	/**
    456 	 * Run an API request.
    457 	 *
    458 	 * @param array $data Array related to an API request.
    459 	 *
    460 	 * @return string API response string.
    461 	 */
    462 	public function api_request( $data ) {
    463 
    464 		$api_url = $this->api_base_url;
    465 
    466 		if ( isset( $data['path'] ) ) {
    467 			if ( 'library/' === $data['path'] ) {
    468 				$api_url = 'https://files.redux.io/library.json';
    469 				$request = wp_remote_get( $api_url, array( 'timeout' => $this->timeout ) );
    470 				if ( is_wp_error( $request ) ) {
    471 					wp_send_json_error(
    472 						array(
    473 							'success'       => 'false',
    474 							'message'       => $request->get_error_messages(),
    475 							'message_types' => 'error',
    476 						)
    477 					);
    478 				}
    479 				if ( 404 === wp_remote_retrieve_response_code( $request ) ) {
    480 					wp_send_json_error(
    481 						array(
    482 							'success'       => 'false',
    483 							'message'       => 'Error fetching library, URL not found. Please try again',
    484 							'message_types' => 'error',
    485 						)
    486 					);
    487 				}
    488 				return $request['body'];
    489 			}
    490 			$api_url = $api_url . $data['path'];
    491 		}
    492 
    493 		if ( isset( $data['_locale'] ) ) {
    494 			unset( $data['_locale'] );
    495 		}
    496 		$headers = array();
    497 		if ( isset( $data['headers'] ) ) {
    498 			$headers = $data['headers'];
    499 			unset( $data['headers'] );
    500 		}
    501 		if ( isset( $data['p'] ) ) {
    502 			$headers['Redux-P'] = $data['p'];
    503 			unset( $data['p'] );
    504 		}
    505 		if ( isset( $data['path'] ) ) {
    506 			$headers['Redux-Path'] = $data['path'];
    507 			unset( $data['path'] );
    508 		}
    509 
    510 		$headers['Redux-Slug'] = \Redux_Helpers::get_hash();
    511 
    512 		$headers = wp_parse_args( $headers, $this->default_request_headers );
    513 
    514 		$headers['Content-Type'] = 'application/json; charset=utf-8';
    515 		$headers                 = array_filter( $headers );
    516 
    517 		if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
    518 			$headers['Redux-User-Agent'] = (string) sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) );
    519 		}
    520 
    521 		$headers['Redux-SiteURL'] = get_site_url( get_current_blog_id() );
    522 
    523 		$post_args = array(
    524 			'timeout'     => $this->timeout,
    525 			'body'        => wp_json_encode( $data ),
    526 			'method'      => 'POST',
    527 			'data_format' => 'body',
    528 			'redirection' => 5,
    529 			'headers'     => $headers,
    530 		);
    531 
    532 		$request = wp_remote_post( $api_url, $post_args );
    533 
    534 		// Handle redirects.
    535 		if ( ! is_wp_error( $request ) && isset( $request['http_response'] ) && $request['http_response'] instanceof \WP_HTTP_Requests_Response && method_exists( $request['http_response'], 'get_response_object' ) && strpos( $request['http_response']->get_response_object()->url, 'files.redux.io' ) !== false ) {
    536 			if ( isset( $data['no_redirect'] ) ) {
    537 				return $request['http_response']->get_response_object()->url;
    538 			} else {
    539 				$request = wp_remote_get( $request['http_response']->get_response_object()->url, array( 'timeout' => $this->timeout ) );
    540 			}
    541 		}
    542 
    543 		if ( is_wp_error( $request ) ) {
    544 
    545 			wp_send_json_error(
    546 				array(
    547 					'success'       => 'false',
    548 					'message'       => $request->get_error_messages(),
    549 					'message_types' => 'error',
    550 				)
    551 			);
    552 		}
    553 
    554 		if ( 404 === wp_remote_retrieve_response_code( $request ) ) {
    555 			return false;
    556 		}
    557 
    558 		return $request['body'];
    559 	}
    560 
    561 	/**
    562 	 * Fetch a single template.
    563 	 *
    564 	 * @param \WP_REST_Request $request WP Rest request.
    565 	 *
    566 	 * @since 4.0.0
    567 	 */
    568 	public function get_template( \WP_REST_Request $request ) {
    569 
    570 		$parameters = $request->get_params();
    571 		$attributes = $request->get_attributes();
    572 		$parameters = $this->process_registered_blocks( $parameters );
    573 
    574 		if ( in_array( $parameters['type'], array( 'sections', 'pages' ), true ) ) {
    575 			$parameters['type'] = substr_replace( $parameters['type'], '', - 1 );
    576 		}
    577 
    578 		$config = array(
    579 			'path'   => 'template/',
    580 			'id'     => sanitize_text_field( $parameters['id'] ),
    581 			'type'   => (string) sanitize_text_field( $parameters['type'] ),
    582 			'source' => isset( $parameters['source'] ) ? $parameters['source'] : '',
    583 		);
    584 
    585 		$template_response = $this->check_template_response( $parameters );
    586 
    587 		if ( 'wp_block_patterns' === $config['source'] && class_exists( 'WP_Patterns_Registry' ) ) {
    588 			$patterns = \WP_Patterns_Registry::get_instance()->get_all_registered();
    589 			$id       = explode( '_', $config['id'] );
    590 			$id       = end( $id );
    591 
    592 			if ( isset( $patterns[ $id ] ) ) {
    593 				$response = array( 'template' => $patterns[ $id ]['content'] );
    594 			}
    595 		} else {
    596 			$cache_path             = $config['type'] . DIRECTORY_SEPARATOR . $config['id'] . '.json';
    597 			$parameters['no_cache'] = 1;
    598 			$response               = $this->api_cache_fetch( $parameters, $config, $cache_path );
    599 			if ( false === $response ) {
    600 				wp_send_json_error(
    601 					array(
    602 						'success'       => 'false',
    603 						'message'       => 'Error fetching template. Please try again',
    604 						'message_types' => 'error',
    605 					)
    606 				);
    607 			}
    608 			$response = wp_parse_args( $response, $template_response );
    609 		}
    610 
    611 		if ( ! empty( $response ) && isset( $response['message'] ) ) {
    612 			$response['template'] = $response['message'];
    613 			unset( $response['message'] );
    614 		}
    615 
    616 		wp_send_json_success( $response );
    617 	}
    618 
    619 	/**
    620 	 * Check template reponse.
    621 	 *
    622 	 * @param array $parameters Parameters array.
    623 	 *
    624 	 * @return array
    625 	 * @since 4.0.0
    626 	 */
    627 	public function check_template_response( $parameters ) {
    628 		$response = array();
    629 		if ( ! \Redux_Helpers::mokama() ) {
    630 			if ( \Redux_Functions_Ex::activated() ) {
    631 				$response['left'] = 999;
    632 			} else {
    633 				$count = ReduxTemplates\Init::left( $parameters['uid'] );
    634 
    635 				$count = intval( $count ) - 1;
    636 				if ( 0 === $count ) {
    637 					$response['left'] = $count;
    638 					update_user_meta( $parameters['uid'], '_redux_templates_counts', -1 );
    639 				} elseif ( $count < 0 ) {
    640 					wp_send_json_error(
    641 						array(
    642 							'message' => 'Please activate Redux',
    643 							'left'    => 0,
    644 						)
    645 					);
    646 				} else {
    647 					update_user_meta( $parameters['uid'], '_redux_templates_counts', $count );
    648 				}
    649 				$response['left'] = $count;
    650 			}
    651 		}
    652 
    653 		return $response;
    654 	}
    655 
    656 	/**
    657 	 * Check template reponse.
    658 	 *
    659 	 * @param array $parameters Parameters array.
    660 	 *
    661 	 * @since 4.0.0
    662 	 */
    663 	public function activate_redux( $parameters ) {
    664 		if ( \Redux_Functions_Ex::activated() ) {
    665 			$response['left'] = 999;
    666 		} else {
    667 			\Redux_Core::$insights->optin();
    668 			if ( \Redux_Functions_Ex::activated() ) {
    669 				$response['left'] = 999;
    670 			} else {
    671 				$count = get_user_meta( get_current_user_id(), '_redux_templates_counts', true );
    672 				if ( false === $count ) {
    673 					$count = Init::$default_left;
    674 					update_user_meta( get_current_user_id(), '_redux_templates_counts', $count );
    675 				}
    676 				$response = array(
    677 					'left' => $count,
    678 				);
    679 			}
    680 		}
    681 		if ( 999 === $response['left'] ) {
    682 			wp_send_json_success( $response );
    683 		}
    684 		wp_send_json_error( $response );
    685 	}
    686 
    687 	/**
    688 	 * Fetch a single template.
    689 	 *
    690 	 * @param array $data Data array.
    691 	 *
    692 	 * @return array
    693 	 * @since 4.0.0
    694 	 */
    695 	public function request_verify( $data = array() ) {
    696 		$config = array(
    697 			'Redux-Version'   => REDUXTEMPLATES_VERSION,
    698 			'Redux-Multisite' => is_multisite(),
    699 			'Redux-Mokama'    => \Redux_Helpers::mokama(),
    700 			'Redux-Insights'  => \Redux_Core::$insights->tracking_allowed(),
    701 		);
    702 
    703 		// TODO - Update this with the EDD key or developer key.
    704 		$config['Redux-API-Key'] = '';
    705 
    706 		if ( ! empty( \Redux_Core::$pro_loaded ) && \Redux_Core::$pro_loaded ) {
    707 			$config['Redux-Pro'] = \Redux_Core::$pro_loaded;
    708 		}
    709 		$data = wp_parse_args( $data, $config );
    710 
    711 		return $data;
    712 	}
    713 
    714 
    715 	/**
    716 	 * Get all saved blocks (reusable blocks).
    717 	 *
    718 	 * @param \WP_REST_Request $request WP Rest request.
    719 	 *
    720 	 * @since 4.0.0
    721 	 */
    722 	public function get_saved_blocks( \WP_REST_Request $request ) {
    723 		$args      = array(
    724 			'post_type'   => 'wp_block',
    725 			'post_status' => 'publish',
    726 		);
    727 		$r         = wp_parse_args( null, $args );
    728 		$get_posts = new \WP_Query();
    729 		$wp_blocks = $get_posts->query( $r );
    730 		wp_send_json_success( $wp_blocks );
    731 	}
    732 
    733 	/**
    734 	 * Delete a single saved (reusable) block
    735 	 *
    736 	 * @param \WP_REST_Request $request WP Rest request.
    737 	 *
    738 	 * @since 4.0.0
    739 	 */
    740 	public function delete_saved_block( \WP_REST_Request $request ) {
    741 		$parameters = $request->get_params();
    742 		$attributes = $request->get_attributes();
    743 		if ( ! isset( $parameters['block_id'] ) ) {
    744 			return wp_send_json_error(
    745 				array(
    746 					'status'  => 'error',
    747 					'message' => 'Missing block_id.',
    748 				)
    749 			);
    750 		}
    751 
    752 		$block_id      = (int) sanitize_text_field( wp_unslash( $parameters['block_id'] ) );
    753 		$deleted_block = wp_delete_post( $block_id );
    754 
    755 		wp_send_json_success( $deleted_block );
    756 	}
    757 
    758 	/**
    759 	 * Record that the user has used the welcome guide.
    760 	 *
    761 	 * @param \WP_REST_Request $request WP Rest request.
    762 	 *
    763 	 * @since 4.0.0
    764 	 */
    765 	public function welcome_guide( \WP_REST_Request $request ) {
    766 		$parameters = $request->get_params();
    767 		$attributes = $request->get_attributes();
    768 		if ( ! isset( $parameters['uid'] ) ) {
    769 			return wp_send_json_error(
    770 				array(
    771 					'status'  => 'error',
    772 					'message' => 'User ID required.',
    773 				)
    774 			);
    775 		}
    776 		update_user_meta( $parameters['uid'], '_redux_welcome_guide', '1' );
    777 		wp_send_json_success( array( 'status' => 'success' ) );
    778 	}
    779 
    780 	/**
    781 	 * Method used to register all rest endpoint hooks.
    782 	 *
    783 	 * @since 4.0.0
    784 	 */
    785 	public function register_api_hooks() {
    786 
    787 		$hooks = array(
    788 			'library'            => array(
    789 				'callback' => 'get_index',
    790 			),
    791 			'pages'              => array(
    792 				'callback' => 'get_index',
    793 			),
    794 			'sections'           => array(
    795 				'callback' => 'get_index',
    796 			),
    797 			'collections'        => array(
    798 				'callback' => 'get_index',
    799 			),
    800 			'feedback'           => array(
    801 				'callback' => 'send_feedback',
    802 			),
    803 			'suggestion'         => array(
    804 				'callback' => 'send_suggestion',
    805 			),
    806 			'template'           => array(
    807 				'callback' => 'get_template',
    808 			),
    809 			'share'              => array(
    810 				'method'   => 'POST',
    811 				'callback' => 'share_template',
    812 			),
    813 			'get_saved_blocks'   => array(
    814 				'callback' => 'get_saved_blocks',
    815 			),
    816 			'delete_saved_block' => array(
    817 				'method'   => 'POST',
    818 				'callback' => 'delete_saved_block',
    819 			),
    820 			'activate'           => array(
    821 				'method'   => 'GET',
    822 				'callback' => 'activate_redux',
    823 			),
    824 			'plugin-install'     => array(
    825 				'method'   => 'GET',
    826 				'callback' => 'plugin_install',
    827 			),
    828 			'license'            => array(
    829 				'method'   => 'GET',
    830 				'callback' => 'license',
    831 			),
    832 			'license-validate'   => array(
    833 				'method'   => 'GET',
    834 				'callback' => 'validate_license',
    835 			),
    836 			'license-activate'   => array(
    837 				'method'   => 'GET',
    838 				'callback' => 'activate_license',
    839 			),
    840 			'license-deactivate' => array(
    841 				'method'   => 'GET',
    842 				'callback' => 'deactivate_license',
    843 			),
    844 			'get-pro-url'        => array(
    845 				'method'   => 'GET',
    846 				'callback' => 'get_pro_url',
    847 			),
    848 			'opt_out'            => array(
    849 				'method'   => 'GET',
    850 				'callback' => 'opt_out_account',
    851 			),
    852 			'welcome'            => array(
    853 				'method'   => 'POST',
    854 				'callback' => 'welcome_guide',
    855 			),
    856 			'nps'                => array(
    857 				'method'   => 'POST',
    858 				'callback' => 'send_nps',
    859 			),
    860 		);
    861 		$fs    = \Redux_Filesystem::get_instance();
    862 
    863 		foreach ( $hooks as $route => $data ) {
    864 			$methods = array( 'GET', 'POST' );
    865 			if ( isset( $data['method'] ) ) {
    866 				$methods = explode( ',', $data['method'] );
    867 			}
    868 
    869 			foreach ( $methods as $method ) {
    870 				$args = array(
    871 					'methods'  => $method,
    872 					'callback' => array( $this, $data['callback'] ),
    873 					'args'     => array(),
    874 				);
    875 				if ( defined( 'REDUX_PLUGIN_FILE' ) && ! $fs->file_exists( trailingslashit( dirname( REDUX_PLUGIN_FILE ) ) . 'local_developer.txt' ) ) {
    876 					$args['permission_callback'] = function () {
    877 						return current_user_can( 'install_plugins' );
    878 					};
    879 				} else {
    880 					$args['permission_callback'] = '__return_true';
    881 				}
    882 
    883 				register_rest_route(
    884 					'redux/v1/templates',
    885 					$route,
    886 					$args
    887 				);
    888 			}
    889 		}
    890 
    891 	}
    892 
    893 	/**
    894 	 * Install plugin endpoint.
    895 	 *
    896 	 * @param \WP_REST_Request $request WP Rest request.
    897 	 *
    898 	 * @since 4.0.0
    899 	 */
    900 	public function plugin_install( \WP_REST_Request $request ) {
    901 
    902 		$data = $request->get_params();
    903 
    904 		if ( empty( $data['slug'] ) ) {
    905 			wp_send_json_error(
    906 				array(
    907 					'error' => __( 'Slug not specified.', 'redux-framework' ),
    908 				)
    909 			);
    910 		}
    911 
    912 		$slug = (string) sanitize_text_field( $data['slug'] );
    913 		if ( ! empty( $data['redux_pro'] ) ) {
    914 			if ( \Redux_Helpers::mokama() || 'redux-pro' === $slug ) {
    915 				$config                 = array(
    916 					'path'        => 'installer/',
    917 					'slug'        => $slug,
    918 					'no_redirect' => true,
    919 				);
    920 				$parameters['no_cache'] = 1;
    921 
    922 				if ( 'redux-pro' === $slug ) {
    923 
    924 					$lic_status = get_option( 'redux_pro_license_status', false );
    925 
    926 					if ( 'valid' !== $lic_status && 'active' !== $lic_status ) {
    927 						$status = array(
    928 							'error' => __( 'Redux Pro license not active, please activate.', 'redux-framework' ),
    929 						);
    930 					} else {
    931 						$array   = array(
    932 							'edd_action' => 'get_version',
    933 						);
    934 						$request = $this->do_license_request( $array );
    935 
    936 						if ( isset( $request['download_link'] ) ) {
    937 							$status = ReduxTemplates\Installer::run( $slug, $request['download_link'] );
    938 						} else {
    939 							$status = array(
    940 								'error' => __( 'Invalid license key.', 'redux-framework' ),
    941 							);
    942 							delete_option( 'redux_pro_license_status' );
    943 						}
    944 					}
    945 				} else {
    946 					$response = $this->api_cache_fetch( $parameters, $config, false, false );
    947 					if ( isset( $response['message'] ) && false !== strpos( $response['message'], 'redux.io' ) ) {
    948 						$status = ReduxTemplates\Installer::run( $slug, $response['message'] );
    949 					} else {
    950 						if ( isset( $response['error'] ) && ! empty( $response['error'] ) ) {
    951 							$status = array(
    952 								'error' => $response['error'],
    953 							);
    954 						} else {
    955 							$status = array(
    956 								'error' => __( 'A valid Redux Pro subscription is required.', 'redux-framework' ),
    957 							);
    958 						}
    959 					}
    960 				}
    961 			} else {
    962 				$status = array(
    963 					'error' => __( 'A valid Redux Pro subscription is required.', 'redux-framework' ),
    964 				);
    965 			}
    966 		} else {
    967 			$status = ReduxTemplates\Installer::run( $slug );
    968 		}
    969 
    970 		if ( isset( $status['error'] ) ) {
    971 			wp_send_json_error( $status );
    972 		}
    973 		if ( 'otter-blocks' === $slug ) {
    974 			update_option( 'themeisle_blocks_settings_default_block', false );
    975 		}
    976 		wp_send_json_success( $status );
    977 	}
    978 
    979 	/**
    980 	 * Check the license key.
    981 	 *
    982 	 * @since 4.1.18
    983 	 * @return bool|array
    984 	 */
    985 	protected function check_license_key() {
    986 		$lic = get_option( 'redux_pro_license_key' );
    987 		if ( empty( $lic ) ) {
    988 			delete_option( 'redux_pro_license_status' );
    989 			return array(
    990 				'success'       => 'false',
    991 				'message'       => 'No license found.',
    992 				'message_types' => 'error',
    993 			);
    994 		}
    995 		return true;
    996 	}
    997 
    998 	/**
    999 	 * Run the license API calls.
   1000 	 *
   1001 	 * @param \WP_REST_Request $request WP Rest request.
   1002 	 * @since 4.1.18
   1003 	 */
   1004 	public function license( \WP_REST_Request $request ) {
   1005 		$data = $request->get_params();
   1006 
   1007 		if ( ! isset( $data['key'] ) || ( isset( $data['key'] ) && empty( $data['key'] ) ) ) {
   1008 			wp_send_json_error(
   1009 				array(
   1010 					'success'       => 'false',
   1011 					'message'       => 'License key not provided or empty.',
   1012 					'message_types' => 'error',
   1013 				)
   1014 			);
   1015 		}
   1016 
   1017 		$array    = array(
   1018 			'edd_action' => 'check_license',
   1019 			'license'    => $data['key'],
   1020 		);
   1021 		$response = $this->do_license_request( $array );
   1022 
   1023 		if ( isset( $response['license'] ) && in_array( $response['license'], array( 'valid', 'site_inactive', 'inactive' ), true ) ) {
   1024 			if ( 'valid' === $response['license'] ) {
   1025 				wp_send_json_success( array( 'status' => 'success' ) );
   1026 			} else {
   1027 				if ( 0 === $response['data']['activations_left'] ) {
   1028 					delete_option( 'redux_pro_license_key' );
   1029 					wp_send_json_error(
   1030 						array(
   1031 							'status'  => 'error',
   1032 							'message' => __( 'You have reached your activation limits for Redux Pro', 'redux-framework' ),
   1033 						)
   1034 					);
   1035 				}
   1036 				update_option( 'redux_pro_license_key', $data['key'] );
   1037 
   1038 				$array = array(
   1039 					'edd_action' => 'activate_license',
   1040 				);
   1041 
   1042 				$response = $this->do_license_request( $array );
   1043 				if ( isset( $response['license'] ) && 'valid' === $response['license'] ) {
   1044 					\Redux_Functions_Ex::set_activated();
   1045 					wp_send_json_success( $response );
   1046 				}
   1047 			}
   1048 		}
   1049 		wp_send_json_error(
   1050 			array(
   1051 				'status'   => 'error',
   1052 				'msg'      => __( 'Invalid license key.', 'redux-framework' ),
   1053 				'response' => $response,
   1054 			)
   1055 		);
   1056 	}
   1057 
   1058 	/**
   1059 	 * Validate a license key.
   1060 	 *
   1061 	 * @param \WP_REST_Request $request WP Rest request.
   1062 	 * @since 4.1.18
   1063 	 */
   1064 	public function validate_license( \WP_REST_Request $request ) {
   1065 
   1066 		$data = $request->get_params();
   1067 
   1068 		if ( ! isset( $data['key'] ) || ( isset( $data['key'] ) && empty( $data['key'] ) ) ) {
   1069 			wp_send_json_error(
   1070 				array(
   1071 					'success'       => 'false',
   1072 					'message'       => 'License key not provided or empty.',
   1073 					'message_types' => 'error',
   1074 				)
   1075 			);
   1076 		}
   1077 
   1078 		$array    = array(
   1079 			'edd_action' => 'check_license',
   1080 			'license'    => $data['key'],
   1081 		);
   1082 		$response = $this->do_license_request( $array );
   1083 
   1084 		if ( isset( $response['license'] ) && in_array( $response['key'], array( 'valid', 'site_inactive' ), true ) ) {
   1085 			update_option( 'redux_pro_license_status', $data['key'] );
   1086 			wp_send_json_success( $response );
   1087 		} else {
   1088 			wp_send_json_error(
   1089 				$response
   1090 			);
   1091 		}
   1092 	}
   1093 
   1094 	/**
   1095 	 * Activate a license key.
   1096 	 *
   1097 	 * @param \WP_REST_Request $request WP Rest request.
   1098 	 * @since 4.1.18
   1099 	 */
   1100 	public function activate_license( \WP_REST_Request $request ) {
   1101 		$check = $this->check_license_key();
   1102 		if ( is_array( $check ) ) {
   1103 			wp_send_json_error( $check );
   1104 		}
   1105 		$lic = get_option( 'redux_pro_license_key' );
   1106 		if ( empty( $lic ) ) {
   1107 			delete_option( 'redux_pro_license_status' );
   1108 			wp_send_json_error(
   1109 				array(
   1110 					'success'       => 'false',
   1111 					'message'       => 'No license found.',
   1112 					'message_types' => 'error',
   1113 				)
   1114 			);
   1115 		}
   1116 
   1117 		$array   = array(
   1118 			'edd_action' => 'activate_license',
   1119 		);
   1120 		$request = $this->do_license_request( $array );
   1121 
   1122 		if ( isset( $request['license'] ) && 'valid' === $request['license'] ) {
   1123 			wp_send_json_success( $request );
   1124 		}
   1125 		wp_send_json_error(
   1126 			array(
   1127 				'success'       => 'false',
   1128 				'message'       => 'License is not valid.',
   1129 				'message_types' => 'error',
   1130 			)
   1131 		);
   1132 	}
   1133 
   1134 	/**
   1135 	 * Deactivate a license key.
   1136 	 *
   1137 	 * @since 4.1.18
   1138 	 */
   1139 	public function deactivate_license() {
   1140 		$check = $this->check_license_key();
   1141 		if ( is_array( $check ) ) {
   1142 			wp_send_json_error( $check );
   1143 		}
   1144 		$lic = get_option( 'redux_pro_license_key' );
   1145 		if ( empty( $lic ) ) {
   1146 			wp_send_json_error(
   1147 				array(
   1148 					'success'       => 'false',
   1149 					'message'       => 'No license found.',
   1150 					'message_types' => 'error',
   1151 				)
   1152 			);
   1153 		}
   1154 
   1155 		$array   = array(
   1156 			'edd_action' => 'deactivate_license',
   1157 		);
   1158 		$request = $this->do_license_request( $array );
   1159 		if ( isset( $request['license'] ) && 'deactivated' === $request['license'] ) {
   1160 			delete_option( 'redux_pro_license_key' );
   1161 			wp_send_json_success( $request );
   1162 		}
   1163 		wp_send_json_error(
   1164 			array(
   1165 				'success'       => 'false',
   1166 				'message'       => 'License is not valid.',
   1167 				'message_types' => 'error',
   1168 			)
   1169 		);
   1170 	}
   1171 
   1172 	/**
   1173 	 * Get the Redux Pro download URL.
   1174 	 *
   1175 	 * @since 4.1.18
   1176 	 */
   1177 	public function get_pro_url() {
   1178 		$lic_status = get_option( 'redux_pro_license_status', 'inactive' );
   1179 
   1180 		if ( 'valid' !== $lic_status && 'active' !== $lic_status ) {
   1181 			wp_send_json_error(
   1182 				array(
   1183 					'success'       => 'false',
   1184 					'message'       => 'License not active, please activate.',
   1185 					'message_types' => 'error',
   1186 				)
   1187 			);
   1188 		}
   1189 
   1190 		$array   = array(
   1191 			'edd_action' => 'get_version',
   1192 		);
   1193 		$request = $this->do_license_request( $array );
   1194 
   1195 		if ( isset( $request['download_link'] ) ) {
   1196 			wp_send_json_success( $request['download_link'] );
   1197 		}
   1198 
   1199 		wp_send_json_error(
   1200 			array(
   1201 				'success'       => 'false',
   1202 				'message'       => 'Could not recover Pro download URL.',
   1203 				'message_types' => 'error',
   1204 			)
   1205 		);
   1206 	}
   1207 
   1208 	/**
   1209 	 * Run the license API calls.
   1210 	 *
   1211 	 * @param array $args Array of args.
   1212 	 * @since 4.1.18
   1213 	 * @return mixed
   1214 	 */
   1215 	private function do_license_request( $args ) {
   1216 
   1217 		$defaults = array(
   1218 			'item_name' => 'Redux Pro',
   1219 			'url'       => network_site_url(),
   1220 			'license'   => get_option( 'redux_pro_license_key' ),
   1221 		);
   1222 		$args     = wp_parse_args( $args, $defaults );
   1223 
   1224 		if ( ! isset( $args['edd_action'] ) || ( isset( $args['edd_action'] ) && empty( $args['edd_action'] ) ) ) {
   1225 			wp_send_json_error(
   1226 				array(
   1227 					'success'       => 'false',
   1228 					'message'       => 'Missing edd_action.',
   1229 					'message_types' => 'error',
   1230 				)
   1231 			);
   1232 		}
   1233 
   1234 		if ( 'check_license' !== $args['edd_action'] ) {
   1235 			$check = $this->check_license_key();
   1236 			if ( is_array( $check ) ) {
   1237 				wp_send_json_error( $check );
   1238 			}
   1239 		}
   1240 
   1241 		$url = add_query_arg( $args, $this->license_base_url );
   1242 
   1243 		$request = wp_remote_get( $url, array( 'timeout' => $this->timeout ) );
   1244 		if ( is_wp_error( $request ) ) {
   1245 			wp_send_json_error(
   1246 				array(
   1247 					'success'       => 'false',
   1248 					'message'       => $request->get_error_messages(),
   1249 					'message_types' => 'error',
   1250 				)
   1251 			);
   1252 		}
   1253 		if ( 404 === wp_remote_retrieve_response_code( $request ) ) {
   1254 			wp_send_json_error(
   1255 				array(
   1256 					'success'       => 'false',
   1257 					'message'       => 'Our API appears to be down... please try again later.',
   1258 					'message_types' => 'error',
   1259 				)
   1260 			);
   1261 		}
   1262 		$data = json_decode( $request['body'], true );
   1263 		if ( isset( $data['license'] ) ) {
   1264 			update_option( 'redux_pro_license_status', $data['license'] );
   1265 		}
   1266 		return $data;
   1267 	}
   1268 
   1269 	/**
   1270 	 * Send the NPS value.
   1271 	 *
   1272 	 * @param \WP_REST_Request $request WP Rest request.
   1273 	 * @since 4.1.19
   1274 	 */
   1275 	public function send_nps( \WP_REST_Request $request ) {
   1276 		$data = $request->get_params();
   1277 
   1278 		if ( empty( $data['nps'] ) ) {
   1279 			wp_send_json_error(
   1280 				array(
   1281 					'error' => __( 'NPS not specified.', 'redux-framework' ),
   1282 				)
   1283 			);
   1284 		}
   1285 
   1286 		$nps         = (string) sanitize_text_field( $data['nps'] );
   1287 		$the_request = array(
   1288 			'path' => 'nps',
   1289 			'nps'  => $nps,
   1290 		);
   1291 		$response    = $this->api_request( $the_request );
   1292 		if ( false === $response ) {
   1293 			wp_send_json_error(
   1294 				array(
   1295 					'message' => __( 'Could not record score.', 'redux-framework' ),
   1296 				)
   1297 			);
   1298 		}
   1299 		wp_send_json_success( json_decode( $response ) );
   1300 	}
   1301 
   1302 }