rest_authorization_required_code() ) ); } return true; } /** * Makes sure the current user has access to READ the settings APIs. * * @param WP_REST_Request $request Full data about the request. * * @return WP_Error|boolean * @since 3.0.0 */ public function get_items_permissions_check( $request ) { if ( ! Wpfnl_functions::wpfnl_rest_check_manager_permissions( 'templates' ) ) { return new WP_Error( 'wpfunnels_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'wpfnl' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base . 'get_templates', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_templates' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), ), ) ); /* * Building a funnel out of a template, and clearing what the library * has cached. The template library screen is React (see * src/admin/pages/FunnelTemplates) and talks REST; the Vue library * still embedded in the funnel canvas and Store Checkout posts the same * work to admin-ajax, so both routes here hand straight over to the * handlers those calls already run rather than restating them. */ register_rest_route( $this->namespace, '/' . $this->rest_base . 'import-funnel', array( array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'import_funnel' ), 'permission_callback' => array( $this, 'edit_items_permissions_check' ), ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . 'finish-import', array( array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'finish_import' ), 'permission_callback' => array( $this, 'edit_items_permissions_check' ), ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . 'sync', array( array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'sync_library' ), 'permission_callback' => array( $this, 'edit_items_permissions_check' ), ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . 'get_template_type_id', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_template_type_id' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), ), ) ); } /** * Prepare a single setting object for response. * * @param $request * * @return WP_REST_Response */ /** * Prepare a single setting object for response. * * @param $request * * @return WP_REST_Response */ /** * Makes sure the current user may build a funnel from a template. * * @param WP_REST_Request $request Full data about the request. * * @return WP_Error|boolean * @since 3.13.5 */ public function edit_items_permissions_check( $request ) { if ( ! Wpfnl_functions::wpfnl_rest_check_manager_permissions( 'templates', 'edit' ) ) { return new WP_Error( 'wpfunnels_rest_cannot_edit', __( 'Sorry, you cannot edit this resource.', 'wpfnl' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Create a funnel from a library template. * * First of the three calls that import a template: this creates the funnel * post and answers with its id, the steps are then created one by one * against the step controller, and `finish_import()` below closes the * import off. * * @param WP_REST_Request $request Full data about the request. * * @return WP_REST_Response The importer's own response, carrying `funnelID`. * @since 3.13.5 */ public function import_funnel( $request ) { $payload = $this->get_import_payload( $request ); return rest_ensure_response( Wpfnl::$instance->template_manager->import_funnel( $payload ) ); } /** * Close off a template import. * * Answers with the `redirectLink` the caller should follow — where the * funnel wants to be opened depends on what was imported, so the server * decides it. * * @param WP_REST_Request $request Full data about the request. * * @return WP_REST_Response The importer's own response. * @since 3.13.5 */ public function finish_import( $request ) { $payload = $this->get_import_payload( $request ); return rest_ensure_response( Wpfnl::$instance->template_manager->after_funnel_creation( $payload ) ); } /** * Drop everything the library has cached, so the next listing is fetched * fresh from the template server. * * @param WP_REST_Request $request Full data about the request. * * @return WP_REST_Response * @since 3.13.5 */ public function sync_library( $request ) { $settings = Wpfnl::$instance->module_manager->get_admin_modules( 'settings' ); if ( ! $settings ) { return rest_ensure_response( array( 'success' => false ) ); } return rest_ensure_response( $settings->clear_templates_data( array() ) ); } /** * The request body as the importer expects to read it. * * The importer was written against `wp_ajax_helper`, which handed it a * plain array; REST params are the same shape, so this only unwraps them. * * @param WP_REST_Request $request Full data about the request. * * @return array Payload for the importer. * @since 3.13.5 */ private function get_import_payload( $request ) { $payload = $request->get_json_params(); if ( ! is_array( $payload ) ) { $payload = $request->get_params(); } // `source` names which registered source to import through, and there // is only one the library offers. if ( empty( $payload['source'] ) ) { $payload['source'] = 'remote'; } return $payload; } public function get_templates( $request ) { $type_param = isset( $_GET['type'] ) ? sanitize_key( $_GET['type'] ) : 'wc'; $store_checkout_param = isset( $_GET['store_checkout'] ) ? sanitize_key( $_GET['store_checkout'] ) : 'no'; // Map frontend goal types to API template types. // 'store_checkout' (improve-checkout goal) passes through as-is. // 'sales' / 'order-value' goals send 'wc'; keep backward-compat mapping here too. $type_map = array( 'sales' => 'wc', 'lead' => 'lead', ); $funnel_template_type = isset( $type_map[ $type_param ] ) ? $type_map[ $type_param ] : $type_param; $step = isset( $_GET['step'] ) ? $_GET['step'] : false; $builder = isset( $_GET['builder'] ) ? $_GET['builder'] : false; if( 'yes' === $store_checkout_param ) { $funnel_template_type = 'store_checkout'; } $templates = $this->get_funnels_data( $funnel_template_type, $step, array(), false, $builder ); $templates = $this->prepare_custom_step($templates); $templates['success'] = true; return $this->prepare_item_for_response( $templates, $request ); } /** * Prepares custom steps. * Allow user to create custom step from landing page. * * @param array $templates The templates to prepare. * @return array * * @since 3.4.15 */ public function prepare_custom_step($templates){ if( is_array( $templates ) ){ if( isset( $templates['steps'] ) && is_array( $templates['steps'] ) ){ foreach( $templates['steps'] as $key => $step ){ if( is_array( $step ) && isset( $step['step_type'] ) && 'landing' === $step['step_type'] ){ $custom_step = $step; $custom_step['step_type'] = 'custom'; if( isset( $custom_step['title'] ) ){ $custom_step['title'] = str_ireplace('Landing', '', $custom_step['title']); } $templates['steps'][] = $custom_step; } } } } return $templates; } /** * Send http request * * @param $url * @param $args * * @return array */ public static function remote_get( $url, $args ) { $response = wp_remote_get( $url, $args ); // bail if there is an error if ( is_wp_error( $response ) || ! is_array( $response ) || ! isset( $response['body'] ) ) { return array( 'success' => false, 'message' => is_wp_error( $response ) ? $response->get_error_message() : __( 'Unexpected response from the template server.', 'wpfnl' ), 'data' => $response, ); } // Decode the results. $results = json_decode( $response['body'], true ); // bail if there is no result found if ( ! is_array( $results ) ) { return new \WP_Error( 'unexpected_data_format', 'Data was not returned in the expected format.' ); } return array( 'success' => true, 'message' => 'Data successfully retrieved', 'data' => json_decode( wp_remote_retrieve_body( $response ), true ), ); } /** * Get all funnels of the specific builder * * @param bool $type * @param bool $isStep * @param array $args * @param bool $force_update * @param bool $builder * * @return bool|mixed|void */ public static function get_funnels( $type = false, $isStep = false, $args = array(), $force_update = false, $builder = false ) { $builder_type = $builder ? $builder : Wpfnl_functions::get_builder_type(); $cache_key = 'wpfunnels_remote_template_data_' . $type . '_' . $builder_type . '_' . WPFNL_VERSION; $data = get_transient( $cache_key ); // A cached miss is stored as an empty array, which is falsy, so check // against false explicitly or a failed fetch would be retried on every // single request. An explicit refresh always goes out to the network. if ( false !== $data && ! $force_update ) { return; } if ( $type && ( $force_update || false === $data ) ) { if ( false === $data ) { $data = array(); } // Keep the blocking request well inside the web server's FastCGI/proxy // timeout: this runs during an admin REST request, and up to four of // these calls can happen back to back. $timeout = ( $force_update ) ? 30 : 15; $template_url = self::$all_funnels_api_url; if ( 'oxygen' === $builder_type ) { $template_url = 'https://oxygentemplates.getwpfunnels.com/wp-json/wpfunnels/v1/get_all_funnels/'; } elseif ( 'bricks' === $builder_type ) { $template_url = 'https://brickstemplates.getwpfunnels.com/wp-json/wpfunnels/v1/get_all_funnels/'; } // get all templates $params = array( 'per_page' => 100, 'offset' => 0, 'builder' => $builder_type, 'template_type' => $type, 'template_url' => $template_url, ); if( !defined('QUBELY_VERSION') ){ $params['gutenberg_type'] = 'wpf_native_gutenberg'; } $url = add_query_arg( $params, $template_url ); $template_data = self::remote_get( $url, array( 'timeout' => $timeout, ) ); if ( ! is_array( $template_data ) || ! $template_data['success'] ) { $url = add_query_arg( $params, WPFNL_MIDDLEMAN_TEMPLATE_URL ); $template_data = self::remote_get( $url, array( 'timeout' => $timeout, ) ); if ( ! is_array( $template_data ) || ! $template_data['success'] ) { set_transient( $cache_key, array(), 15 * MINUTE_IN_SECONDS ); return false; } } // get all steps $steps = array(); $templates = array(); if ( $template_data['data'] ) { foreach ( $template_data['data'] as $key => $template ) { $i = 0; $thankyou_count = 0; if( isset($template['page_builder']) && 'gutenberg' === $template['page_builder'] ){ if( !defined('QUBELY_VERSION') && 'wpf_native_gutenberg' !== $template['gutenberg_type'] ){ continue; } } if( isset($template['is_store_checkout']) && $template['is_store_checkout'] && 'store_checkout' !== $type ) { continue; } foreach ( $template['steps'] as $_step ) { if ( $thankyou_count && 'thankyou' === $_step['step_type'] ) { continue; } if ( 0 == $i ) { $template_data['data'][ $key ]['link'] = $_step['link']; } $_step['funnel_name'] = $template['title']; $_step['template_type'] = isset( $template['templateType'] ) ? ( 'free' === $template['templateType'] ? 'free' : 'pro' ) : 'free'; $steps[] = $_step; if ( 'thankyou' === $_step['step_type'] ) { $thankyou_count++; } $i++; } array_push($templates, $template_data['data'][ $key ]); } } // fetch the funnel categories from the remote server $cat_url = self::$funnel_categories_api_url; if ( 'oxygen' === $builder_type ) { $cat_url = 'https://oxygentemplates.getwpfunnels.com/wp-json/wp/v2/template_industries/'; } elseif ( 'bricks' === $builder_type ) { $cat_url = 'https://brickstemplates.getwpfunnels.com/wp-json/wp/v2/template_industries/'; } $params = array( 'per_page' => 100, 'category_url' => $cat_url, ); $url = add_query_arg( $params, $cat_url ); $categories_data = self::remote_get( $url, array( 'timeout' => $timeout, ) ); if ( ! is_array( $categories_data ) || ! $categories_data['success'] ) { $url = add_query_arg( $params, WPFNL_MIDDLEMAN_TEMPLATE_CATEGORY_URL ); $categories_data = self::remote_get( $url, array( 'timeout' => $timeout, ) ); if ( ! is_array( $categories_data ) || ! $categories_data['success'] ) { set_transient( $cache_key, array(), 15 * MINUTE_IN_SECONDS ); return false; } } $data['templates'] = $templates; $data['steps'] = $steps; $data['categories'] = $categories_data['data']; update_option( WPFNL_TEMPLATES_OPTION_KEY . '_' . $type . '_' . $builder_type, $data, 'no' ); set_transient( $cache_key, $data, 24 * HOUR_IN_SECONDS ); return false; } } /** * Get funnel templates data * * @param array $args * @param bool $force_update * * @return array|mixed|void * @since 1.0.0 */ public function get_funnels_data( $type = false, $step = false, $args = array(), $force_update = false, $builder = false ) { self::get_funnels( $type, $step, $args, $force_update, $builder ); $builder_type = $builder ? $builder : Wpfnl_functions::get_builder_type(); $template_data = get_option( WPFNL_TEMPLATES_OPTION_KEY . '_' . $type . '_' . $builder_type ); // Fallback for backward compatibility if data is not found with builder if ( empty( $template_data ) && !$builder ) { $template_data = get_option( WPFNL_TEMPLATES_OPTION_KEY . '_' . $type ); } if ( empty( $template_data ) ) { return array(); } return $template_data; } /** * Get step from template library site by step ID * * @param $step_id * @param bool $force_update * * @return array * @since 1.0.0 */ public static function get_step( $step_id, $force_update = false ) { $timeout = ( $force_update ) ? 25 : 8; $params = array( '_fields' => 'id,title,link,slug,featured_media,post_meta,rawData,steps,divi_content,featured_image,steps_order,builder,industry,is_pro,type,step_type,funnel_id,funnel_name,_qubely_css,_qubely_interaction_json,__qubely_available_blocks', 'url' => self::get_remote_funnel_steps_api_url() . $step_id, ); $url = add_query_arg( $params, self::get_remote_funnel_steps_api_url() . $step_id ); $api_args = array( 'timeout' => $timeout, ); $response = ( new TemplateLibraryController() )->remote_get( $url, $api_args ); if ( is_array($response) && isset( $response['success'] ) ) { $step = $response['data']; return array( 'title' => ( isset( $step['title']['rendered'] ) ) ? $step['title']['rendered'] : '', 'post_meta' => ( isset( $step['post_meta'] ) ) ? $step['post_meta'] : '', 'data' => $step, 'content' => isset( $response['data']['content']['rendered'] ) ? $response['data']['content']['rendered'] : '', 'rawData' => isset( $response['data']['rawData'] ) ? $response['data']['rawData'] : '', 'divi_content' => isset( $response['data']['divi_content'] ) ? $response['data']['divi_content'] : '', 'message' => $response['message'], 'success' => $response['success'], ); } $url = add_query_arg( $params, WPFNL_MIDDLEMAN_STEP_URL ); $response = ( new TemplateLibraryController() )->remote_get( $url, $api_args ); if ( is_array($response) && isset( $response['success'] ) ) { $step = $response['data']; return array( 'title' => ( isset( $step['title']['rendered'] ) ) ? $step['title']['rendered'] : '', 'post_meta' => ( isset( $step['post_meta'] ) ) ? $step['post_meta'] : '', 'data' => $step, 'content' => isset( $response['data']['content']['rendered'] ) ? $response['data']['content']['rendered'] : '', 'rawData' => isset( $response['data']['rawData'] ) ? $response['data']['rawData'] : '', 'divi_content' => isset( $response['data']['divi_content'] ) ? $response['data']['divi_content'] : '', 'message' => $response['message'], 'success' => $response['success'], ); } if ( is_wp_error( $response ) ) { return array( 'title' => '', 'post_meta' => array(), 'message' => $response->get_error_message(), 'data' => array(), 'success' => false, 'content' => '', ); } return array( 'title' => '', 'post_meta' => array(), 'message' => isset( $response['message'] ) ? $response['message'] : '', 'data' => isset( $response['data'] ) ? $response['data'] : array(), 'success' => isset( $response['success'] ) ? $response['success'] : false, 'content' => '', ); } public static function get_funnel( $funnel_id, $force_update = false ) { $timeout = ( $force_update ) ? 25 : 8; $params = array( '_fields' => 'id,title,link,slug,featured_media,funnel_data,_funnel_data', 'url' => self::get_remote_funnel_api_url().$funnel_id, ); $url = add_query_arg( $params, self::get_remote_funnel_api_url() . $funnel_id ); $api_args = array( 'timeout' => $timeout, ); $response = ( new TemplateLibraryController() )->remote_get( $url, $api_args ); if (is_array($response) && isset( $response['success'] )) { $funnel = $response['data']; return array( 'funnel_id' => $funnel_id, 'funnel_data' => $funnel['funnel_data'] ?? '', '_funnel_data' => $funnel['_funnel_data'] ?? '', 'title' => ( isset( $funnel['title']['rendered'] ) ) ? $funnel['title']['rendered'] : '', 'success' => $response['success'], ); } $url = add_query_arg( $params, WPFNL_MIDDLEMAN_SINGLE_TEMPLATE_URL ); $response = ( new TemplateLibraryController() )->remote_get( $url, $api_args ); if ( is_array($response) && isset( $response['success'] ) ) { $funnel = $response['data']; return array( 'funnel_id' => $funnel_id, 'funnel_data' => $funnel['funnel_data'] ?? '', '_funnel_data' => $funnel['_funnel_data'] ?? '', 'title' => ( isset( $funnel['title']['rendered'] ) ) ? $funnel['title']['rendered'] : '', 'success' => $response['success'], ); } if ( is_wp_error( $response ) ) { return array( 'title' => '', 'post_meta' => array(), 'message' => $response->get_error_message(), 'data' => array(), 'success' => false, 'content' => '', ); } return array( 'title' => '', 'post_meta' => array(), 'message' => isset( $response['message'] ) ? $response['message'] : '', 'data' => isset( $response['data'] ) ? $response['data'] : array(), 'success' => isset( $response['success'] ) ? $response['success'] : false, 'content' => '', ); } /** * Get funnel type id */ public function get_template_type_id( $request ) { $response = array( 'success' => false, 'type_id' => '', ); if ( $request['type'] ) { $type = $request['type']; $force_update = false; $timeout = ( $force_update ) ? 40 : 55; $teplate_type_url = self::get_remote_funnel_template_type_api_url(); $response = self::remote_get( $teplate_type_url, array( 'timeout' => $timeout, ) ); if ( isset( $response['data'] ) && is_array( $response['data'] ) && !empty($response['data'])) { foreach ( $response['data'] as $types ) { if ( $type === $types['name'] ) { $response['success'] = true; $response['type_id'] = $types['id']; } } } $params = array( 'url' => self::get_remote_funnel_template_type_api_url() ); $url = add_query_arg( $params, WPFNL_MIDDLEMAN_TEMPLATE_TYPE_URL ); $response = ( new TemplateLibraryController() )->remote_get( $url , array( 'timeout' => $timeout, ) ); if ( isset( $response['data'] ) && is_array( $response['data'] ) && !empty($response['data'])) { foreach ( $response['data'] as $types ) { if ( $type === $types['name'] ) { $response['success'] = true; $response['type_id'] = $types['id']; } } } } return rest_ensure_response( $response ); } /** * Prepare a single setting object for response. * * @param object $item Setting object. * @param WP_REST_Request $request Request object. * * @return WP_REST_Response $response Response data. * @since 1.0.0 */ public function prepare_item_for_response( $item, $request ) { $data = $this->add_additional_fields_to_object( $item, $request ); return rest_ensure_response( $data ); } }