*/ namespace RankMath\Redirections; use RankMath\Helper; use MyThemeShop\Helpers\Str; use MyThemeShop\Database\Database; defined( 'ABSPATH' ) || exit; /** * DB class. */ class DB { /** * Get query builder. * * @return Query_Builder */ private static function table() { return Database::table( 'rank_math_redirections' ); } /** * Get counts of record group by active and inactive. * * @return array */ public static function get_counts() { static $redirction_counts; if ( ! is_null( $redirction_counts ) ) { return $redirction_counts; } $redirction_counts = self::table() ->selectSum( 'status = "active"', 'active' ) ->selectSum( 'status = "inactive"', 'inactive' ) ->selectSum( 'status = "trashed"', 'trashed' ) ->one( ARRAY_A ); $redirction_counts['all'] = $redirction_counts['active'] + $redirction_counts['inactive']; return $redirction_counts; } /** * Get redirections. * * @param array $args Array of filters apply to query. * * @return array */ public static function get_redirections( $args = [] ) { $args = wp_parse_args( $args, [ 'orderby' => 'id', 'order' => 'DESC', 'limit' => 10, 'paged' => 1, 'search' => '', 'status' => 'any', ]); $status = self::is_valid_status( $args['status'] ) ? [ $args['status'], null ] : [ '!=', 'trashed' ]; $table = self::table() ->found_rows() ->page( $args['paged'] - 1, $args['limit'] ) ->where( 'status', $status[0], $status[1] ); if ( ! empty( $args['search'] ) ) { $table->whereLike( 'sources', $args['search'] ); $table->orWhereLike( 'url_to', $args['search'] ); } if ( ! empty( $args['orderby'] ) && in_array( $args['orderby'], [ 'id', 'url_to', 'header_code', 'hits', 'last_accessed' ], true ) ) { $table->orderBy( $args['orderby'], $args['order'] ); } $redirections = $table->get( ARRAY_A ); $count = $table->get_found_rows(); return compact( 'redirections', 'count' ); } /** * Match redirections for uri * * @param string $uri Current uri to match. * @param bool $all Get All. * * @return object */ public static function match_redirections( $uri, $all = false ) { if ( empty( $uri ) ) { return false; } // If nothing found than go for all. if ( $all ) { $redirections = self::table() ->where( 'status', 'active' ) ->orderby( 'updated', 'desc' ) ->get( ARRAY_A ); return self::compare_redirections( $redirections, $uri ); } $table = self::table()->where( 'status', 'active' )->orderby( 'updated', 'desc' ); // Generate words. $words = self::remove_stopwords( $uri ); // Generate where cluase. $where = []; $source = maybe_serialize([ 'pattern' => $uri, 'comparison' => 'exact', ]); $where[] = [ 'sources', 'like', $table->esc_like( $source ) ]; foreach ( $words as $word ) { $where[] = [ 'sources', 'like', $table->esc_like( $word ) ]; } $redirections = $table->where( $where, 'or' )->get( ARRAY_A ); $redirection = self::compare_redirections( $redirections, $uri ); if ( false === $redirection ) { return self::match_redirections( $uri, true ); } return $redirection; } /** * Compare given redirections * * @param array $redirections Array of redirection matched. * @param string $uri Uri to comapre with. * * @return array|bool */ private static function compare_redirections( $redirections, $uri ) { foreach ( $redirections as $redirection ) { $redirection['sources'] = maybe_unserialize( $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 comapre with. * * @return bool */ private static function compare_sources( $sources, $uri ) { foreach ( $sources as $source ) { if ( Str::comparison( self::get_clean_pattern( $source['pattern'], $source['comparison'] ), $uri, $source['comparison'] ) ) { return true; } } return false; } /** * Match redirections for 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 ) { $pattern = trim( $pattern, '/' ); return 'regex' === $comparison ? ( '@' . stripslashes( $pattern ) . '@' ) : $pattern; } /** * 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' ) { $table = self::table()->where( 'id', $id ); if ( 'all' !== $status ) { $table->where( 'status', $status ); } $item = $table->one( ARRAY_A ); if ( ! isset( $item['sources'] ) ) { return false; } $item['sources'] = maybe_unserialize( $item['sources'] ); return $item; } /** * Get stats for dashboard widget. * * @return int */ 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; } $args = wp_parse_args( $args, [ 'sources' => '', 'url_to' => '', 'header_code' => '301', 'hits' => '0', 'status' => 'active', 'created' => current_time( 'mysql' ), 'updated' => current_time( 'mysql' ), ]); $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'] ); 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']; } // 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' ); self::table()->set( $args )->where( 'id', $redirection['id'] )->update(); } /** * Delete multiple record. * * @param array $ids Array of ids to delete. * * @return int Number of records deleted. */ public static function delete( $ids ) { Cache::purge( $ids ); return self::table()->whereIn( 'id', (array) $ids )->delete(); } /** * 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(); } /** * Cleans trashed redirects after 30 days. * * @return int Number of records deleted. */ public static function periodic_clean_trash() { $ids = self::table()->select( 'id' )->where( 'status', 'trashed' )->where( 'updated', '<=', date( 'Y-m-d', strtotime( '30 days ago' ) ) )->get( ARRAY_A ); if ( empty( $ids ) ) { return 0; } return self::delete( wp_list_pluck( $ids, 'id' ) ); } /** * Deletes 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' ) ); } /** * Removes stopword from the sample permalink that is generated in an AJAX request. * * @param string $uri The uri to remove words from. * * @return array */ private static function remove_stopwords( $uri ) { static $redirection_stop_words; if ( is_null( $redirection_stop_words ) ) { $redirection_stop_words = explode( ',', esc_html__( "a,about,above,after,again,against,all,am,an,and,any,are,as,at,be,because,been,before,being,below,between,both,but,by,could,did,do,does,doing,down,during,each,few,for,from,further,had,has,have,having,he,he'd,he'll,he's,her,here,here's,hers,herself,him,himself,his,how,how's,i,i'd,i'll,i'm,i've,if,in,into,is,it,it's,its,itself,let's,me,more,most,my,myself,nor,of,on,once,only,or,other,ought,our,ours,ourselves,out,over,own,same,she,she'd,she'll,she's,should,so,some,such,than,that,that's,the,their,theirs,them,themselves,then,there,there's,these,they,they'd,they'll,they're,they've,this,those,through,to,too,under,until,up,very,was,we,we'd,we'll,we're,we've,were,what,what's,when,when's,where,where's,which,while,who,who's,whom,why,why's,with,would,you,you'd,you'll,you're,you've,your,yours,yourself,yourselves", 'rank-math' ) ); } // Turn it to an array and strip stop words by comparing against an array of stopwords. $words = str_replace( '/', '-', $uri ); $words = str_replace( '.', '-', $words ); $words = explode( '-', $words ); return array_diff( $words, $redirection_stop_words ); } /** * 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 ); } }