*/ namespace RankMath\Admin; use RankMath\Runner; use RankMath\Helper; use RankMath\Helpers\Str; use RankMath\Helpers\Param; use RankMath\Admin\Admin_Helper; use RankMath\Traits\Ajax; use RankMath\Traits\Hooker; defined( 'ABSPATH' ) || exit; /** * Admin class. * * @codeCoverageIgnore */ class Admin implements Runner { use Hooker, Ajax; /** * Register hooks. */ public function hooks() { $this->action( 'init', 'flush', 999 ); $this->filter( 'user_contactmethods', 'update_user_contactmethods' ); $this->action( 'admin_footer', 'convert_additional_profile_url_to_textarea' ); $this->action( 'save_post', 'canonical_check_notice' ); $this->action( 'cmb2_save_options-page_fields', 'update_is_configured_value', 10, 2 ); $this->filter( 'action_scheduler_pastdue_actions_check_pre', 'as_exclude_pastdue_actions' ); $this->action( 'rank_math/pro_badge', 'offer_icon' ); $this->filter( 'load_script_translation_file', 'load_script_translation_file', 10, 3 ); // AJAX. $this->ajax( 'search_pages', 'search_pages' ); $this->ajax( 'is_keyword_new', 'is_keyword_new' ); $this->ajax( 'save_checklist_layout', 'save_checklist_layout' ); $this->ajax( 'deactivate_plugins', 'deactivate_plugins' ); } /** * Flush the rewrite rules once if the rank_math_flush_rewrite option is set. */ public function flush() { if ( get_option( 'rank_math_flush_rewrite' ) ) { flush_rewrite_rules(); delete_option( 'rank_math_flush_rewrite' ); } if ( 'rank-math' === Param::get( 'page' ) && get_option( 'rank_math_view_modules' ) ) { delete_option( 'rank_math_view_modules' ); } } /** * Add Facebook and Twitter as user contact methods. * * @param array $contactmethods Current contact methods. * @return array New contact methods with extra items. * * @copyright Copyright (C) 2008-2019, Yoast BV * The following code is a derivative work of the code from the Yoast(https://github.com/Yoast/wordpress-seo/), which is licensed under GPL v3. */ public function update_user_contactmethods( $contactmethods ) { $contactmethods['twitter'] = esc_html__( 'Twitter username (without @)', 'rank-math' ); $contactmethods['facebook'] = esc_html__( 'Facebook profile URL', 'rank-math' ); $contactmethods['additional_profile_urls'] = esc_html__( 'Additional profile URLs', 'rank-math' ); return $contactmethods; } /** * Display admin header. */ public function display_admin_header() { $nav_tabs = new Admin_Header(); $nav_tabs->display(); } /** * Display admin breadcrumbs. */ public function display_admin_breadcrumbs() { $nav_tabs = new Admin_Breadcrumbs(); $nav_tabs->display(); } /** * Display dashboard tabs. */ public function display_dashboard_nav() { $nav_tabs = new Admin_Dashboard_Nav(); $nav_tabs->display(); } /** * Show notice when canonical URL is not a valid URL. * * @param int $post_id The post ID. */ public function canonical_check_notice( $post_id ) { $post_type = get_post_type( $post_id ); $is_allowed = in_array( $post_type, Helper::get_allowed_post_types(), true ); if ( ! $is_allowed || Helper::is_autosave() || Helper::is_ajax() || isset( $_REQUEST['bulk_edit'] ) ) { return $post_id; } if ( ! empty( $_POST['rank_math_canonical_url'] ) && false === Param::post( 'rank_math_canonical_url', false, FILTER_VALIDATE_URL ) ) { $message = esc_html__( 'The canonical URL you entered does not seem to be a valid URL. Please double check it in the SEO meta box » Advanced tab.', 'rank-math' ); Helper::add_notification( $message, [ 'type' => 'error' ] ); } } /** * Save checklist layout. */ public function save_checklist_layout() { check_ajax_referer( 'rank-math-ajax-nonce', 'security' ); $this->has_cap_ajax( 'onpage_general' ); if ( empty( $_POST['layout'] ) || ! is_array( $_POST['layout'] ) ) { return; } $layout = Param::post( 'layout', [], FILTER_DEFAULT, FILTER_REQUIRE_ARRAY ); $allowed = [ 'basic' => 1, 'advanced' => 1, 'title-readability' => 1, 'content-readability' => 1, ]; $layout = array_intersect_key( $layout, $allowed ); update_user_meta( get_current_user_id(), 'rank_math_metabox_checklist_layout', $layout ); exit; } /** * Ajax handler to search pages based on the searched string. Used in the Local SEO Settings. */ public function search_pages() { check_ajax_referer( 'rank-math-ajax-nonce', 'security' ); $this->has_cap_ajax( 'general' ); $term = Param::get( 'term' ); if ( empty( $term ) ) { exit; } global $wpdb; $pages = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_title FROM {$wpdb->prefix}posts WHERE post_type = 'page' AND post_status = 'publish' AND post_title LIKE %s", "%{$wpdb->esc_like( $term )}%" ), ARRAY_A ); $data = []; foreach ( $pages as $page ) { $data[] = [ 'id' => $page['ID'], 'text' => $page['post_title'], 'url' => get_permalink( $page['ID'] ), ]; } wp_send_json( [ 'results' => $data ] ); } /** * Check if the keyword has been used before for another post. */ public function is_keyword_new() { global $wpdb; check_ajax_referer( 'rank-math-ajax-nonce', 'security' ); $this->has_cap_ajax( 'onpage_general' ); $result = [ 'isNew' => true ]; if ( empty( $_GET['keyword'] ) ) { $this->success( $result ); } $keyword = Param::get( 'keyword', '', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_BACKTICK ); $object_id = Param::get( 'objectID', 0, FILTER_VALIDATE_INT ); $object_type = Param::get( 'objectType', '', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_BACKTICK ); $column_ids = [ 'post' => 'ID', 'term' => 'term_id', 'user' => 'ID', ]; if ( ! in_array( $object_type, [ 'post', 'term', 'user' ], true ) ) { $object_type = 'post'; } $main = $wpdb->{$object_type . 's'}; $meta = $wpdb->{$object_type . 'meta'}; $query = sprintf( 'select %2$s.%1$s from %2$s inner join %3$s on %2$s.%1$s = %3$s.%4$s_id where ', $column_ids[ $object_type ], $main, $meta, $object_type ); if ( 'post' === $object_type ) { $query .= sprintf( '%s.post_status = \'publish\' and ', $main ); } $query .= sprintf( '%1$s.meta_key = \'rank_math_focus_keyword\' and ( %1$s.meta_value = %2$s OR %1$s.meta_value like %3$s ) and %1$s.%4$s_id != %5$d', $meta, '%s', '%s', $object_type, $object_id ); $data = $wpdb->get_row( $wpdb->prepare( $query, $keyword, $wpdb->esc_like( $keyword ) . ',%' ) ); // phpcs:ignore $result['isNew'] = empty( $data ); $this->success( $result ); } /** * Get link suggestions for the current post. * * @param int|WP_Post $post Current post. * @return array */ public function get_link_suggestions( $post ) { global $pagenow; if ( 'post-new.php' === $pagenow ) { return; } $output = []; $post = get_post( $post ); $args = [ 'post_type' => $post->post_type, 'post__not_in' => [ $post->ID ], 'posts_per_page' => 5, 'meta_key' => 'rank_math_pillar_content', 'meta_value' => 'on', 'tax_query' => [ 'relation' => 'OR' ], ]; $taxonomies = Helper::get_object_taxonomies( $post, 'names' ); $taxonomies = array_filter( $taxonomies, [ $this, 'is_taxonomy_allowed' ] ); foreach ( $taxonomies as $taxonomy ) { $this->set_term_query( $args, $post->ID, $taxonomy ); } $posts = get_posts( $args ); foreach ( $posts as $related_post ) { $item = [ 'title' => get_the_title( $related_post->ID ), 'url' => get_permalink( $related_post->ID ), 'post_id' => $related_post->ID, 'focus_keywords' => get_post_meta( $related_post->ID, 'rank_math_focus_keyword', true ), ]; $item['focus_keywords'] = empty( $item['focus_keywords'] ) ? [] : explode( ',', $item['focus_keywords'] ); $output[] = $item; } return $output; } /** * Is taxonomy allowed * * @param string $taxonomy Taxonomy to check. * * @return bool */ public function is_taxonomy_allowed( $taxonomy ) { $exclude_taxonomies = [ 'post_format', 'product_shipping_class' ]; if ( Str::starts_with( 'pa_', $taxonomy ) || in_array( $taxonomy, $exclude_taxonomies, true ) ) { return false; } return true; } /** * Set term query. * * @param array $query Array of query. * @param int $post_id Post ID to get terms from. * @param string $taxonomy Taxonomy to get terms for. */ private function set_term_query( &$query, $post_id, $taxonomy ) { $terms = wp_get_post_terms( $post_id, $taxonomy, [ 'fields' => 'ids' ] ); if ( empty( $terms ) || is_wp_error( $terms ) ) { return; } $query['tax_query'][] = [ 'taxonomy' => $taxonomy, 'field' => 'term_id', 'terms' => $terms, ]; } /** * Output link suggestions. * * @param array $suggestions Link items. * @return string */ public function get_link_suggestions_html( $suggestions ) { $output = ''; return $output; } /** * Updates the is_configured value. * * @param int $object_id The ID of the current object. * @param string $cmb_id The current box ID. */ public function update_is_configured_value( $object_id, $cmb_id ) { if ( 0 !== strpos( $cmb_id, 'rank_math' ) && 0 !== strpos( $cmb_id, 'rank-math' ) ) { return; } Helper::is_configured( true ); } /** * Deactivate plugin. */ public function deactivate_plugins() { check_ajax_referer( 'rank-math-ajax-nonce', 'security' ); if ( ! current_user_can( 'activate_plugins' ) ) { $this->error( esc_html__( 'You are not authorized to perform this action.', 'rank-math' ) ); } $plugin = Param::post( 'plugin', '', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_BACKTICK ); if ( 'all' !== $plugin ) { deactivate_plugins( $plugin ); die( '1' ); } Importers\Detector::deactivate_all(); die( '1' ); } /** * Action Scheduler: exclude our actions from the past-due checker. * Since this is a *_pre hook, it replaces the original checker. * * We first do the same check as what ActionScheduler_AdminView->check_pastdue_actions() does, * but then we also count how many of those past-due actions are ours. * * @param null $null Null value. */ public function as_exclude_pastdue_actions( $null ) { $query_args = [ 'date' => as_get_datetime_object( time() - DAY_IN_SECONDS ), 'status' => \ActionScheduler_Store::STATUS_PENDING, 'per_page' => 1, ]; $store = \ActionScheduler_Store::instance(); $num_pastdue_actions = (int) $store->query_actions( $query_args, 'count' ); if ( 0 !== $num_pastdue_actions ) { $query_args['group'] = 'rank-math'; $num_pastdue_rm_actions = (int) $store->query_actions( $query_args, 'count' ); $num_pastdue_actions -= $num_pastdue_rm_actions; } $threshold_seconds = (int) apply_filters( 'action_scheduler_pastdue_actions_seconds', DAY_IN_SECONDS ); $threshhold_min = (int) apply_filters( 'action_scheduler_pastdue_actions_min', 1 ); $check = ( $num_pastdue_actions >= $threshhold_min ); return (bool) apply_filters( 'action_scheduler_pastdue_actions_check', $check, $num_pastdue_actions, $threshold_seconds, $threshhold_min ); } /** * Check and print the Anniversary icon in the header of Rank Math's setting pages. */ public static function offer_icon() { if ( ! current_user_can( 'manage_options' ) || defined( 'RANK_MATH_PRO_FILE' ) ) { return; } // Holiday Season related variables. $time = time(); $current_year = 2022; $anniversary_start_time = gmmktime( 17, 00, 00, 10, 30, $current_year ); // 30 Oct. $anniversary_end_time = gmmktime( 17, 00, 00, 11, 30, $current_year ); // 30 Nov. $holiday_start_time = gmmktime( 17, 00, 00, 12, 20, $current_year ); // 20 Dec. $holiday_end_time = gmmktime( 17, 00, 00, 01, 07, 2023 ); // 07 Jan. if ( ( $time > $anniversary_start_time && $time < $anniversary_end_time ) || ( $time > $holiday_start_time && $time < $holiday_end_time ) ) { ?> 🎉 sameAs Schema property.', 'rank-math' ) ); ?>