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();