mirror of
https://github.com/WenPai-org/wpnav-links.git
synced 2025-08-03 11:11:29 +08:00
494 lines
15 KiB
PHP
494 lines
15 KiB
PHP
<?php
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class WPNAV_Links {
|
|
private $options;
|
|
private $table_name;
|
|
|
|
public function __construct() {
|
|
global $wpdb;
|
|
$this->table_name = $wpdb->prefix . WPNAV_LINKS_TABLE;
|
|
}
|
|
|
|
public function init() {
|
|
add_action('plugins_loaded', array($this, 'load_textdomain'));
|
|
|
|
$this->options = get_option('wpnav_links_options');
|
|
|
|
if (is_admin()) {
|
|
new WPNAV_Admin($this->options);
|
|
}
|
|
|
|
new WPNAV_Public($this->options, $this);
|
|
|
|
add_action('init', array($this, 'add_rewrite_rules'));
|
|
add_filter('query_vars', array($this, 'add_query_vars'));
|
|
add_action('template_redirect', array($this, 'handle_external_redirect'));
|
|
add_action('admin_bar_menu', array($this, 'add_admin_bar_menu'), 100);
|
|
}
|
|
|
|
public function load_textdomain() {
|
|
load_plugin_textdomain('wpnav-links', false, dirname(plugin_basename(__FILE__)) . '/languages/');
|
|
}
|
|
|
|
public function add_rewrite_rules() {
|
|
$url_format = isset($this->options['url_format']) ? $this->options['url_format'] : 'query';
|
|
|
|
if ($url_format === 'path') {
|
|
add_rewrite_rule('^go/([^/]+)/?$', 'index.php?wpnav_redirect=1&url_param=$matches[1]', 'top');
|
|
} else {
|
|
add_rewrite_rule('^go/?$', 'index.php?wpnav_redirect=1', 'top');
|
|
}
|
|
}
|
|
|
|
public function add_query_vars($vars) {
|
|
$vars[] = 'wpnav_redirect';
|
|
$vars[] = 'target';
|
|
$vars[] = 'url_param';
|
|
$vars[] = 'ref';
|
|
$vars[] = 'url';
|
|
return $vars;
|
|
}
|
|
|
|
public function handle_external_redirect() {
|
|
global $wp_query;
|
|
|
|
if (!isset($wp_query->query_vars['wpnav_redirect']) || $wp_query->query_vars['wpnav_redirect'] != 1) {
|
|
return;
|
|
}
|
|
|
|
$url = $this->get_redirect_url_from_request();
|
|
|
|
if (empty($url)) {
|
|
$this->handle_redirect_error(__('Invalid or missing URL parameter', 'wpnav-links'));
|
|
return;
|
|
}
|
|
|
|
if (!$this->validate_url($url)) {
|
|
$this->handle_redirect_error(__('Invalid URL format', 'wpnav-links'));
|
|
return;
|
|
}
|
|
|
|
$ref = isset($wp_query->query_vars['ref']) ? esc_url_raw($wp_query->query_vars['ref']) : wp_get_referer();
|
|
|
|
set_query_var('target_url', $url);
|
|
set_query_var('source_url', $ref);
|
|
|
|
if ($this->should_skip_redirect($url)) {
|
|
wp_redirect($url, 302);
|
|
exit;
|
|
}
|
|
|
|
$this->record_redirect($url, $ref);
|
|
$this->load_redirect_template($url, $ref);
|
|
exit;
|
|
}
|
|
|
|
private function get_redirect_url_from_request() {
|
|
global $wp_query;
|
|
|
|
$url_format = isset($this->options['url_format']) ? $this->options['url_format'] : 'query';
|
|
$url = '';
|
|
|
|
if ($url_format === 'path') {
|
|
if (isset($wp_query->query_vars['url_param'])) {
|
|
$url_param = $wp_query->query_vars['url_param'];
|
|
$decoded = base64_decode($url_param);
|
|
if ($decoded !== false) {
|
|
$url = $decoded;
|
|
}
|
|
}
|
|
} elseif ($url_format === 'target') {
|
|
if (isset($wp_query->query_vars['target'])) {
|
|
$url = urldecode($wp_query->query_vars['target']);
|
|
} elseif (isset($_GET['target'])) {
|
|
$url = urldecode(sanitize_text_field($_GET['target']));
|
|
}
|
|
} else {
|
|
if (isset($wp_query->query_vars['target'])) {
|
|
$url = urldecode($wp_query->query_vars['target']);
|
|
} elseif (isset($_GET['target'])) {
|
|
$url = urldecode(sanitize_text_field($_GET['target']));
|
|
} elseif (isset($wp_query->query_vars['url'])) {
|
|
$url_param = $wp_query->query_vars['url'];
|
|
$decoded = base64_decode($url_param);
|
|
if ($decoded !== false) {
|
|
$url = $decoded;
|
|
} else {
|
|
$url = urldecode($url_param);
|
|
}
|
|
} elseif (isset($_GET['url'])) {
|
|
$url_param = sanitize_text_field($_GET['url']);
|
|
$decoded = base64_decode($url_param);
|
|
if ($decoded !== false) {
|
|
$url = $decoded;
|
|
} else {
|
|
$url = urldecode($url_param);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($url) && strpos($url, 'http') !== 0) {
|
|
$url = 'http://' . $url;
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
private function handle_redirect_error($message) {
|
|
if (WP_DEBUG) {
|
|
error_log('WPNAV Redirect Error: ' . $message);
|
|
}
|
|
|
|
wp_die(
|
|
esc_html__('Redirect Error: ', 'wpnav-links') . esc_html($message),
|
|
esc_html__('External Link Redirect Error', 'wpnav-links'),
|
|
array(
|
|
'response' => 400,
|
|
'back_link' => true
|
|
)
|
|
);
|
|
}
|
|
|
|
private function should_skip_redirect($url) {
|
|
if (!empty($this->options['admin_exempt']) && current_user_can('manage_options')) {
|
|
return true;
|
|
}
|
|
|
|
if ($this->check_whitelist_domain($url)) {
|
|
return true;
|
|
}
|
|
|
|
if (isset($_COOKIE['wpnav_noredirect']) && $_COOKIE['wpnav_noredirect'] == '1') {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function check_whitelist_domain($url) {
|
|
$domain = parse_url($url, PHP_URL_HOST);
|
|
if (empty($domain)) {
|
|
return false;
|
|
}
|
|
|
|
$whitelist = array();
|
|
if (!empty($this->options['whitelist_domains'])) {
|
|
$whitelist = explode("\n", $this->options['whitelist_domains']);
|
|
$whitelist = array_map('trim', $whitelist);
|
|
}
|
|
|
|
if (in_array($domain, $whitelist)) {
|
|
return true;
|
|
}
|
|
|
|
foreach ($whitelist as $pattern) {
|
|
if (strpos($pattern, '*') !== false) {
|
|
$pattern = str_replace('*', '(.*)', preg_quote($pattern, '/'));
|
|
if (preg_match('/^' . $pattern . '$/i', $domain)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($this->options['auto_whitelist']['same_root'])) {
|
|
$site_domain = parse_url(home_url(), PHP_URL_HOST);
|
|
$site_root = $this->get_root_domain($site_domain);
|
|
$url_root = $this->get_root_domain($domain);
|
|
|
|
if ($site_root == $url_root) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!empty($this->options['auto_whitelist']['search_engines'])) {
|
|
$search_engines = array('google.', 'bing.', 'yahoo.', 'baidu.', 'yandex.', 'duckduckgo.');
|
|
foreach ($search_engines as $engine) {
|
|
if (strpos($domain, $engine) !== false) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function get_root_domain($domain) {
|
|
$domain_parts = explode('.', $domain);
|
|
if (count($domain_parts) > 2) {
|
|
$tld_part = array_slice($domain_parts, -2, 2);
|
|
if (in_array($tld_part[0], array('co', 'com', 'net', 'org', 'gov', 'edu'))) {
|
|
return implode('.', array_slice($domain_parts, -3, 3));
|
|
}
|
|
return implode('.', array_slice($domain_parts, -2, 2));
|
|
}
|
|
return $domain;
|
|
}
|
|
|
|
private function load_redirect_template($url, $ref) {
|
|
wp_enqueue_style(
|
|
'wpnav-redirect-style',
|
|
WPNAV_LINKS_PLUGIN_URL . 'assets/css/frontend.css',
|
|
array(),
|
|
WPNAV_LINKS_VERSION
|
|
);
|
|
|
|
$template_locations = array(
|
|
get_stylesheet_directory() . '/wpnav-redirect-template.php',
|
|
get_template_directory() . '/wpnav-redirect-template.php',
|
|
WPNAV_LINKS_PLUGIN_DIR . 'templates/redirect-template.php'
|
|
);
|
|
|
|
foreach ($template_locations as $template) {
|
|
if (file_exists($template)) {
|
|
include $template;
|
|
return;
|
|
}
|
|
}
|
|
|
|
include WPNAV_LINKS_PLUGIN_DIR . 'templates/redirect-template.php';
|
|
}
|
|
|
|
public function get_redirect_url($url) {
|
|
$url_format = isset($this->options['url_format']) ? $this->options['url_format'] : 'query';
|
|
|
|
if ($url_format === 'path') {
|
|
$encoded_url = base64_encode($url);
|
|
if (get_option('permalink_structure')) {
|
|
return home_url('/go/' . $encoded_url);
|
|
} else {
|
|
return home_url('?wpnav_redirect=1&url_param=' . urlencode($encoded_url));
|
|
}
|
|
} elseif ($url_format === 'target') {
|
|
return home_url('?wpnav_redirect=1&target=' . urlencode($url));
|
|
} else {
|
|
return home_url('?wpnav_redirect=1&url=' . urlencode(base64_encode($url)));
|
|
}
|
|
}
|
|
|
|
public function add_admin_bar_menu($wp_admin_bar) {
|
|
if (!current_user_can('manage_options')) {
|
|
return;
|
|
}
|
|
|
|
$wp_admin_bar->add_node(array(
|
|
'id' => 'wpnav-links',
|
|
'title' => esc_html__('External Links', 'wpnav-links'),
|
|
'href' => admin_url('tools.php?page=wpnav-links'),
|
|
));
|
|
}
|
|
|
|
public function create_tables() {
|
|
global $wpdb;
|
|
|
|
$charset_collate = $wpdb->get_charset_collate();
|
|
|
|
$sql = "CREATE TABLE $this->table_name (
|
|
id BIGINT UNSIGNED AUTO_INCREMENT,
|
|
target_url VARCHAR(512) NOT NULL,
|
|
source_page VARCHAR(255) NOT NULL,
|
|
click_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
user_ip VARCHAR(45),
|
|
user_agent TEXT,
|
|
is_https TINYINT(1) DEFAULT 0,
|
|
PRIMARY KEY (id),
|
|
INDEX (target_url(191)),
|
|
INDEX (click_time)
|
|
) $charset_collate;";
|
|
|
|
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
|
dbDelta($sql);
|
|
|
|
update_option('wpnav_links_db_version', WPNAV_LINKS_DB_VERSION);
|
|
}
|
|
|
|
public function record_redirect($target_url, $source_page) {
|
|
global $wpdb;
|
|
|
|
$is_https = strpos($target_url, 'https://') === 0 ? 1 : 0;
|
|
$ip = $this->get_client_ip();
|
|
$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
|
|
|
|
$data = array(
|
|
'target_url' => esc_url_raw($target_url),
|
|
'source_page' => esc_url_raw($source_page),
|
|
'click_time' => current_time('mysql'),
|
|
'user_ip' => sanitize_text_field($ip),
|
|
'user_agent' => sanitize_text_field($user_agent),
|
|
'is_https' => $is_https
|
|
);
|
|
|
|
$result = $wpdb->insert($this->table_name, $data);
|
|
return $result ? $wpdb->insert_id : false;
|
|
}
|
|
|
|
private function get_client_ip() {
|
|
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
|
|
$ip = $_SERVER['HTTP_CLIENT_IP'];
|
|
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
|
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
|
} else {
|
|
$ip = $_SERVER['REMOTE_ADDR'];
|
|
}
|
|
return $ip;
|
|
}
|
|
|
|
public function get_stats($args = array()) {
|
|
global $wpdb;
|
|
|
|
$defaults = array(
|
|
'start_date' => date('Y-m-d', strtotime('-30 days')),
|
|
'end_date' => date('Y-m-d'),
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'order_by' => 'click_time',
|
|
'order' => 'DESC'
|
|
);
|
|
|
|
$args = wp_parse_args($args, $defaults);
|
|
|
|
$start_date = sanitize_text_field($args['start_date']);
|
|
$end_date = sanitize_text_field($args['end_date']);
|
|
$limit = intval($args['limit']);
|
|
$offset = intval($args['offset']);
|
|
$order_by = sanitize_sql_orderby($args['order_by']) ?: 'click_time';
|
|
$order = strtoupper($args['order']) == 'ASC' ? 'ASC' : 'DESC';
|
|
|
|
$query = $wpdb->prepare(
|
|
"SELECT * FROM {$this->table_name}
|
|
WHERE click_time BETWEEN %s AND %s
|
|
ORDER BY {$order_by} {$order}
|
|
LIMIT %d OFFSET %d",
|
|
"{$start_date} 00:00:00",
|
|
"{$end_date} 23:59:59",
|
|
$limit,
|
|
$offset
|
|
);
|
|
|
|
return $wpdb->get_results($query);
|
|
}
|
|
|
|
public function get_total_count($start_date = '', $end_date = '') {
|
|
global $wpdb;
|
|
|
|
if (empty($start_date) || empty($end_date)) {
|
|
return $wpdb->get_var("SELECT COUNT(*) FROM {$this->table_name}");
|
|
}
|
|
|
|
$query = $wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$this->table_name}
|
|
WHERE click_time BETWEEN %s AND %s",
|
|
"{$start_date} 00:00:00",
|
|
"{$end_date} 23:59:59"
|
|
);
|
|
|
|
return $wpdb->get_var($query);
|
|
}
|
|
|
|
public function get_top_urls($limit = 10) {
|
|
global $wpdb;
|
|
|
|
$query = $wpdb->prepare(
|
|
"SELECT target_url, COUNT(*) as count
|
|
FROM {$this->table_name}
|
|
GROUP BY target_url
|
|
ORDER BY count DESC
|
|
LIMIT %d",
|
|
$limit
|
|
);
|
|
|
|
return $wpdb->get_results($query);
|
|
}
|
|
|
|
public function get_https_stats() {
|
|
global $wpdb;
|
|
|
|
$total = $wpdb->get_var("SELECT COUNT(*) FROM {$this->table_name}");
|
|
|
|
if ($total == 0) {
|
|
return false;
|
|
}
|
|
|
|
$https_count = $wpdb->get_var("SELECT COUNT(*) FROM {$this->table_name} WHERE is_https = 1");
|
|
$http_count = $total - $https_count;
|
|
|
|
return array(
|
|
'total' => $total,
|
|
'https_count' => $https_count,
|
|
'http_count' => $http_count,
|
|
'https_percentage' => ($https_count / $total) * 100,
|
|
'http_percentage' => ($http_count / $total) * 100
|
|
);
|
|
}
|
|
|
|
public function get_unique_domains_count() {
|
|
global $wpdb;
|
|
|
|
$query = "SELECT COUNT(DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX(target_url, '/', 3), '://', -1)) as unique_domains FROM {$this->table_name}";
|
|
|
|
$result = $wpdb->get_var($query);
|
|
|
|
return $result ? intval($result) : 0;
|
|
}
|
|
|
|
public function cleanup_old_data($days = 90) {
|
|
global $wpdb;
|
|
|
|
$days = max(1, intval($days));
|
|
|
|
$query = $wpdb->prepare(
|
|
"DELETE FROM {$this->table_name}
|
|
WHERE click_time < DATE_SUB(NOW(), INTERVAL %d DAY)",
|
|
$days
|
|
);
|
|
|
|
return $wpdb->query($query);
|
|
}
|
|
|
|
public function validate_url($url) {
|
|
if (empty($url) || !wp_http_validate_url($url)) {
|
|
return false;
|
|
}
|
|
|
|
$parsed_url = parse_url($url);
|
|
if (empty($parsed_url['host'])) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->contains_script_injection($url)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private function contains_script_injection($url) {
|
|
$patterns = array(
|
|
'/javascript:/i',
|
|
'/data:/i',
|
|
'/vbscript:/i',
|
|
'/<script/i',
|
|
'/onclick/i',
|
|
'/onerror/i',
|
|
'/onload/i',
|
|
'/eval\(/i'
|
|
);
|
|
|
|
foreach ($patterns as $pattern) {
|
|
if (preg_match($pattern, $url)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function sanitize_url($url) {
|
|
$url = preg_replace('/[\x00-\x1F\x7F]/', '', $url);
|
|
$url = esc_url_raw($url);
|
|
return $url;
|
|
}
|
|
}
|