Files
htaccess-manager/htaccess-manager.php
2025-11-19 16:38:24 +08:00

449 lines
17 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Plugin Name: HTACCESS Manager
* Plugin URI: https://lhcy.org
* Description: 根据需求生成.htaccess重定向规则
* Version: 1.0
* Author: 林海草原
* Text Domain: htaccess-manager
* License: Private
*
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
class HTACCESS_Manager {
private $options;
public function __construct() {
$this->options = get_option('htaccess_manager_options');
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_init', array($this, 'admin_init'));
add_action('admin_notices', array($this, 'admin_notices'));
register_activation_hook(__FILE__, array($this, 'activate'));
register_deactivation_hook(__FILE__, array($this, 'deactivate'));
register_uninstall_hook(__FILE__, array('HTACCESS_Manager', 'uninstall'));
}
public function activate() {
// 创建must-use插件目录
$mu_plugins_dir = WP_CONTENT_DIR . '/mu-plugins';
if (!file_exists($mu_plugins_dir)) {
wp_mkdir_p($mu_plugins_dir);
}
// 生成初始选项
if (false === $this->options) {
$default_options = array(
'redirect_type' => 'www_to_root',
'is_subdomain' => false,
'auto_update' => true
);
update_option('htaccess_manager_options', $default_options);
}
$this->update_htaccess();
$this->create_mu_plugin();
}
public function deactivate() {
// 从.htaccess中安全移除本插件的规则
$this->safe_remove_htaccess_rules();
// 清理mu-plugin
$mu_plugin_file = WP_CONTENT_DIR . '/mu-plugins/htaccess-helper.php';
if (file_exists($mu_plugin_file)) {
unlink($mu_plugin_file);
}
}
public static function uninstall() {
// 删除选项
delete_option('htaccess_manager_options');
// 从.htaccess中安全移除规则
self::safe_remove_htaccess_rules_static();
// 清理mu-plugin
$mu_plugin_file = WP_CONTENT_DIR . '/mu-plugins/htaccess-helper.php';
if (file_exists($mu_plugin_file)) {
unlink($mu_plugin_file);
}
}
public function add_admin_menu() {
add_options_page(
'HTACCESS 管理',
'HTACCESS 管理',
'manage_options',
'htaccess-manager',
array($this, 'admin_page')
);
}
public function admin_init() {
register_setting('htaccess_manager', 'htaccess_manager_options');
add_settings_section(
'htaccess_manager_section',
'重定向设置',
array($this, 'section_callback'),
'htaccess_manager'
);
add_settings_field(
'is_subdomain',
'是否为二级域名',
array($this, 'is_subdomain_callback'),
'htaccess_manager',
'htaccess_manager_section'
);
add_settings_field(
'redirect_type',
'重定向类型',
array($this, 'redirect_type_callback'),
'htaccess_manager',
'htaccess_manager_section'
);
add_settings_field(
'auto_update',
'自动更新',
array($this, 'auto_update_callback'),
'htaccess_manager',
'htaccess_manager_section'
);
}
public function section_callback() {
echo '<p>配置您的网站重定向规则。HTTPS重定向是强制的。</p>';
}
public function is_subdomain_callback() {
$value = isset($this->options['is_subdomain']) ? $this->options['is_subdomain'] : false;
?>
<label>
<input type="checkbox" name="htaccess_manager_options[is_subdomain]" value="1" <?php checked(1, $value); ?> />
我的网站使用二级域名sub.domain.com
</label>
<p class="description">如果启用将只强制HTTPS不处理www和根域名之间的跳转。</p>
<?php
}
public function redirect_type_callback() {
$value = isset($this->options['redirect_type']) ? $this->options['redirect_type'] : 'www_to_root';
?>
<fieldset>
<label>
<input type="radio" name="htaccess_manager_options[redirect_type]" value="www_to_root" <?php checked('www_to_root', $value); ?> <?php echo isset($this->options['is_subdomain']) && $this->options['is_subdomain'] ? 'disabled' : ''; ?> />
www跳转到根域名www.example.com → example.com
</label><br>
<label>
<input type="radio" name="htaccess_manager_options[redirect_type]" value="root_to_www" <?php checked('root_to_www', $value); ?> <?php echo isset($this->options['is_subdomain']) && $this->options['is_subdomain'] ? 'disabled' : ''; ?> />
根域名跳转到wwwexample.com → www.example.com
</label>
</fieldset>
<?php
}
public function auto_update_callback() {
$value = isset($this->options['auto_update']) ? $this->options['auto_update'] : true;
?>
<label>
<input type="checkbox" name="htaccess_manager_options[auto_update]" value="1" <?php checked(1, $value); ?> />
设置更改时自动更新.htaccess文件
</label>
<?php
}
public function admin_page() {
?>
<div class="wrap">
<h1>HTACCESS 管理</h1>
<form action="options.php" method="post">
<?php
settings_fields('htaccess_manager');
do_settings_sections('htaccess_manager');
submit_button('保存设置');
?>
</form>
<hr>
<div class="card">
<h2>当前生成的规则</h2>
<textarea readonly style="width: 100%; height: 200px; font-family: monospace; background: #f6f6f6;"><?php echo esc_textarea($this->generate_htaccess_rules()); ?></textarea>
<p>
<button type="button" class="button button-secondary" onclick="copyToClipboard(this)">复制规则</button>
<span id="copy-message" style="margin-left: 10px; color: green; display: none;">已复制到剪贴板!</span>
</p>
</div>
<div class="card">
<h2>手动更新</h2>
<p>如果自动更新失败,您可以手动复制上面的规则到您的.htaccess文件中。</p>
<form method="post">
<?php wp_nonce_field('manual_update', 'htaccess_nonce'); ?>
<input type="submit" name="manual_update" class="button button-primary" value="手动更新.htaccess文件">
<input type="submit" name="manual_remove" class="button button-secondary" value="从.htaccess中移除规则" onclick="return confirm('确定要从.htaccess文件中移除所有HTACCESS Manager规则吗')">
</form>
</div>
<div class="card">
<h2>当前.htaccess内容预览</h2>
<?php
$htaccess_file = ABSPATH . '.htaccess';
if (file_exists($htaccess_file)) {
$content = file_get_contents($htaccess_file);
echo '<textarea readonly style="width: 100%; height: 300px; font-family: monospace; background: #f0f0f0;">' . esc_textarea($content) . '</textarea>';
} else {
echo '<p>.htaccess文件不存在。</p>';
}
?>
</div>
</div>
<script>
function copyToClipboard(button) {
const textarea = document.querySelector('textarea');
textarea.select();
document.execCommand('copy');
const message = document.getElementById('copy-message');
message.style.display = 'inline';
setTimeout(() => {
message.style.display = 'none';
}, 2000);
}
// 动态禁用/启用重定向类型选项
document.querySelector('input[name="htaccess_manager_options[is_subdomain]"]').addEventListener('change', function() {
var radios = document.querySelectorAll('input[name="htaccess_manager_options[redirect_type]"]');
radios.forEach(function(radio) {
radio.disabled = this.checked;
}.bind(this));
});
</script>
<?php
// 处理手动更新
if (isset($_POST['manual_update']) && check_admin_referer('manual_update', 'htaccess_nonce')) {
$this->update_htaccess();
echo '<div class="notice notice-success is-dismissible"><p>.htaccess文件已更新</p></div>';
}
// 处理手动移除
if (isset($_POST['manual_remove']) && check_admin_referer('manual_update', 'htaccess_nonce')) {
$this->safe_remove_htaccess_rules();
echo '<div class="notice notice-success is-dismissible"><p>已从.htaccess文件中移除所有HTACCESS Manager规则</p></div>';
}
}
public function admin_notices() {
if (isset($_GET['settings-updated']) && $_GET['settings-updated']) {
if (isset($this->options['auto_update']) && $this->options['auto_update']) {
$this->update_htaccess();
echo '<div class="notice notice-success is-dismissible"><p>设置已保存并更新了.htaccess文件</p></div>';
} else {
echo '<div class="notice notice-success is-dismissible"><p>设置已保存!请手动更新.htaccess文件。</p></div>';
}
}
}
private function generate_htaccess_rules() {
$rules = array();
$rules[] = 'RewriteEngine On';
$rules[] = '';
if (isset($this->options['is_subdomain']) && $this->options['is_subdomain']) {
// 二级域名情况只强制HTTPS
$rules[] = '# 强制HTTPS重定向二级域名';
$rules[] = 'RewriteCond %{HTTPS} off';
$rules[] = 'RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]';
} else {
// 主域名情况处理www和HTTPS
$rules[] = '# 同时处理 HTTPS 和 WWW 重定向';
if (isset($this->options['redirect_type']) && $this->options['redirect_type'] === 'root_to_www') {
// 根域名跳转到www
$rules[] = 'RewriteCond %{HTTPS} off [OR]';
$rules[] = 'RewriteCond %{HTTP_HOST} ^' . preg_quote(parse_url(home_url(), PHP_URL_HOST), '.') . '$ [NC]';
$rules[] = 'RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]';
$rules[] = 'RewriteRule ^ https://www.%1%{REQUEST_URI} [L,R=301]';
} else {
// www跳转到根域名默认
$rules[] = 'RewriteCond %{HTTPS} off [OR]';
$rules[] = 'RewriteCond %{HTTP_HOST} ^www\. [NC]';
$rules[] = 'RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]';
$rules[] = 'RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301]';
}
}
return implode("\n", $rules);
}
private function update_htaccess() {
$htaccess_file = ABSPATH . '.htaccess';
if (!file_exists($htaccess_file)) {
// 如果.htaccess文件不存在创建它
touch($htaccess_file);
}
if (is_writable($htaccess_file)) {
$new_rules = $this->generate_htaccess_rules();
// 读取现有内容
$content = file_get_contents($htaccess_file);
// 定义标记
$start_marker = '# BEGIN HTACCESS Manager';
$end_marker = '# END HTACCESS Manager';
// 检查是否已有我们的规则
if (strpos($content, $start_marker) !== false) {
// 替换现有规则
$pattern = '/' . preg_quote($start_marker, '/') . '.*?' . preg_quote($end_marker, '/') . '/s';
$replacement = $start_marker . "\n" . $new_rules . "\n" . $end_marker;
$content = preg_replace($pattern, $replacement, $content);
} else {
// 在文件开头添加新规则在WordPress规则之前
$wp_start_marker = '# BEGIN WordPress';
if (strpos($content, $wp_start_marker) !== false) {
$replacement = $start_marker . "\n" . $new_rules . "\n" . $end_marker . "\n\n" . $wp_start_marker;
$content = str_replace($wp_start_marker, $replacement, $content);
} else {
// 如果没有WordPress标记直接添加到文件开头
$content = $start_marker . "\n" . $new_rules . "\n" . $end_marker . "\n\n" . $content;
}
}
// 清理多余的空行
$content = preg_replace("/(\r\n|\r|\n){3,}/", "\n\n", $content);
file_put_contents($htaccess_file, $content);
return true;
}
return false;
}
private function safe_remove_htaccess_rules() {
$htaccess_file = ABSPATH . '.htaccess';
if (file_exists($htaccess_file) && is_writable($htaccess_file)) {
$content = file_get_contents($htaccess_file);
// 备份原始内容
$backup_content = $content;
// 只移除我们自己的规则标记和内容
$start_marker = '# BEGIN HTACCESS Manager';
$end_marker = '# END HTACCESS Manager';
$pattern = '/' . preg_quote($start_marker, '/') . '.*?' . preg_quote($end_marker, '/') . '\s*\n?/s';
$content = preg_replace($pattern, '', $content);
// 清理多余的空行
$content = preg_replace("/(\r\n|\r|\n){3,}/", "\n\n", $content);
$content = trim($content) . "\n";
// 只有在内容确实发生变化且不为空时才写入
if ($content !== $backup_content && !empty(trim($content))) {
file_put_contents($htaccess_file, $content);
}
return true;
}
return false;
}
public static function safe_remove_htaccess_rules_static() {
$htaccess_file = ABSPATH . '.htaccess';
if (file_exists($htaccess_file) && is_writable($htaccess_file)) {
$content = file_get_contents($htaccess_file);
// 备份原始内容
$backup_content = $content;
// 只移除我们自己的规则标记和内容
$start_marker = '# BEGIN HTACCESS Manager';
$end_marker = '# END HTACCESS Manager';
$pattern = '/' . preg_quote($start_marker, '/') . '.*?' . preg_quote($end_marker, '/') . '\s*\n?/s';
$content = preg_replace($pattern, '', $content);
// 清理多余的空行
$content = preg_replace("/(\r\n|\r|\n){3,}/", "\n\n", $content);
$content = trim($content) . "\n";
// 只有在内容确实发生变化且不为空时才写入
if ($content !== $backup_content && !empty(trim($content))) {
file_put_contents($htaccess_file, $content);
}
return true;
}
return false;
}
private function create_mu_plugin() {
$mu_plugin_content = '<?php
/**
* Must-use plugin for HTACCESS Manager
* Auto-generated file - DO NOT EDIT
*/
defined(\'ABSPATH\') or die();
class HTACCESS_Manager_Helper {
public static function safe_remove_rules() {
$htaccess_file = ABSPATH . \'.htaccess\';
if (file_exists($htaccess_file) && is_writable($htaccess_file)) {
$content = file_get_contents($htaccess_file);
$backup_content = $content;
// 只移除HTACCESS Manager的规则
$start_marker = \'# BEGIN HTACCESS Manager\';
$end_marker = \'# END HTACCESS Manager\';
$pattern = \'/\' . preg_quote($start_marker, \'/\') . \'.*?\' . preg_quote($end_marker, \'/\') . \'\\s*\\n?/s\';
$content = preg_replace($pattern, \'\', $content);
// 清理多余空行
$content = preg_replace("/(\\r\\n|\\r|\\n){3,}/", "\\n\\n", $content);
$content = trim($content) . "\\n";
// 只有在内容变化且不为空时才写入
if ($content !== $backup_content && !empty(trim($content))) {
file_put_contents($htaccess_file, $content);
}
}
}
}
// 在主插件被禁用时清理规则
add_action(\'deactivated_plugin\', function($plugin) {
if ($plugin === \'htaccess-manager/htaccess-manager.php\') {
HTACCESS_Manager_Helper::safe_remove_rules();
}
});
';
$mu_plugin_file = WP_CONTENT_DIR . '/mu-plugins/htaccess-helper.php';
file_put_contents($mu_plugin_file, $mu_plugin_content);
}
}
new HTACCESS_Manager();