nserialize( $redirection['sources'] ); if ( ! empty( $redirection['sources'] ) && self::compare_sources( $redirection['sources'], $uri ) ) { return $redirection; } } return false; } /** * Compare sources. * * @param array $sources Array of sources. * @param string $uri URI to compare with. * * @return bool */ public static function compare_sources( $sources, $uri ) { if ( ! is_array( $sources ) || empty( $sources ) ) { return false; } foreach ( $sources as $source ) { $compare_uri = $uri; $comparison = $source['comparison']; if ( 'exact' === $comparison ) { $compare_uri = untrailingslashit( $compare_uri ); } if ( in_array( $comparison, [ 'contains', 'start', 'end' ], true ) ) { $source['pattern'] = untrailingslashit( $source['pattern'] ); } if ( 'exact' === $comparison && isset( $source['ignore'] ) && 'case' === $source['ignore'] && strtolower( $source['pattern'] ) === strtolower( $compare_uri ) ) { return true; } if ( Str::comparison( self::get_clean_pattern( $source['pattern'], $comparison ), $compare_uri, $comparison ) ) { return true; } } return false; } /** * Match redirections for a source. * * @param string $source Current source to match. * * @return array */ public static function match_redirections_source( $source ) { if ( empty( $source ) ) { return false; } $table = self::table(); return $table->found_rows() ->where( 'status', 'active' ) ->whereLike( 'sources', $source ) ->orderby( 'updated', 'desc' ) ->page( 0, 1 ) ->get( ARRAY_A ); } /** * Get clean pattern for testing. * * @param string $pattern Pattern to clean. * @param string $comparison Comparison type. * * @return string */ public static function get_clean_pattern( $pattern, $comparison ) { if ( 'exact' === $comparison ) { $pattern = trim( $pattern, '/' ); } $cleaned = 'regex' === $comparison ? ( '@' . stripslashes( $pattern ) . '@' ) : $pattern; return apply_filters( 'rank_math/redirection/get_clean_pattern', $cleaned, $pattern, $comparison ); } /** * Get source by ID. * * @param int $id ID of the record to search for. * @param string $status Status to filter with. * * @return bool|array */ public static function get_redirection_by_id( $id, $status = 'all' ) { $fields = [ [ 'id', '=', $id ], ]; if ( 'all' !== $status ) { $fields[] = [ 'status', '=', $status ]; } return self::get_redirection_by( $fields ); } /** * Get redirection * * @param array $data Redirection data. * * @return bool|array */ public static function get_redirection( $data ) { $args = []; if ( isset( $data['destination'] ) ) { $args[] = [ 'url_to', '=', $data['destination'] ]; } if ( isset( $data['type'] ) ) { $args[] = [ 'header_code', '=', $data['type'] ]; } if ( isset( $data['status'] ) ) { $args[] = [ 'status', '=', $data['status'] ]; } if ( empty( $args ) ) { return false; } // Exist by destination. $exist = self::get_redirection_by( $args ); if ( $exist ) { return $exist; } // Exist by ID. if ( ! empty( $data['id'] ) ) { return self::get_redirection_by_id( $data['id'] ); } return false; } /** * Get source by. * * @param array $data Redirection fields. * * @return bool|array */ public static function get_redirection_by( $data = [] ) { $table = self::table()->where( $data ); $item = $table->one( ARRAY_A ); if ( ! isset( $item['sources'] ) ) { return false; } $item['sources'] = self::get_sources( maybe_unserialize( $item['sources'] ) ); return $item; } /** * Get stats for dashboard widget. * * @return object */ public static function get_stats() { return self::table()->selectCount( '*', 'total' )->selectSum( 'hits', 'hits' )->one(); } /** * Add a new record. * * @param array $args Values to insert. * * @return bool|int */ public static function add( $args = [] ) { if ( empty( $args ) ) { return false; } if ( array_key_exists( 'id', $args ) ) { unset( $args['id'] ); } $args = wp_parse_args( $args, [ 'sources' => '', 'url_to' => '', 'header_code' => '301', 'hits' => '0', 'status' => 'active', 'created' => current_time( 'mysql' ), 'updated' => current_time( 'mysql' ), ] ); if ( in_array( $args['header_code'], [ 410, 451 ], true ) ) { $args['url_to'] = ''; } $args['sources'] = maybe_serialize( $args['sources'] ); return self::table()->insert( $args, [ '%s', '%s', '%d', '%d', '%s', '%s', '%s' ] ); } /** * Update a record. * * @param array $args Values to update. * * @return bool|int */ public static function update( $args = [] ) { if ( empty( $args ) ) { return false; } $args = wp_parse_args( $args, [ 'id' => '', 'sources' => '', 'url_to' => '', 'header_code' => '301', 'status' => 'active', 'updated' => current_time( 'mysql' ), ] ); $id = absint( $args['id'] ); if ( 0 === $id ) { return false; } $args['sources'] = maybe_serialize( $args['sources'] ); unset( $args['id'] ); if ( in_array( $args['header_code'], [ 410, 451 ], true ) ) { $args['url_to'] = ''; } Cache::purge( $id ); return self::table()->set( $args )->where( 'id', $id )->update(); } /** * Add or Update record. * * @param array $redirection Single redirection item. * * @return int */ public static function update_iff( $redirection ) { // Update record. if ( isset( $redirection['id'] ) && ! empty( $redirection['id'] ) ) { self::update( $redirection ); return $redirection['id']; } $existing_redirection = self::match_redirections_source( maybe_serialize( $redirection['sources'] ) ); if ( ! empty( $existing_redirection ) && isset( $existing_redirection[0]['id'] ) ) { $redirection['id'] = $existing_redirection[0]['id']; self::update( $redirection ); return $redirection['id']; } // Add record. return self::add( $redirection ); } /** * Update counter for redirection. * * @param object $redirection Record to update. * * @return int|false The number of rows updated, or false on error. */ public static function update_access( $redirection = false ) { if ( empty( $redirection ) ) { return false; } $args['hits'] = absint( $redirection['hits'] ) + 1; $args['last_accessed'] = current_time( 'mysql' ); return self::table()->set( $args )->where( 'id', $redirection['id'] )->update(); } /** * Delete multiple records. * * @param array $ids Array of ids to delete. * * @return int Number of records deleted. */ public static function delete( $ids ) { Cache::purge( $ids ); $deleted = self::table()->whereIn( 'id', (array) $ids )->delete(); /** * Fires after deleting redirections. */ do_action( 'rank_math/redirection/deleted', $ids, $deleted ); return $deleted; } /** * Change record status to active or inactive. * * @param array $ids Array of ids. * @param bool $status Active=1, Inactive=0. * * @return int Number of records updated. */ public static function change_status( $ids, $status ) { if ( ! self::is_valid_status( $status ) ) { return false; } return self::table()->set( 'status', $status ) ->set( 'updated', current_time( 'mysql' ) ) ->whereIn( 'id', (array) $ids )->update(); } /** * Clean trashed redirects after 30 days. * * @return void */ public static function periodic_clean_trash() { $ids = self::table()->select( 'id' )->where( 'status', 'trashed' )->where( 'updated', '<=', date_i18n( 'Y-m-d', strtotime( '30 days ago' ) ) )->get( ARRAY_A ); if ( empty( $ids ) ) { return; } self::delete( wp_list_pluck( $ids, 'id' ) ); } /** * Delete all trashed redirections and associated sources. * * @return int Number of records deleted. */ public static function clear_trashed() { $ids = self::table()->select( 'id' )->where( 'status', 'trashed' )->get(); if ( empty( $ids ) ) { return 0; } return self::delete( wp_list_pluck( $ids, 'id' ) ); } /** * Check if status is valid. * * @param string $status Status to validate. * * @return bool */ private static function is_valid_status( $status ) { $allowed = [ 'active', 'inactive', 'trashed' ]; return in_array( $status, $allowed, true ); } /** * Get redirection source. * * @param array $sources Unserialized sources. * * @return array */ private static function get_sources( $sources ) { if ( ! is_array( $sources ) || empty( $sources ) ) { return $sources; } foreach ( $sources as $key => $source ) { $sources[ $key ]['pattern'] = wp_specialchars_decode( $source['pattern'] ); } return $sources; } }