options = get_option( 'aio_options', [] ); $utility_active = aio_is_module_active( 'opt-module-utility' ); $seo_active = aio_is_module_active( 'opt-module-optimizer' ); if ( $utility_active ) { // 1. Maintenance Mode if ( ! empty( $this->options['optEnableMaintenance'] ) ) { add_action( 'template_redirect', [ $this, 'handle_maintenance_mode' ], 0 ); } // 2. Duplicate Post / Page if ( ! empty( $this->options['optEnableDuplicatePost'] ) ) { add_filter( 'post_row_actions', [ $this, 'add_duplicate_post_link' ], 10, 2 ); add_filter( 'page_row_actions', [ $this, 'add_duplicate_post_link' ], 10, 2 ); add_action( 'admin_action_aio_duplicate_post_as_draft', [ $this, 'duplicate_post_as_draft' ] ); } // 2.5 Duplicate Menu if ( ! empty( $this->options['optEnableDuplicateMenu'] ) ) { add_action( 'admin_footer-nav-menus.php', [ $this, 'add_duplicate_menu_button' ] ); add_action( 'admin_init', [ $this, 'handle_duplicate_menu' ] ); } } // 3. 404 Logging & 301 Redirection (shared between SEO Pro and Utility) if ( ( $seo_active || $utility_active ) && ! empty( $this->options['optEnableRedirection'] ) ) { add_action( 'template_redirect', [ $this, 'handle_404_and_redirects' ], 1 ); } } /** * Check whether an IP matches any entry (exact IP or IPv4/IPv6 CIDR) in a newline-separated list. */ private function is_ip_in_list( $ip, $raw_list ) { if ( function_exists( 'aio_is_ip_in_list' ) ) { return aio_is_ip_in_list( $ip, $raw_list ); } if ( empty( $ip ) || empty( $raw_list ) ) { return false; } $lines = preg_split( '/[\r\n,]+/', (string) $raw_list ); foreach ( $lines as $entry ) { $entry = trim( $entry ); if ( $entry === '' ) { continue; } if ( $entry === $ip ) { return true; } if ( strpos( $entry, '/' ) !== false && filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) { list( $subnet, $bits ) = array_pad( explode( '/', $entry, 2 ), 2, '' ); $bits = (int) $bits; if ( $bits >= 0 && $bits <= 32 && filter_var( $subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) { $ip_long = ip2long( $ip ); $sub_long = ip2long( $subnet ); $mask = $bits === 0 ? 0 : ( -1 << ( 32 - $bits ) ); if ( ( $ip_long & $mask ) === ( $sub_long & $mask ) ) { return true; } } } } return false; } /** * Maintenance Mode Logic */ public function handle_maintenance_mode() { if ( wp_doing_ajax() || wp_doing_cron() ) { return; } // Allow whitelisted IPs if ( ! empty( $this->options['txtMaintWhitelist'] ) && function_exists( 'aio_get_client_ip' ) ) { if ( $this->is_ip_in_list( aio_get_client_ip(), $this->options['txtMaintWhitelist'] ) ) { return; } } // Bypass Logic (constant-time comparison + HttpOnly cookie unified as aio_maint_bypass) $bypass_token = isset( $this->options['optMaintenanceBypass'] ) ? trim( (string) $this->options['optMaintenanceBypass'] ) : ''; if ( '' !== $bypass_token ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['aio_bypass'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $provided = sanitize_text_field( wp_unslash( $_GET['aio_bypass'] ) ); if ( hash_equals( $bypass_token, $provided ) ) { $cookie_domain = ( defined( 'COOKIE_DOMAIN' ) && is_string( COOKIE_DOMAIN ) ) ? COOKIE_DOMAIN : ''; setcookie( 'aio_maint_bypass', $bypass_token, [ 'expires' => time() + ( 86400 * 30 ), 'path' => COOKIEPATH ? COOKIEPATH : '/', 'domain' => $cookie_domain, 'secure' => is_ssl(), 'httponly' => true, 'samesite' => 'Lax', ] ); wp_safe_redirect( remove_query_arg( 'aio_bypass' ) ); exit; } } $raw_cookie = ''; if ( isset( $_COOKIE['aio_maint_bypass'] ) ) { $raw_cookie = sanitize_text_field( wp_unslash( $_COOKIE['aio_maint_bypass'] ) ); } elseif ( isset( $_COOKIE['aio_maintenance_bypass'] ) ) { $raw_cookie = sanitize_text_field( wp_unslash( $_COOKIE['aio_maintenance_bypass'] ) ); } if ( '' !== $raw_cookie && hash_equals( $bypass_token, $raw_cookie ) ) { return; // Allow access } } // Do not block admin users if ( is_user_logged_in() && current_user_can( 'manage_options' ) ) { return; } // Do not block wp-login.php or custom Hide Login slug (root or subdirectory install) if ( isset( $GLOBALS['pagenow'] ) && in_array( $GLOBALS['pagenow'], array( 'wp-login.php', 'wp-register.php' ), true ) ) { return; } $custom_login_slug = ! empty( $this->options['optHideLoginUrl'] ) ? trim( (string) $this->options['optHideLoginUrl'], '/' ) : ''; if ( '' !== $custom_login_slug && isset( $_SERVER['REQUEST_URI'] ) ) { $req_path = trim( (string) wp_parse_url( wp_unslash( $_SERVER['REQUEST_URI'] ), PHP_URL_PATH ), '/' ); $home_path = trim( (string) wp_parse_url( home_url(), PHP_URL_PATH ), '/' ); if ( '' !== $home_path && strpos( $req_path, $home_path . '/' ) === 0 ) { $req_path = substr( $req_path, strlen( $home_path ) + 1 ); } if ( $req_path === $custom_login_slug ) { return; } } if ( ! defined( 'DONOTCACHEPAGE' ) ) { define( 'DONOTCACHEPAGE', true ); } nocache_headers(); // Return 503 Service Unavailable status_header( 503 ); header( 'Retry-After: 3600' ); // Render maintenance template $title = ! empty( $this->options['txtMaintenanceTitle'] ) ? $this->options['txtMaintenanceTitle'] : __( 'Site Under Maintenance', 'aio-tools' ); $message = ! empty( $this->options['txtMaintenanceMessage'] ) ? $this->options['txtMaintenanceMessage'] : __( 'We are currently updating our website. Please check back later.', 'aio-tools' ); $bg_image = $this->options['optMaintenanceBgImage'] ?? ''; $bg_color = sanitize_hex_color( $this->options['optMaintenanceColor'] ?? '#3b82f6' ); if ( ! $bg_color ) { $bg_color = '#3b82f6'; } $body_style = ''; $overlay = ''; $text_color = '#334155'; $bg_card = '#ffffff'; if (!empty($bg_image)) { $body_style = "background: url('" . esc_url($bg_image) . "') no-repeat center center fixed; background-size: cover;"; $overlay = '
'; $text_color = '#ffffff'; $bg_card = 'rgba(255,255,255,0.1)'; } else { $body_style = "background-color: #f8fafc;"; } echo ' ' . esc_html($title) . ' ' . $overlay . '
🚧

' . esc_html($title) . '

' . nl2br(esc_html($message)) . '

'; exit; } /** * Add Duplicate Link in Admin */ public function add_duplicate_post_link($actions, $post) { if ( ! ( $post instanceof WP_Post ) ) { return $actions; } // Preserve WooCommerce's native product duplicator if ( $post->post_type === 'product' && class_exists( 'WooCommerce' ) ) { return $actions; } if ( current_user_can( 'edit_post', $post->ID ) && current_user_can( 'read_post', $post->ID ) ) { $nonce = wp_create_nonce('aio_duplicate_post_' . $post->ID); $duplicate_url = admin_url("admin.php?action=aio_duplicate_post_as_draft&post={$post->ID}&_wpnonce={$nonce}"); $actions['duplicate'] = '' . esc_html__('Duplicate', 'aio-tools') . ''; } return $actions; } public function duplicate_post_as_draft() { if (!isset($_GET['post']) || !isset($_GET['_wpnonce'])) { wp_die(esc_html__('No post to duplicate has been supplied!', 'aio-tools')); } $post_id = absint($_GET['post']); $nonce = sanitize_text_field(wp_unslash($_GET['_wpnonce'])); if (!$post_id || !wp_verify_nonce($nonce, 'aio_duplicate_post_' . $post_id)) { wp_die(esc_html__('Security check failed', 'aio-tools')); } $post = get_post($post_id); if (!$post) { wp_die(esc_html__('Post creation failed, could not find original post:', 'aio-tools') . ' ' . esc_html($post_id)); } $post_type_obj = get_post_type_object($post->post_type); $create_cap = $post_type_obj && isset($post_type_obj->cap->create_posts) ? $post_type_obj->cap->create_posts : 'edit_posts'; if ( ! current_user_can( 'edit_post', $post_id ) || ! current_user_can( 'read_post', $post_id ) || ! current_user_can( $create_cap ) ) { wp_die( esc_html__( 'You do not have permission to duplicate this item.', 'aio-tools' ), 403 ); } $current_user = wp_get_current_user(); $new_post_author = $current_user->ID; $args = array( 'comment_status' => $post->comment_status, 'ping_status' => $post->ping_status, 'post_author' => $new_post_author, 'post_content' => wp_slash( $post->post_content ), 'post_excerpt' => wp_slash( $post->post_excerpt ), 'post_name' => $post->post_name . '-copy', 'post_parent' => $post->post_parent, 'post_password' => $post->post_password, 'post_status' => 'draft', 'post_title' => wp_slash( $post->post_title . ' (' . __( 'Copy', 'aio-tools' ) . ')' ), 'post_type' => $post->post_type, 'to_ping' => $post->to_ping, 'menu_order' => $post->menu_order, ); $new_post_id = wp_insert_post($args); if ( is_wp_error( $new_post_id ) || ! $new_post_id ) { wp_die( esc_html__( 'Failed to insert duplicated post.', 'aio-tools' ) ); } // Copy taxonomies $taxonomies = get_object_taxonomies($post->post_type); foreach ($taxonomies as $taxonomy) { $post_terms = wp_get_object_terms($post_id, $taxonomy, array('fields' => 'slugs')); if ( ! is_wp_error( $post_terms ) ) { wp_set_object_terms($new_post_id, $post_terms, $taxonomy, false); } } // Copy meta safely global $wpdb; $post_meta_infos = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id = %d", $post_id ) ); if ( ! empty( $post_meta_infos ) ) { $sql_query_sel = []; $excluded_meta = array( '_wp_old_slug', '_edit_lock', '_edit_last' ); foreach ( $post_meta_infos as $meta_info ) { $meta_key = $meta_info->meta_key; if ( in_array( $meta_key, $excluded_meta, true ) ) { continue; } $meta_value = $meta_info->meta_value; $sql_query_sel[] = $wpdb->prepare( 'SELECT %d, %s, %s', $new_post_id, $meta_key, $meta_value ); } if ( ! empty( $sql_query_sel ) ) { $sql_query = "INSERT INTO $wpdb->postmeta (post_id, meta_key, meta_value) " . implode( ' UNION ALL ', $sql_query_sel ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $wpdb->query( $sql_query ); } } clean_post_cache( $new_post_id ); wp_safe_redirect(admin_url('post.php?action=edit&post=' . $new_post_id)); exit; } /** * Add Duplicate button to nav-menus.php */ public function add_duplicate_menu_button() { if (!isset($_GET['menu'])) return; $menu_id = intval($_GET['menu']); if ($menu_id <= 0) return; $nonce = wp_create_nonce('aio_duplicate_menu_' . $menu_id); $url = admin_url("nav-menus.php?action=aio_duplicate_menu&menu={$menu_id}&_wpnonce={$nonce}"); ?> name . ' (' . __('Copy', 'aio-tools') . ' ' . time() . ')'; $new_menu_id = wp_create_nav_menu($new_menu_name); if (is_wp_error($new_menu_id)) { wp_die($new_menu_id->get_error_message()); } $menu_items = wp_get_nav_menu_items($menu_id); if (!empty($menu_items)) { $item_id_map = []; // Map old ID to new ID // First pass foreach ($menu_items as $item) { $args = array( 'menu-item-object-id' => $item->object_id, 'menu-item-object' => $item->object, 'menu-item-parent-id' => isset($item_id_map[$item->menu_item_parent]) ? $item_id_map[$item->menu_item_parent] : 0, 'menu-item-position' => $item->menu_order, 'menu-item-type' => $item->type, 'menu-item-title' => $item->title, 'menu-item-url' => $item->url, 'menu-item-description' => $item->description, 'menu-item-attr-title' => $item->attr_title, 'menu-item-target' => $item->target, 'menu-item-classes' => implode(' ', $item->classes), 'menu-item-xfn' => $item->xfn, 'menu-item-status' => $item->post_status ); $new_item_id = wp_update_nav_menu_item($new_menu_id, 0, $args); if (!is_wp_error($new_item_id)) { $item_id_map[$item->ID] = $new_item_id; } } // Second pass for hierarchy foreach ($menu_items as $item) { if ($item->menu_item_parent && isset($item_id_map[$item->menu_item_parent]) && isset($item_id_map[$item->ID])) { update_post_meta($item_id_map[$item->ID], '_menu_item_menu_item_parent', $item_id_map[$item->menu_item_parent]); } } } wp_safe_redirect(admin_url("nav-menus.php?menu={$new_menu_id}")); exit; } /** * 404 Logging & Redirection Logic */ public function handle_404_and_redirects() { global $wp; $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : ''; $parsed_path = $request_uri ? (string) wp_parse_url( $request_uri, PHP_URL_PATH ) : ''; $home_path = trim( (string) wp_parse_url( home_url(), PHP_URL_PATH ), '/' ); if ( '' !== $parsed_path ) { $clean_path = ltrim( $parsed_path, '/' ); if ( '' !== $home_path && 0 === strpos( $clean_path, $home_path . '/' ) ) { $clean_path = substr( $clean_path, strlen( $home_path ) + 1 ); } elseif ( '' !== $home_path && $clean_path === $home_path ) { $clean_path = ''; } $path = '/' . ltrim( $clean_path, '/' ); } else { $path = '/' . ltrim( (string) $wp->request, '/' ); } // Check if there is a redirect rule for this path (exact or normalized trailing slash) $redirects = get_option( 'aio_redirect_rules', [] ); if ( is_array( $redirects ) && ! empty( $redirects ) ) { $alt_path = '/' !== $path ? rtrim( $path, '/' ) : $path; $slash_path = '/' !== $path ? rtrim( $path, '/' ) . '/' : $path; $target = $redirects[ $path ] ?? ( $redirects[ $alt_path ] ?? ( $redirects[ $slash_path ] ?? '' ) ); if ( ! empty( $target ) ) { if ( preg_match( '#^https?://#i', $target ) ) { // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- Intentional admin-configured external 301 redirect wp_redirect( esc_url_raw( $target ), 301 ); } else { wp_safe_redirect( home_url( $target ), 301 ); } exit; } } // Log 404 (ignoring common static asset 404 noise) if ( is_404() ) { if ( preg_match( '/\.(?:ico|map|jpg|jpeg|png|gif|webp|svg|css|js|woff|woff2|ttf|eot)$/i', $path ) ) { return; } global $wpdb; $table_name = $wpdb->prefix . 'aio_404_logs'; $path_safe = substr( sanitize_text_field( $path ), 0, 255 ); if ( '' === $path_safe ) { return; } $existing = $wpdb->get_row( $wpdb->prepare( "SELECT id, hits FROM $table_name WHERE url = %s", $path_safe ) ); if ( $existing ) { $wpdb->update( $table_name, [ 'hits' => (int) $existing->hits + 1, 'last_hit' => current_time( 'mysql' ), ], [ 'id' => $existing->id ], [ '%d', '%s' ], [ '%d' ] ); } else { $wpdb->insert( $table_name, [ 'url' => $path_safe, 'hits' => 1, 'last_hit' => current_time( 'mysql' ), ], [ '%s', '%d', '%s' ] ); // Keep table bounded to 200 most recent unique 404 paths if ( 1 === wp_rand( 1, 10 ) ) { $cutoff = $wpdb->get_var( "SELECT last_hit FROM $table_name ORDER BY last_hit DESC LIMIT 1 OFFSET 200" ); if ( $cutoff ) { $wpdb->query( $wpdb->prepare( "DELETE FROM $table_name WHERE last_hit < %s", $cutoff ) ); } } } } } } // Init Module new AIO_Utility();