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 }