ars; } /** * Finds match from redirects. * * @param Item[] $redirects Redirect items. * * @return Item|null */ public function find_match( $redirects ) { if ( ! isset( $_SERVER['REQUEST_URI'] ) ) { return null; } $current_query_vars = $this->get_url_query_vars( rawurldecode( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ); foreach ( $redirects as $redirect ) { $redirect_query_vars = $this->get_url_query_vars( $redirect->get_source() ); if ( \smartcrawl_arrays_same( $redirect_query_vars, $current_query_vars ) ) { return $redirect; } } return null; } /** * Converts the redirection to a safe one * * @param string $destination Raw URL. * @param int $type Type. * * @return string Safe redirection URL */ private function to_safe_redirection( $destination, $type ) { $fallback = apply_filters( 'wp_safe_redirect_fallback', home_url(), $type ); $filter = $this->allowed_hosts_filter( $destination ); add_filter( 'allowed_redirect_hosts', $filter ); $destination = wp_sanitize_redirect( $destination ); $destination = wp_validate_redirect( $destination, $fallback ); remove_filter( 'allowed_redirect_hosts', $filter ); return $destination; } /** * Redirects attachments to parent post * * If we can't determine parent post type, * we at least throw the noindex header. * * Respects the `redirect-attachment-images-only` sub-option, * * @return void */ public function redirect_attachments() { if ( ! is_attachment() ) { return; } if ( ! empty( $this->options['images-only'] ) ) { $type = get_post_mime_type(); if ( ! preg_match( '/^image\//', $type ) ) { return; } } // Get attachment URL. $url = wp_get_attachment_url( get_queried_object_id() ); if ( ! empty( $url ) ) { wp_safe_redirect( $url, 301 ); die; } // No URL found, let's noindex. header( 'X-Robots-Tag: noindex', true ); } /** * Performs page redirect */ public function redirect_post() { global $post; // Fixes redirection on archive pages - do not redirect if not singular. // Fixes: https://app.asana.com/0/46496453944769/505196129561557/f. if ( ! is_singular() || empty( $post->ID ) ) { return false; } // Allows optional filtering out. if ( ! apply_filters_deprecated( 'wds_process_redirect', array( true ), '6.4.2', 'smartcrawl_process_redirect', __( 'Please use our new filter `smartcrawl_process_redirect` in SmartCrawl.' ) ) ) { return false; } if ( ! apply_filters( 'smartcrawl_process_redirect', true ) ) { return false; } $redirect = \smartcrawl_get_value( 'redirect', $post->ID ); if ( $post && $redirect ) { wp_redirect( // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect $this->sanitize_post_redirect( $redirect ), 301 ); exit; } return true; } /** * Sanitizes post redirect. * * @param string $destination Redirect destination. * * @return mixed|string */ private function sanitize_post_redirect( $destination ) { $filter = $this->allowed_hosts_filter( $destination ); add_filter( 'allowed_redirect_hosts', $filter ); $destination = wp_sanitize_redirect( $destination ); $destination = wp_validate_redirect( $destination, home_url() ); remove_filter( 'allowed_redirect_hosts', $filter ); return $destination; } /** * Ajax handler to save redirect. * * @return void */ public function save_redirect() { $data = $this->get_request_data(); if ( empty( $data ) ) { wp_send_json_error( __( 'There is no data to proceed with your request.', 'wds' ) ); } $id = intval( \smartcrawl_get_array_value( $data, 'id' ) ); $source = \smartcrawl_get_array_value( $data, 'source' ); $destination = \smartcrawl_get_array_value( $data, 'destination', '' ); $type = \smartcrawl_get_array_value( $data, 'type', '' ); $title = \smartcrawl_get_array_value( $data, 'title', '' ); $options = \smartcrawl_get_array_value( $data, 'options', array() ); $rules = \smartcrawl_get_array_value( $data, 'rules', array() ); if ( empty( $source ) ) { wp_send_json_error( __( 'The redirect from URL is empty.', 'wds' ) ); } if ( $this->utils->is_non_redirect_type( $type ) ) { $destination = ''; $rules = array(); } elseif ( empty( $destination ) && empty( $rules ) ) { wp_send_json_error( __( 'The redirect from/to URL is empty.', 'wds' ) ); } $redirect_item = $this->utils->create_redirect_item( $source, $destination, $type, $title, $options, $rules ); if ( $redirect_item->is_regex() && $this->is_source_regex_invalid( $source ) ) { wp_send_json_error( array( 'message' => 'Invalid regex source.' ) ); } if ( $id ) { $redirect_item->set_id( $id ); } $table = Database_Table::get(); $saved = $table->save_redirect( $redirect_item ); if ( $saved ) { $redirect_item->set_id( $saved ); $data = $redirect_item->deflate(); wp_send_json_success( $this->populate_destination( $data ) ); } wp_send_json_error(); } /** * Retrieves redirects from database table. * * @return array|false */ public function get_redirects() { if ( ! $this->should_run() ) { return false; } $redirects = Database_Table::get()->get_deflated_redirects(); if ( ! $redirects ) { return false; } foreach ( $redirects as $key => $redirect ) { $redirects[ $key ] = $this->populate_destination( $redirect ); } return $redirects; } /** * Fills in data into redirect destination. * * @param array $redirect Redirect data. * * @return array */ private function populate_destination( $redirect ) { if ( ! empty( $redirect['destination'] ) ) { $redirect['destination'] = $this->format_destination( $redirect['destination'] ); } if ( empty( $redirect['rules'] ) ) { $redirect['rules'] = array(); } foreach ( $redirect['rules'] as $index => $rule ) { $rule['url'] = $this->format_destination( $rule['url'] ); $redirect['rules'][ $index ] = $rule; } return $redirect; } /** * Formatts destination. * * @param array $destination Destination data. * * @return array */ private function format_destination( $destination ) { if ( empty( $destination ) ) { return $destination; } if ( empty( $destination['id'] ) ) { $destination = array( 'url' => $destination, 'type' => __( 'Url', 'wds' ), ); } else { $post_id = $destination['id']; $post_url = get_permalink( $post_id ); if ( $post_url ) { $destination['url'] = str_replace( home_url(), '', $post_url ); $post_type = get_post_type( $post_id ); $post_type_obj = get_post_type_object( $post_type ); if ( $post_type_obj ) { $destination['title'] = get_the_title( $post_id ); $destination['type'] = $post_type_obj->labels->singular_name; $destination['_type'] = $post_type; } else { $destination['type'] = __( 'Url', 'wds' ); } } } return $destination; } /** * Checks if a source regex is invalid. * * @param string $source Regex source. * * @return bool */ private function is_source_regex_invalid( $source ) { $with_escaped_delimiter = str_replace( '~', '\~', $source ); return @preg_match( "~$with_escaped_delimiter~", null ) === false; // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } /** * Ajax handler to delete a redirect. * * @return void */ public function delete_redirect() { $data = $this->get_request_data(); if ( empty( $data ) ) { wp_send_json_error( array( 'mesage' => __( 'Request data is not valid.', 'wds' ) ) ); } $ids = \smartcrawl_get_array_value( $data, 'ids' ); $table = Database_Table::get(); $deleted = $table->delete_redirects( $ids ); if ( $deleted ) { wp_send_json_success(); } wp_send_json_error( array( 'message' => __( 'Something was wrong.', 'wds' ) ) ); } /** * Ajax handler to bulk update redirects. * * @return void */ public function bulk_update_redirects() { $data = $this->get_request_data(); if ( empty( $data ) ) { wp_send_json_error( array( 'message' => __( 'Failed to retrieve data.', 'wds' ) ) ); } $ids = \smartcrawl_get_array_value( $data, 'ids' ); if ( empty( $ids ) ) { wp_send_json_error( array( 'message' => __( 'Not found redirects.', 'wds' ) ) ); } $table = Database_Table::get(); $redirects = $table->get_redirects( $ids ); if ( ! $redirects ) { wp_send_json_error( array( 'message' => __( 'Failed to get redirects by given IDs.', 'wds' ) ) ); } $destination = \smartcrawl_get_array_value( $data, 'destination', '' ); $type = \smartcrawl_get_array_value( $data, 'type', '' ); $rules = \smartcrawl_get_array_value( $data, 'rules', array() ); $response = array(); foreach ( $ids as $id ) { $redirect = \smartcrawl_get_array_value( $redirects, $id ); if ( ! $redirect ) { wp_send_json_error( array( 'message' => sprintf( /* translators: %s: Redirect id */ __( 'Failed to get redirect by given ID: %s.', 'wds' ), $id ), ) ); } if ( ! empty( $type ) ) { $redirect->set_type( $type ); } if ( $this->utils->is_non_redirect_type( $type ) ) { $redirect->set_destination( '' ); $redirect->set_rules( $rules ); } else { if ( ! empty( $destination ) ) { $redirect->set_destination( $destination ); } if ( ! empty( $rules ) ) { $redirect->set_rules( $rules ); } } $response[ $id ] = $this->populate_destination( $redirect->deflate() ); } $is_updated = $table->update_redirects( $redirects ); if ( false === $is_updated ) { wp_send_json_error( array( 'message' => __( 'Failed to update redirects.', 'wds' ) ) ); } wp_send_json_success( $response ); } /** * Ajax handler to import redirects from JSON. * * Supports both JSON and CSV files for import. But only JSON will support * location based redirects import. CSV file imports are kept only for the * backward compatibility. * * @return void */ public function import_redirects() { $data = $this->get_request_data(); if ( empty( $data ) ) { wp_send_json_error(); } // File size can not exceed 1MB. $file_size = \smartcrawl_get_array_value( $_FILES, array( 'file', 'size' ) ); if ( $file_size > 1000000 ) { wp_send_json_error( array( 'message' => esc_html__( 'Please select a file under 1MB.', 'wds' ), ) ); } $redirects = array(); $file_name = \smartcrawl_get_array_value( $_FILES, array( 'file', 'tmp_name' ) ); $file_type = \smartcrawl_get_array_value( $_FILES, array( 'file', 'type' ) ); // For backward compatibility only. if ( in_array( $file_type, \smartcrawl_csv_mime_types(), true ) ) { $redirects = $this->get_redirects_from_csv( $file_name, $errors ); } elseif ( 'application/json' === $file_type ) { // Exports are only in json. So this is the correct format now. $redirects = $this->get_redirects_from_json( $file_name, $errors ); } else { wp_send_json_error( array( 'message' => esc_html__( 'Only JSON or CSV files are supported.', 'wds' ), ) ); } if ( $errors ) { wp_send_json_error( array( 'message' => esc_html__( 'Some entries have invalid values. Please try again!', 'wds' ), ) ); } if ( empty( $redirects ) ) { wp_send_json_error( array( 'message' => esc_html__( 'No valid redirects were found, please check your file.', 'wds' ), ) ); } $inserted = $this->redirects_table->insert_redirects( $redirects ); if ( ! $inserted ) { wp_send_json_error( array( 'message' => esc_html__( 'An error occurred while inserting CSV data into the database.', 'wds' ), ) ); } $redirects = $this->redirects_table->get_deflated_redirects(); foreach ( $redirects as $key => $redirect ) { $redirects[ $key ] = $this->populate_destination( $redirect ); } wp_send_json_success( array( 'count' => $inserted, 'redirects' => $redirects, ) ); } /** * Get redirects from CSV. * * @param string $file_name File path name. * @param bool $errors Is error?. * * @return array */ private function get_redirects_from_csv( $file_name, &$errors = false ) { $file = fopen( $file_name, 'r' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen if ( ! $file ) { wp_send_json_error(); } $redirects = array(); while ( $redirect_data = fgetcsv( $file ) ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition list( $source, $destination, $type, $regex, $title, $rules ) = $redirect_data; if ( empty( $source ) ) { continue; } $options = empty( $regex ) ? array() : array( 'regex' ); $redirect_item = $this->utils->create_redirect_item( $source, $destination, $type, $title, $options, $rules ); if ( $redirect_item->is_regex() && $this->is_source_regex_invalid( $source ) ) { $errors = true; } else { $redirects[] = $redirect_item; } } fclose( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose return $redirects; } /** * Get redirects from JSON. * * @param string $file_name File path name. * @param bool $errors Is error?. * * @return array */ private function get_redirects_from_json( $file_name, &$errors = false ) { $redirects_data = file_get_contents( $file_name ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents if ( empty( $redirects_data ) ) { wp_send_json_error(); } $errors = false; $redirects = array(); $redirects_data = json_decode( $redirects_data, true ); // Should be a valid json first. if ( empty( $redirects_data ) || ! is_array( $redirects_data ) ) { return array(); } foreach ( $redirects_data as $redirect ) { // Required items. if ( ! isset( $redirect['source'], $redirect['type'], $redirect['path'] ) ) { continue; } // Format redirect data. $redirect = wp_parse_args( $redirect, array( 'title' => '', 'source' => '', 'path' => '', 'destination' => '', 'type' => '', 'options' => array(), 'rules' => array(), ) ); $redirect_item = $this->utils->create_redirect_item( $redirect['source'], $redirect['destination'], $redirect['type'], $redirect['title'], $redirect['options'], $redirect['rules'] ); if ( $redirect_item->is_regex() && $this->is_source_regex_invalid( $redirect['source'] ) ) { $errors = true; } else { $redirects[] = $redirect_item; } } return $redirects; } /** * Ajax handler to export JSON. * * @return void */ public function export_redirects() { ob_start(); $redirects = $this->redirects_table->get_redirects(); if ( ! $redirects ) { wp_send_json_error( array( 'message' => esc_html__( 'Please save some redirects first.', 'wds' ), ) ); } $file = array(); foreach ( $redirects as $redirect ) { $file[] = array( 'title' => $redirect->get_title(), 'source' => $redirect->get_source(), 'path' => $redirect->get_path(), 'destination' => $redirect->get_destination(), 'type' => $redirect->get_type(), 'options' => $redirect->get_options(), 'rules' => $redirect->get_rules(), ); } $json = wp_json_encode( $file ); header( 'Content-disposition: attachment; filename=smartcrawl-redirects-' . time() . '.json' ); header( 'Content-type: application/json' ); echo( $json ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped wp_send_json_success( array( 'content' => ob_get_clean(), ) ); } /** * Retrieves HTTP Request data. * * @return array|mixed */ private function get_request_data() { return isset( $_POST['_wds_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wds_nonce'] ) ), 'wds-admin-nonce' ) ? stripslashes_deep( $_POST ) : array(); } /** * Retrieves only allowed hosts. * * @param string $destination Destination. * * @return \Closure */ private function allowed_hosts_filter( $destination ) { return function ( $allowed_hosts ) use ( $destination ) { $host = \smartcrawl_get_array_value( wp_parse_url( $destination ), 'host' ); if ( empty( $host ) || ! is_array( $allowed_hosts ) ) { return $allowed_hosts; } return array_unique( array_merge( $allowed_hosts, array( $host ) ) ); }; } }