195 lines
6.9 KiB
PHP
195 lines
6.9 KiB
PHP
<?php
|
||
|
||
/************************************************************************************
|
||
* Pausera – Lightweight maintenance mode with role/user/IP/page exclusions
|
||
************************************************************************************/
|
||
/*
|
||
Plugin Name: Pausera
|
||
Description: Maintenance mode with exclusions (roles, users, IPs, specific pages) and a simple admin UI.
|
||
Version: 1.0.1-hotfix
|
||
Author: Your Name
|
||
*/
|
||
|
||
if (!defined('ABSPATH')) exit;
|
||
|
||
if (!defined('PAUSERA_PATH')) define('PAUSERA_PATH', plugin_dir_path(__FILE__));
|
||
if (!defined('PAUSERA_URL')) define('PAUSERA_URL', plugin_dir_url(__FILE__));
|
||
|
||
require_once PAUSERA_PATH . 'includes/assets.php';
|
||
require_once PAUSERA_PATH . 'includes/admin-ui.php';
|
||
|
||
class Pausera
|
||
{
|
||
private $options;
|
||
|
||
/************************************************************************************
|
||
* constructor
|
||
************************************************************************************/
|
||
public function __construct()
|
||
{
|
||
$defaults = [
|
||
'enable_maintenance' => false,
|
||
'excluded_slugs' => [], // e.g. ["qr", "coming-soon", "shop/qr"]
|
||
'excluded_ips' => [],
|
||
'excluded_roles' => [],
|
||
'allowed_users' => [],
|
||
'redirect_url' => '/maintenance',
|
||
];
|
||
|
||
$saved_options = get_option('pausera_settings', []);
|
||
$this->options = wp_parse_args($saved_options, $defaults);
|
||
|
||
add_action('admin_menu', [$this, 'admin_menu']);
|
||
add_action('admin_init', [$this, 'register_settings']);
|
||
add_action('template_redirect', [$this, 'handle_redirect']);
|
||
add_action('admin_bar_menu', [$this, 'add_admin_bar_status'], 100);
|
||
}
|
||
|
||
/************************************************************************************
|
||
* settings registration + sanitization
|
||
************************************************************************************/
|
||
public function register_settings()
|
||
{
|
||
register_setting('pausera_group', 'pausera_settings', [
|
||
'sanitize_callback' => [$this, 'sanitize_settings']
|
||
]);
|
||
}
|
||
|
||
public function sanitize_settings($input)
|
||
{
|
||
add_option('pausera_settings_saved', 1);
|
||
|
||
// Keep nested paths like "shop/qr" clean
|
||
$sanitize_path = function ($val) {
|
||
$val = is_string($val) ? trim($val) : '';
|
||
$val = trim($val, "/ \t\n\r\0\x0B");
|
||
if ($val === '') return '';
|
||
$parts = array_filter(explode('/', $val), 'strlen');
|
||
$parts = array_map('sanitize_title', $parts);
|
||
return implode('/', $parts);
|
||
};
|
||
|
||
$excluded_slugs = [];
|
||
if (isset($input['excluded_slugs'])) {
|
||
$raw = is_array($input['excluded_slugs']) ? $input['excluded_slugs'] : [$input['excluded_slugs']];
|
||
foreach ($raw as $r) {
|
||
$s = $sanitize_path($r);
|
||
if ($s !== '') $excluded_slugs[] = $s;
|
||
}
|
||
$excluded_slugs = array_values(array_unique($excluded_slugs));
|
||
}
|
||
|
||
return [
|
||
'enable_maintenance' => !empty($input['enable_maintenance']),
|
||
'excluded_slugs' => $excluded_slugs,
|
||
'excluded_ips' => array_filter(array_map('trim', explode(',', sanitize_text_field($input['excluded_ips'] ?? '')))),
|
||
'excluded_roles' => isset($input['excluded_roles']) ? array_filter(array_map('sanitize_text_field', (array) $input['excluded_roles'])) : [],
|
||
'redirect_url' => esc_url_raw($input['redirect_url'] ?? '/maintenance'),
|
||
'allowed_users' => isset($input['allowed_users']) ? array_filter(array_map('sanitize_user', (array) $input['allowed_users'])) : [],
|
||
];
|
||
}
|
||
|
||
/************************************************************************************
|
||
* admin UI + notices
|
||
************************************************************************************/
|
||
public function admin_menu()
|
||
{
|
||
add_menu_page(
|
||
'Pausera',
|
||
'Pausera',
|
||
'manage_options',
|
||
'pausera',
|
||
[$this, 'settings_page'],
|
||
'dashicons-shield-alt',
|
||
80
|
||
);
|
||
}
|
||
|
||
public function settings_page()
|
||
{
|
||
do_action('pausera_render_ui');
|
||
}
|
||
|
||
public function add_admin_bar_status($wp_admin_bar)
|
||
{
|
||
if (!current_user_can('manage_options')) return;
|
||
|
||
$enabled = !empty($this->options['enable_maintenance']);
|
||
$label = 'Pausera: ' . ($enabled ? 'ON' : 'OFF');
|
||
$color = $enabled ? 'orange' : '#999';
|
||
|
||
$wp_admin_bar->add_node([
|
||
'id' => 'pausera_status',
|
||
'title' => '<span style="color:' . esc_attr($color) . '; font-weight: bold;">' . esc_html($label) . '</span>',
|
||
'href' => admin_url('admin.php?page=pausera'),
|
||
'meta' => ['title' => 'Pausera Status']
|
||
]);
|
||
}
|
||
|
||
/************************************************************************************
|
||
* redirect logic
|
||
************************************************************************************/
|
||
private function path_matches($current_path, $slug)
|
||
{
|
||
$slug = trim($slug, "/ \t\n\r\0\x0B");
|
||
if ($slug === '') return false;
|
||
if ($current_path === $slug) return true; // exact
|
||
if (strpos($current_path, $slug . '/') === 0) return true; // prefix segment
|
||
return false;
|
||
}
|
||
|
||
private function is_public_system_request($current_path)
|
||
{
|
||
if (function_exists('wp_doing_ajax') && wp_doing_ajax()) return true;
|
||
if (defined('REST_REQUEST') && REST_REQUEST) return true;
|
||
if (strpos($current_path, 'wp-json') === 0) return true;
|
||
|
||
$script = $_SERVER['SCRIPT_NAME'] ?? '';
|
||
if (strpos($script, 'wp-login.php') !== false) return true;
|
||
if (strpos($script, 'wp-cron.php') !== false) return true;
|
||
|
||
if (function_exists('is_customize_preview') && is_customize_preview()) return true;
|
||
if (is_feed()) return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
public function handle_redirect()
|
||
{
|
||
if (empty($this->options['enable_maintenance'])) return;
|
||
|
||
if (is_user_logged_in()) {
|
||
$user = wp_get_current_user();
|
||
if (in_array($user->user_login, $this->options['allowed_users'] ?? [], true)) return;
|
||
foreach ((array) $user->roles as $role) {
|
||
if (in_array($role, (array) $this->options['excluded_roles'], true)) return;
|
||
}
|
||
}
|
||
|
||
$remote_ip = $_SERVER['REMOTE_ADDR'] ?? '';
|
||
if ($remote_ip && in_array($remote_ip, (array) $this->options['excluded_ips'], true)) return;
|
||
|
||
$current_path = trim(parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?? '', '/');
|
||
$redirect_path = trim($this->options['redirect_url'] ?? '/maintenance', '/');
|
||
|
||
if ($this->is_public_system_request($current_path)) return;
|
||
if ($current_path === $redirect_path) return;
|
||
|
||
foreach ((array) $this->options['excluded_slugs'] as $slug) {
|
||
if ($this->path_matches($current_path, $slug)) return;
|
||
}
|
||
|
||
if (!is_admin()) {
|
||
wp_safe_redirect(home_url('/' . ltrim($redirect_path, '/')));
|
||
exit;
|
||
}
|
||
}
|
||
}
|
||
|
||
/************************************************************************************
|
||
* bootstrap
|
||
************************************************************************************/
|
||
add_action('plugins_loaded', function () {
|
||
$GLOBALS['pausera_instance'] = new Pausera();
|
||
});
|