v1.2.0 稳定版

This commit is contained in:
文派备案 2025-06-01 18:01:15 +08:00
parent e29cccc1a9
commit 5c4a7cdeac
17 changed files with 7310 additions and 0 deletions

1008
admin-page.php Normal file

File diff suppressed because it is too large Load diff

920
admin.css Normal file
View file

@ -0,0 +1,920 @@
.card {
background: #fff;
border: 1px solid #c3c4c7;
border-radius: 4px;
max-width: unset;
margin-top: 20px;
padding: 20px;
box-shadow: 0 1px 1px rgba(0,0,0,0.04);
}
.card h2 {
margin-top: 0;
margin-bottom: 15px;
font-size: 18px;
font-weight: 600;
padding-bottom: 10px;
color: #23282d;
}
.card p {
color: #50575e;
}
.wpnav-tabs {
display: flex;
flex-wrap: wrap;
gap: 5px;
border-bottom: 1px solid #c3c4c7;
margin-bottom: 20px;
}
.wpnav-tab {
padding: 8px 16px;
border: none;
background: none;
cursor: pointer;
font-size: 14px;
border-bottom: 2px solid transparent;
color: #646970;
text-decoration: none;
white-space: nowrap;
transition: all 0.2s ease;
}
.wpnav-tab:hover:not(.active) {
background: #f0f0f1;
border-bottom-color: #dcdcde;
color: #1d2327;
}
.wpnav-tab.active {
border-bottom: 2px solid #0073aa;
font-weight: 600;
background: #f0f0f1;
color: #1d2327;
}
.wpnav-tab:focus {
outline: 2px solid #0073aa;
outline-offset: -2px;
}
.wpnav-tab-content {
flex: 1;
}
.wpnav-tab-section {
display: block;
}
.wpnav-section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #ddd;
}
.wpnav-section-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #23282d;
}
.wpnav-section-actions {
display: flex;
gap: 8px;
}
.wpnav-info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-top: 20px;
}
.wpnav-info-item {
background: #f9f9f9;
border: 1px solid #e5e5e5;
border-radius: 4px;
padding: 15px;
}
.wpnav-info-item h3,
.wpnav-info-item h4 {
margin-top: 0;
margin-bottom: 10px;
font-size: 14px;
font-weight: 600;
color: #23282d;
}
.wpnav-export-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.wpnav-export-item {
background: #f6f7f7;
border: 1px solid #ddd;
border-radius: 4px;
padding: 20px;
margin-top: 20px;
box-shadow: 0 1px 1px rgba(0,0,0,0.04);
}
.wpnav-export-item h2 {
margin-top: 0;
margin-bottom: 15px;
font-size: 18px;
font-weight: 600;
border-bottom: 1px solid #ddd;
padding-bottom: 10px;
color: #23282d;
}
.wpnav-export-item p {
font-size: 13px;
color: #50575e;
margin-bottom: 15px;
}
.wpnav-notice {
padding: 8px 12px;
border-radius: 3px;
margin: 10px 0;
display: block;
}
.wpnav-notice-success {
background-color: #d1e7dd;
border: 1px solid #badbcc;
color: #0f5132;
}
.wpnav-notice-error {
background-color: #f8d7da;
border: 1px solid #f5c2c7;
color: #842029;
}
.wpnav-auto-rules {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #ddd;
}
.wpnav-auto-rules h4 {
margin-top: 0;
margin-bottom: 15px;
font-size: 15px;
font-weight: 600;
color: #23282d;
}
.wpnav-auto-rules .wp-list-table td {
padding: 15px 12px;
border-radius: 4px;
}
.wpnav-auto-rules label {
font-weight: 600;
margin-bottom: 5px;
display: block;
color: #23282d;
}
.wpnav-auto-rules .description {
margin-top: 5px;
font-size: 13px;
color: #50575e;
}
.wpnav-quick-domains {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
}
.wpnav-quick-domains .button-small {
font-size: 12px;
padding: 4px 10px;
height: auto;
line-height: 1.4;
border-radius: 3px;
}
.wpnav-stats-overview {
display: flex;
gap: 15px;
margin: 20px 0;
padding: 15px;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 6px;
}
.wpnav-stat-item {
text-align: center;
flex: 1;
padding: 10px;
}
.wpnav-stat-number {
display: block;
font-size: 20px;
font-weight: 600;
color: #495057;
line-height: 1.2;
margin-bottom: 4px;
}
.wpnav-stat-label {
display: block;
font-size: 12px;
color: #6c757d;
font-weight: 500;
}
.wpnav-url-link {
color: #0073aa;
text-decoration: none;
transition: color 0.2s ease;
}
.wpnav-url-link:hover {
color: #005a87;
text-decoration: underline;
}
.wpnav-url-link .dashicons {
opacity: 0.6;
margin-left: 3px;
transition: opacity 0.2s ease;
}
.wpnav-url-link:hover .dashicons {
opacity: 1;
}
.wpnav-no-data,
.wpnav-no-data-message {
color: #50575e;
font-style: italic;
text-align: center;
padding: 30px 20px;
}
.wpnav-no-data-message p {
margin: 8px 0;
font-size: 14px;
}
.wpnav-no-data-message .description {
font-size: 13px;
color: #8c8f94;
}
.wpnav-https-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 12px;
color: white;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
min-width: 50px;
text-align: center;
}
.wpnav-https-badge.secure {
background-color: #00a32a;
}
.wpnav-https-badge.insecure {
background-color: #d63638;
}
.wpnav-top-links {
margin: 0;
}
.wpnav-top-link-item {
display: flex;
align-items: center;
padding: 5px 0;
border-bottom: 1px solid #f0f0f0;
}
.wpnav-top-link-item:last-child {
border-bottom: none;
}
.wpnav-rank {
font-weight: 700;
color: #0073aa;
margin-right: 12px;
min-width: 30px;
font-size: 14px;
}
.wpnav-link-info {
flex: 1;
min-width: 0;
}
.wpnav-link-title {
display: block;
color: #0073aa;
text-decoration: none;
font-size: 13px;
margin-bottom: 3px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.2s ease;
}
.wpnav-link-title:hover {
color: #005a87;
text-decoration: underline;
}
.wpnav-click-count {
font-size: 11px;
color: #50575e;
font-weight: 500;
}
.column-target-url { width: 30%; }
.column-source-page { width: 25%; }
.column-click-time { width: 15%; }
.column-ip-address { width: 15%; }
.column-https-status { width: 15%; }
.wp-list-table tbody tr:hover,
.wpnav-row-hover {
background-color: #f6f7f7;
}
.button-small {
font-size: 12px;
padding: 6px 10px;
line-height: 1.4;
height: auto;
border-radius: 3px;
transition: all 0.2s ease;
}
.button-small:hover {
transform: translateY(-1px);
}
.wp-list-table.fixed {
table-layout: fixed;
}
.wp-list-table th:first-child {
width: 200px;
}
.wp-list-table td code {
background: #f1f1f1;
padding: 3px 6px;
border-radius: 3px;
font-family: Consolas, Monaco, monospace;
font-size: 12px;
}
.form-table th {
font-weight: 600;
color: #23282d;
}
.form-table td .description {
font-size: 13px;
color: #50575e;
margin-top: 5px;
}
.form-table input[type="number"],
.form-table select {
min-width: 100px;
}
.wpnav-redirect-preview {
border: 2px solid #ddd;
border-radius: 8px;
padding: 20px;
background: #f8f9fa;
margin-top: 20px;
position: relative;
overflow: hidden;
}
.wpnav-redirect-preview::before {
content: "Preview";
position: absolute;
top: 10px;
right: 15px;
background: #0073aa;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
z-index: 10;
}
.preview-container {
padding: 20px;
border-radius: 12px;
min-height: 400px;
display: flex;
align-items: center;
justify-content: center;
transform: scale(0.8);
transform-origin: center;
transition: all 0.3s ease;
overflow: visible;
}
.preview-container .wpnav-container {
max-width: 400px;
width: 100%;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 16px;
padding: 30px;
text-align: center;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
position: relative;
z-index: 1;
}
.preview-container .wpnav-container.wpnav-simple {
max-width: 320px;
padding: 25px;
}
.preview-container .wpnav-container.wpnav-minimal {
max-width: 360px;
padding: 28px;
}
.preview-container .wpnav-container.wpnav-full {
max-width: 480px;
padding: 35px;
text-align: left;
}
.preview-container .wpnav-container.wpnav-default {
max-width: 400px;
padding: 30px;
text-align: center;
}
.preview-container .wpnav-title {
font-size: 24px;
font-weight: 700;
margin: 0 0 15px;
color: #2c3e50;
transition: font-size 0.3s ease;
}
.preview-container .wpnav-simple .wpnav-title {
font-size: 20px;
margin-bottom: 12px;
}
.preview-container .wpnav-minimal .wpnav-title {
font-size: 22px;
}
.preview-container .wpnav-full .wpnav-title {
font-size: 28px;
text-align: center;
}
.preview-container .wpnav-default .wpnav-title {
font-size: 24px;
}
.preview-container .wpnav-subtitle {
font-size: 14px;
color: #7f8c8d;
margin: 5px 0 20px;
font-weight: 400;
}
.preview-container .wpnav-warning {
background: #fff3cd;
border: 1px solid #f39c12;
border-radius: 8px;
padding: 15px;
margin: 20px 0;
color: #8b4513;
font-size: 13px;
}
.preview-container .wpnav-url-container {
margin: 15px 0;
}
.preview-container .wpnav-url-label {
font-size: 12px;
color: #7f8c8d;
margin-bottom: 8px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.preview-container .wpnav-url {
background: #f8f9fa;
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 15px;
margin: 10px 0;
word-break: break-all;
}
.preview-container .wpnav-simple .wpnav-url {
padding: 12px;
}
.preview-container .wpnav-url-domain {
font-weight: 700;
color: #2c3e50;
font-size: 16px;
}
.preview-container .wpnav-simple .wpnav-url-domain {
font-size: 14px;
}
.preview-container .wpnav-url-full {
font-size: 11px;
color: #95a5a6;
margin-top: 8px;
font-family: 'Monaco', 'Consolas', monospace;
word-break: break-all;
}
.preview-container .wpnav-security-status {
margin-top: 10px;
}
.preview-container .wpnav-security-tips {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 15px;
margin: 15px 0;
text-align: left;
}
.preview-container .wpnav-security-tips h4 {
margin: 0 0 10px;
font-size: 14px;
color: #2c3e50;
font-weight: 600;
}
.preview-container .wpnav-security-tips ul {
margin: 0;
padding-left: 15px;
font-size: 12px;
color: #5a6c7d;
}
.preview-container .wpnav-security-tips li {
margin-bottom: 5px;
}
.preview-container .wpnav-buttons {
display: flex;
gap: 10px;
margin-top: 20px;
justify-content: center;
}
.preview-container .wpnav-simple .wpnav-buttons {
gap: 8px;
flex-direction: column;
}
.preview-container .wpnav-btn {
flex: 1;
padding: 12px 16px;
border-radius: 8px;
border: none;
font-weight: 600;
font-size: 14px;
cursor: default;
transition: all 0.3s ease;
max-width: 140px;
}
.preview-container .wpnav-simple .wpnav-btn {
padding: 10px 14px;
font-size: 13px;
max-width: none;
}
.preview-container .wpnav-btn-primary {
background: #667eea;
color: white;
}
.preview-container .wpnav-btn-secondary {
background: rgba(248, 249, 250, 0.9);
color: #5a6c7d;
border: 2px solid #e9ecef;
}
.preview-container .wpnav-options {
margin: 20px 0 10px;
}
.preview-container .wpnav-checkbox {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 12px;
color: #5a6c7d;
}
.preview-container .wpnav-simple .wpnav-checkbox {
font-size: 11px;
}
.preview-container .checkmark {
width: 16px;
height: 16px;
border: 2px solid #bdc3c7;
border-radius: 3px;
position: relative;
flex-shrink: 0;
}
.preview-container .wpnav-countdown {
margin-top: 15px;
font-size: 12px;
color: #7f8c8d;
padding: 10px;
background: rgba(52, 152, 219, 0.1);
border-radius: 6px;
text-align: center;
}
.preview-container .wpnav-simple .wpnav-countdown {
margin-top: 10px;
padding: 8px;
font-size: 11px;
}
.preview-container .wpnav-progress-bar {
width: 100%;
height: 4px;
background: rgba(0, 0, 0, 0.1);
border-radius: 2px;
margin-top: 10px;
overflow: hidden;
}
.preview-container .wpnav-progress-fill {
height: 100%;
background: #667eea;
border-radius: 2px;
width: 60%;
animation: wpnav-progress 5s linear infinite;
}
@keyframes wpnav-progress {
0% { width: 100%; }
100% { width: 0%; }
}
.preview-container .wpnav-btn-rounded {
border-radius: 8px;
}
.preview-container .wpnav-btn-square {
border-radius: 0;
}
.preview-container .wpnav-btn-pill {
border-radius: 25px;
}
.preview-container.wpnav-color-blue .wpnav-btn-primary {
background: #3498db;
}
.preview-container.wpnav-color-green .wpnav-btn-primary {
background: #27ae60;
}
.preview-container.wpnav-color-red .wpnav-btn-primary {
background: #e74c3c;
}
.wpnav-loading {
opacity: 0.6;
pointer-events: none;
}
.wpnav-loading::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin: -10px 0 0 -10px;
border: 2px solid #0073aa;
border-radius: 50%;
border-top-color: transparent;
animation: wpnav-spin 1s linear infinite;
}
@keyframes wpnav-spin {
to {
transform: rotate(360deg);
}
}
@media screen and (max-width: 1024px) {
.wpnav-info-grid,
.wpnav-export-grid {
grid-template-columns: 1fr;
}
}
@media screen and (max-width: 768px) {
.wrap {
margin: 10px;
}
.card {
padding: 15px;
margin-top: 15px;
}
.wpnav-stats-overview {
flex-direction: column;
gap: 10px;
}
.wpnav-section-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.wpnav-section-actions {
width: 100%;
justify-content: flex-start;
}
.wpnav-quick-domains {
flex-direction: column;
align-items: stretch;
}
.wpnav-quick-domains .button-small {
text-align: center;
margin-bottom: 5px;
}
.preview-container {
transform: scale(0.7);
min-height: 320px;
}
.wpnav-redirect-preview {
padding: 15px;
}
.wpnav-activity-stats {
grid-template-columns: 1fr;
gap: 10px;
}
.wpnav-activity-item {
padding: 12px 8px;
}
.wpnav-activity-number {
font-size: 20px;
}
}
@media screen and (max-width: 600px) {
.wpnav-stat-number {
font-size: 18px;
}
.wp-list-table {
font-size: 12px;
}
.wp-list-table th,
.wp-list-table td {
padding: 8px 5px;
}
.column-target-url,
.column-source-page {
width: 35%;
}
.column-click-time,
.column-ip-address,
.column-https-status {
width: 10%;
}
.preview-container {
transform: scale(0.6);
min-height: 280px;
}
.wpnav-activity-stats {
grid-template-columns: 1fr;
gap: 8px;
}
.wpnav-activity-item {
padding: 10px 6px;
}
.wpnav-activity-number {
font-size: 18px;
}
.wpnav-activity-label {
font-size: 10px;
}
}
.wpnav-activity-stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-top: 10px;
}
.wpnav-activity-item {
text-align: center;
padding: 5px 5px;
background: #ffffff;
border: 1px solid #e2e4e7;
border-radius: 6px;
transition: all 0.2s ease;
}
.wpnav-activity-item:hover {
background: #ffffff;
border-color: #72aee6;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.wpnav-activity-number {
display: block;
font-size: 20px;
font-weight: 600;
color: #2271b1;
line-height: 1.2;
margin-bottom: 5px;
}
.wpnav-activity-label {
display: block;
font-size: 11px;
color: #646970;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.wpnav-tabs .nav-tab:focus,
.button:focus,
input:focus,
textarea:focus,
select:focus {
outline: 2px solid #0073aa;
outline-offset: 2px;
}

494
admin.js Normal file
View file

@ -0,0 +1,494 @@
jQuery(document).ready(function($) {
$('.wpnav-tab').click(function(e) {
e.preventDefault();
$('.wpnav-tab').removeClass('active');
$(this).addClass('active');
var tab = $(this).data('tab');
$('.wpnav-tab-section').hide();
$('.wpnav-tab-section[data-section="' + tab + '"]').show();
if (history.pushState) {
var newUrl = updateUrlParameter(window.location.href, 'tab', tab);
history.pushState({path: newUrl}, '', newUrl);
}
});
$('.wpnav-add-domain').click(function(e) {
e.preventDefault();
var domain = $(this).data('domain');
var $textarea = $('textarea[name="whitelist_domains"]');
var domains = $textarea.val().split('\n').filter(function(d) { return d.trim(); });
if (domains.indexOf(domain) >= 0) {
showNotice('wpnav-whitelist-status', wpnav_admin.strings.domain_exists.replace('%s', domain), 'error');
return;
}
domains.push(domain);
$textarea.val(domains.join('\n'));
showNotice('wpnav-whitelist-status', wpnav_admin.strings.domain_added.replace('%s', domain), 'success');
$(this).addClass('wpnav-loading').prop('disabled', true);
setTimeout(function() {
$(this).removeClass('wpnav-loading').prop('disabled', false);
}.bind(this), 1000);
});
$('#wpnav-select-all-domains').click(function(e) {
e.preventDefault();
var commonDomains = [
'google.com', 'facebook.com', 'youtube.com', 'twitter.com',
'instagram.com', 'linkedin.com', 'baidu.com', 'bing.com'
];
var $textarea = $('textarea[name="whitelist_domains"]');
var currentDomains = $textarea.val().split('\n').filter(function(d) { return d.trim(); });
var newDomains = [...new Set([...currentDomains, ...commonDomains])];
$textarea.val(newDomains.join('\n'));
showNotice('wpnav-whitelist-status', wpnav_admin.strings.common_domains_added, 'success');
});
$('#wpnav-clear-domains').click(function(e) {
e.preventDefault();
if (confirm(wpnav_admin.strings.confirm_clear)) {
$('textarea[name="whitelist_domains"]').val('');
showNotice('wpnav-whitelist-status', wpnav_admin.strings.whitelist_cleared, 'success');
}
});
$('#wpnav-export-whitelist').click(function(e) {
e.preventDefault();
var whitelist = $('textarea[name="whitelist_domains"]').val();
var domains = whitelist.split('\n').filter(function(d) { return d.trim(); });
if (domains.length === 0) {
showNotice('wpnav-whitelist-status', wpnav_admin.strings.whitelist_empty, 'error');
return;
}
var csv = domains.join('\n');
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
var link = document.createElement('a');
if (link.download !== undefined) {
var url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', 'wpnav_whitelist_' + getFormattedDate() + '.csv');
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showNotice('wpnav-whitelist-status', wpnav_admin.strings.export_success, 'success');
} else {
showNotice('wpnav-whitelist-status', wpnav_admin.strings.export_unsupported, 'error');
}
});
$('form').submit(function(e) {
var $form = $(this);
var $submitButton = $form.find('input[type="submit"], button[type="submit"]');
if ($form.attr('id') === 'sync-settings-form' || $form.attr('id') === 'sync-config-form') {
return true;
}
var formValid = validateForm($form);
if (!formValid) {
e.preventDefault();
return false;
}
if ($submitButton.length) {
var originalValue = $submitButton.val() || $submitButton.text();
$submitButton.prop('disabled', true).addClass('wpnav-loading');
if ($submitButton.is('input')) {
$submitButton.val(wpnav_admin.strings.saving);
} else {
$submitButton.text(wpnav_admin.strings.saving);
}
setTimeout(function() {
$submitButton.prop('disabled', false).removeClass('wpnav-loading');
if ($submitButton.is('input')) {
$submitButton.val(originalValue);
} else {
$submitButton.text(originalValue);
}
}, 10000);
}
});
if (window.location.search.indexOf('settings-updated=true') !== -1) {
var message = wpnav_admin.strings.settings_saved || 'Settings saved successfully!';
if (window.location.search.indexOf('imported=') !== -1) {
var match = window.location.search.match(/imported=(\d+)/);
if (match) {
message = wpnav_admin.strings.domains_imported ?
wpnav_admin.strings.domains_imported.replace('%d', match[1]) :
'Successfully imported ' + match[1] + ' domains.';
}
}
$('.wrap h1').after('<div class="notice notice-success is-dismissible"><p>' + message + '</p></div>');
setTimeout(function() {
$('.notice-success').fadeIn();
}, 100);
}
if (window.location.search.indexOf('tab=') !== -1) {
setTimeout(function() {
$('.notice-success').fadeIn();
}, 100);
}
$('.wp-list-table tbody tr').hover(
function() {
$(this).addClass('wpnav-row-hover');
},
function() {
$(this).removeClass('wpnav-row-hover');
}
);
$('#auto_redirect_enabled').change(function() {
if ($(this).is(':checked')) {
$('#redirect_delay_row').show();
} else {
$('#redirect_delay_row').hide();
}
});
// Enhanced preview functionality
$('#page_title').on('input', function() {
$('#preview-page-title').text($(this).val());
});
$('#page_subtitle').on('input', function() {
var subtitle = $(this).val();
if (subtitle) {
$('#preview-page-subtitle').text(subtitle).show();
} else {
$('#preview-page-subtitle').hide();
}
});
$('#url_label').on('input', function() {
$('#preview-url-label').text($(this).val());
});
$('#warning_text').on('input', function() {
$('#preview-warning-text').text($(this).val());
});
$('#countdown_text').on('input', function() {
var text = $(this).val().replace('{seconds}', '5');
$('#preview-countdown-text').text(text);
});
$('#button_text_continue').on('input', function() {
$('#preview-continue-btn').text($(this).val());
});
$('#button_text_back').on('input', function() {
$('#preview-back-btn').text($(this).val());
});
// Show/hide warning message functionality
$('input[name="show_warning_message"], textarea[name="warning_text"]').on('change input', function() {
var showWarning = $('input[name="show_warning_message"]').is(':checked');
var warningText = $('textarea[name="warning_text"]').val().trim();
if (showWarning && warningText !== '') {
$('#preview-warning').show();
} else {
$('#preview-warning').hide();
}
});
function setupRedirectPagePreview() {
function updatePreview() {
var colorScheme = $('select[name="color_scheme"]').val();
var template = $('input[name="template"]:checked').val();
var warningText = $('textarea[name="warning_text"]').val();
var showWarningMessage = $('input[name="show_warning_message"]').is(':checked');
var continueText = $('input[name="button_text_continue"]').val();
var backText = $('input[name="button_text_back"]').val();
var showLogo = $('input[name="show_logo"]').is(':checked');
var showUrlFull = $('input[name="show_url_full"]').is(':checked');
var showSecurityTips = $('input[name="show_security_tips"]').is(':checked');
var showBackButton = $('input[name="show_back_button"]').is(':checked');
console.log('Updating preview - Template:', template, 'Color:', colorScheme);
var $preview = $('.preview-container');
$preview.removeClass('wpnav-color-blue wpnav-color-green wpnav-color-red');
$preview.addClass('wpnav-color-' + colorScheme);
var $container = $preview.find('.wpnav-container');
$container.removeClass('wpnav-simple wpnav-minimal wpnav-default wpnav-full');
$container.addClass('wpnav-' + template);
$('#preview-warning-text').text(warningText);
$('#preview-continue-btn').text(continueText);
$('#preview-back-btn').text(backText);
$('.wpnav-site-logo').toggle(showLogo);
$('.wpnav-url-full').toggle(showUrlFull);
$('.wpnav-security-tips').toggle(showSecurityTips);
$('#preview-back-btn').toggle(showBackButton);
$('#preview-warning').toggle(showWarningMessage && warningText.trim() !== '');
updateTemplateSpecificElements(template);
}
function updateTemplateSpecificElements(template) {
var $container = $('.wpnav-container');
var $header = $('.wpnav-header');
switch(template) {
case 'simple':
$container.css({
'max-width': '480px',
'padding': '30px 25px',
'text-align': 'center'
});
$header.find('.wpnav-title').css('font-size', '22px');
break;
case 'minimal':
$container.css({
'max-width': '680px',
'padding': '30px',
'text-align': 'center'
});
$header.find('.wpnav-title').css('font-size', '24px');
break;
case 'full':
$container.css({
'max-width': '880px',
'padding': '50px',
'text-align': 'left'
});
$header.find('.wpnav-title').css('font-size', '36px');
break;
default: // default
$container.css({
'max-width': '780px',
'padding': '40px',
'text-align': 'center'
});
$header.find('.wpnav-title').css('font-size', '28px');
break;
}
}
$('select[name="color_scheme"]').on('change', updatePreview);
$('input[name="template"]').on('change', updatePreview);
$('textarea[name="warning_text"]').on('input', updatePreview);
$('input[name="show_warning_message"]').on('change', updatePreview);
$('input[name="button_text_continue"]').on('input', updatePreview);
$('input[name="button_text_back"]').on('input', updatePreview);
$('input[name="show_logo"]').on('change', updatePreview);
$('input[name="show_url_full"]').on('change', updatePreview);
$('input[name="show_security_tips"]').on('change', updatePreview);
$('input[name="show_back_button"]').on('change', updatePreview);
updatePreview();
}
if ($('.wpnav-tab[data-tab="redirect_page"]').length) {
setupRedirectPagePreview();
}
$('#redirect_delay, #cookie_duration, #stats_retention').on('input', function() {
var $this = $(this);
var val = parseInt($this.val());
var min = parseInt($this.attr('min'));
var max = parseInt($this.attr('max'));
if (val < min || val > max) {
$this.addClass('error').css('border-color', '#dc3232');
} else {
$this.removeClass('error').css('border-color', '');
}
});
var draftTimeout;
$('textarea[name="whitelist_domains"], textarea[name="warning_text"], textarea[name="custom_css"]').on('input', function() {
var $this = $(this);
clearTimeout(draftTimeout);
draftTimeout = setTimeout(function() {
var draftKey = 'wpnav_draft_' + $this.attr('name');
try {
sessionStorage.setItem(draftKey, $this.val());
if ($this.siblings('.draft-saved').length === 0) {
$this.after('<span class="draft-saved" style="font-size: 11px; color: #46b450; margin-left: 5px;">' + wpnav_admin.strings.draft_saved + '</span>');
setTimeout(function() {
$('.draft-saved').fadeOut();
}, 2000);
}
} catch (e) {
console.log('Session storage not available');
}
}, 2000);
});
$('textarea[name="whitelist_domains"], textarea[name="warning_text"], textarea[name="custom_css"]').each(function() {
var $this = $(this);
var draftKey = 'wpnav_draft_' + $this.attr('name');
try {
var draft = sessionStorage.getItem(draftKey);
if (draft && draft !== $this.val() && draft.trim() !== '') {
var restore = confirm(wpnav_admin.strings.restore_draft);
if (restore) {
$this.val(draft);
if ($this.attr('name') === 'warning_text') {
$('#preview-warning-text').text(draft);
}
}
}
} catch (e) {
console.log('Session storage not available');
}
});
$('form').on('submit', function() {
setTimeout(function() {
if (window.location.search.indexOf('settings-updated=true') !== -1) {
try {
sessionStorage.removeItem('wpnav_draft_whitelist_domains');
sessionStorage.removeItem('wpnav_draft_warning_text');
sessionStorage.removeItem('wpnav_draft_custom_css');
} catch (e) {
console.log('Session storage not available');
}
}
}, 100);
});
function validateForm($form) {
var isValid = true;
var errors = [];
var redirectDelay = $('#redirect_delay').val();
if (redirectDelay && (redirectDelay < 1 || redirectDelay > 30)) {
errors.push('Redirect delay must be between 1 and 30 seconds.');
isValid = false;
}
var cookieDuration = $('#cookie_duration').val();
if (cookieDuration && (cookieDuration < 1 || cookieDuration > 365)) {
errors.push('Cookie duration must be between 1 and 365 days.');
isValid = false;
}
var statsRetention = $('#stats_retention').val();
if (statsRetention && (statsRetention < 1 || statsRetention > 365)) {
errors.push('Stats retention must be between 1 and 365 days.');
isValid = false;
}
if (!isValid) {
var tabName = $form.closest('.wpnav-tab-section').attr('data-section');
var statusId = 'wpnav-settings-status';
if (tabName === 'whitelist') {
statusId = 'wpnav-whitelist-status';
} else if (tabName === 'redirect_page') {
statusId = 'wpnav-redirect-status';
} else if (tabName === 'logs_statistics') {
statusId = 'wpnav-logs-status';
}
showNotice(statusId, errors.join(' '), 'error');
}
return isValid;
}
function updateUrlParameter(url, param, paramVal) {
var newAdditionalURL = "";
var tempArray = url.split("?");
var baseURL = tempArray[0];
var additionalURL = tempArray[1];
var temp = "";
if (additionalURL) {
tempArray = additionalURL.split("&");
for (var i = 0; i < tempArray.length; i++) {
if (tempArray[i].split('=')[0] != param) {
newAdditionalURL += temp + tempArray[i];
temp = "&";
}
}
}
var rows_txt = temp + "" + param + "=" + paramVal;
return baseURL + "?" + newAdditionalURL + rows_txt;
}
function showNotice(elementId, message, type) {
var $notice = $('#' + elementId);
if ($notice.length === 0) {
$notice = $('<span id="' + elementId + '" class="wpnav-notice"></span>');
$('.card h2').first().after($notice);
}
$notice.removeClass('wpnav-notice-success wpnav-notice-error')
.addClass('wpnav-notice-' + type)
.text(message)
.show()
.delay(4000)
.fadeOut();
}
function getFormattedDate() {
var now = new Date();
var year = now.getFullYear();
var month = ('0' + (now.getMonth() + 1)).slice(-2);
var day = ('0' + now.getDate()).slice(-2);
return year + month + day;
}
function initColorSchemePreview() {
$('select[name="color_scheme"]').on('change', function() {
var scheme = $(this).val();
var $preview = $('.wpnav-redirect-preview');
$preview.removeClass('wpnav-color-blue wpnav-color-green wpnav-color-red wpnav-color-light');
$preview.addClass('wpnav-color-' + scheme);
});
}
if ($('.wpnav-redirect-preview').length) {
initColorSchemePreview();
}
console.log('WPNav Links Admin initialized');
var activeTab = $('.wpnav-tab.active').data('tab') || 'basic_settings';
if (activeTab) {
$('.wpnav-tab-section').hide();
$('.wpnav-tab-section[data-section="' + activeTab + '"]').show();
}
var urlParams = new URLSearchParams(window.location.search);
var tabFromUrl = urlParams.get('tab');
if (tabFromUrl) {
$('.wpnav-tab').removeClass('active');
$('.wpnav-tab[data-tab="' + tabFromUrl + '"]').addClass('active');
$('.wpnav-tab-section').hide();
$('.wpnav-tab-section[data-section="' + tabFromUrl + '"]').show();
}
});

1
assets/i-like-food.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.6 KiB

1
assets/leaf.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 40"><path fill="#000" d="M0 40a19.96 19.96 0 0 1 5.9-14.11 20.17 20.17 0 0 1 19.44-5.2A20 20 0 0 1 20.2 40H0zM65.32.75A20.02 20.02 0 0 1 40.8 25.26 20.02 20.02 0 0 1 65.32.76zM.07 0h20.1l-.08.07A20.02 20.02 0 0 1 .75 5.25 20.08 20.08 0 0 1 .07 0zm1.94 40h2.53l4.26-4.24v-9.78A17.96 17.96 0 0 0 2 40zm5.38 0h9.8a17.98 17.98 0 0 0 6.67-16.42L7.4 40zm3.43-15.42v9.17l11.62-11.59c-3.97-.5-8.08.3-11.62 2.42zm32.86-.78A18 18 0 0 0 63.85 3.63L43.68 23.8zm7.2-19.17v9.15L62.43 2.22c-3.96-.5-8.05.3-11.57 2.4zm-3.49 2.72c-4.1 4.1-5.81 9.69-5.13 15.03l6.61-6.6V6.02c-.51.41-1 .85-1.48 1.33zM17.18 0H7.42L3.64 3.78A18 18 0 0 0 17.18 0zM2.08 0c-.01.8.04 1.58.14 2.37L4.59 0H2.07z"></path></svg>

After

Width:  |  Height:  |  Size: 739 B

131
class-admin.php Normal file
View file

@ -0,0 +1,131 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPNAV_Admin {
private $options;
private $plugin;
public function __construct($options) {
$this->options = $options;
$this->plugin = new WPNAV_Links();
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
add_filter('plugin_action_links_wpnav-links/wpnav-links.php', array($this, 'add_settings_link'));
add_action('wp_ajax_wpnav_export_stats', array($this, 'ajax_export_stats'));
}
public function add_admin_menu() {
add_submenu_page(
'tools.php',
__('External Links Redirect', 'wpnav-links'),
__('External Links', 'wpnav-links'),
'manage_options',
'wpnav-links',
array($this, 'display_admin_page')
);
}
public function enqueue_admin_scripts($hook) {
if ($hook !== 'tools_page_wpnav-links') {
return;
}
wp_enqueue_style(
'wpnav-admin-style',
WPNAV_LINKS_PLUGIN_URL . 'admin.css',
array(),
WPNAV_LINKS_VERSION
);
wp_enqueue_script(
'wpnav-admin-script',
WPNAV_LINKS_PLUGIN_URL . 'admin.js',
array('jquery'),
WPNAV_LINKS_VERSION,
true
);
wp_localize_script(
'wpnav-admin-script',
'wpnav_admin',
array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wpnav_admin_nonce'),
'strings' => array(
'domain_exists' => __('Domain %s is already in the whitelist.', 'wpnav-links'),
'domain_added' => __('Added %s to whitelist.', 'wpnav-links'),
'common_domains_added' => __('Added common domains to whitelist.', 'wpnav-links'),
'whitelist_cleared' => __('Whitelist cleared.', 'wpnav-links'),
'confirm_clear' => __('Are you sure you want to clear all domains from the whitelist?', 'wpnav-links'),
'whitelist_empty' => __('Whitelist is empty, cannot export.', 'wpnav-links'),
'export_success' => __('Whitelist exported successfully.', 'wpnav-links'),
'export_unsupported' => __('Export not supported in this browser.', 'wpnav-links'),
'saving' => __('Saving...', 'wpnav-links'),
'draft_saved' => __('Draft saved', 'wpnav-links'),
'restore_draft' => __('A draft was found for this field. Would you like to restore it?', 'wpnav-links'),
'settings_saved' => __('Settings saved successfully!', 'wpnav-links'),
'domains_imported' => __('Successfully imported %d domains.', 'wpnav-links')
)
)
);
}
public function add_settings_link($links) {
$settings_link = '<a href="' . admin_url('tools.php?page=wpnav-links') . '">' . __('Settings', 'wpnav-links') . '</a>';
array_unshift($links, $settings_link);
return $links;
}
public function display_admin_page() {
include WPNAV_LINKS_PLUGIN_DIR . 'admin-page.php';
}
public function ajax_export_stats() {
if (!check_ajax_referer('wpnav_admin_nonce', 'nonce', false)) {
wp_send_json_error(array('message' => __('Security check failed', 'wpnav-links')));
}
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => __('Insufficient permissions', 'wpnav-links')));
}
$start_date = isset($_POST['start_date']) ? sanitize_text_field($_POST['start_date']) : '';
$end_date = isset($_POST['end_date']) ? sanitize_text_field($_POST['end_date']) : '';
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=wpnav_stats_' . date('Y-m-d') . '.csv');
$output = fopen('php://output', 'w');
fputcsv($output, array(
__('Target URL', 'wpnav-links'),
__('Source Page', 'wpnav-links'),
__('Click Time', 'wpnav-links'),
__('IP Address', 'wpnav-links'),
__('User Agent', 'wpnav-links')
));
$stats = $this->plugin->get_stats(array(
'start_date' => $start_date,
'end_date' => $end_date,
'limit' => 5000
));
foreach ($stats as $row) {
fputcsv($output, array(
$row->target_url,
$row->source_page,
$row->click_time,
$row->user_ip,
isset($row->user_agent) ? $row->user_agent : ''
));
}
fclose($output);
wp_die();
}
}

323
class-public.php Normal file
View file

@ -0,0 +1,323 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPNAV_Public {
private $options;
private $security;
public function __construct($options, $security) {
$this->options = $options;
$this->security = $security;
if (isset($options['enabled']) && $options['enabled']) {
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
$this->setup_content_filters();
add_action('wp_footer', array($this, 'add_footer_script'));
}
}
public function enqueue_scripts() {
wp_enqueue_script(
'wpnav-redirect-script',
WPNAV_LINKS_PLUGIN_URL . 'redirect.js',
array('jquery'),
WPNAV_LINKS_VERSION,
true
);
wp_localize_script(
'wpnav-redirect-script',
'wpnav_params',
array(
'home_url' => home_url(),
'site_domain' => parse_url(home_url(), PHP_URL_HOST),
'exclude_class' => $this->options['exclude_css_class'],
'open_new_tab' => $this->options['open_in_new_tab'],
'redirect_method' => isset($this->options['url_format']) ? $this->options['url_format'] : 'query',
'url_encoding' => 'base64',
'permalink_structure' => get_option('permalink_structure') ? true : false,
'whitelist_domains' => $this->get_whitelist_domains(),
'strings' => array(
'external_link' => __('External link', 'wpnav-links'),
'opens_new_window' => __('(opens in a new window)', 'wpnav-links')
)
)
);
}
private function get_whitelist_domains() {
$whitelist = array();
if (!empty($this->options['whitelist_domains'])) {
$whitelist = explode("\n", $this->options['whitelist_domains']);
$whitelist = array_map('trim', $whitelist);
$whitelist = array_filter($whitelist);
}
return $whitelist;
}
private function setup_content_filters() {
if (isset($this->options['intercept_content']) && $this->options['intercept_content']) {
add_filter('the_content', array($this, 'process_content'));
}
if (isset($this->options['intercept_comments']) && $this->options['intercept_comments']) {
add_filter('comment_text', array($this, 'process_content'));
}
if (isset($this->options['intercept_widgets']) && $this->options['intercept_widgets']) {
add_filter('widget_text', array($this, 'process_content'));
add_filter('widget_custom_html_content', array($this, 'process_content'));
}
}
public function process_content($content) {
if (empty($content)) {
return $content;
}
$dom = new DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML('<?xml encoding="UTF-8"><div>' . $content . '</div>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
$links = $dom->getElementsByTagName('a');
$modified = false;
$links_to_process = array();
foreach ($links as $link) {
$links_to_process[] = $link;
}
foreach ($links_to_process as $link) {
$href = $link->getAttribute('href');
if (empty($href)) {
continue;
}
if (!empty($this->options['exclude_css_class'])) {
$class = $link->getAttribute('class');
if (strpos($class, $this->options['exclude_css_class']) !== false) {
continue;
}
}
if ($this->is_external_link($href)) {
if ($this->security->check_whitelist_domain($href)) {
continue;
}
$rel = $link->getAttribute('rel');
$rel_values = empty($rel) ? array() : explode(' ', $rel);
$rel_values = array_merge($rel_values, array('nofollow', 'noopener', 'noreferrer'));
$rel_values = array_unique($rel_values);
$link->setAttribute('rel', implode(' ', $rel_values));
if ($this->options['open_in_new_tab']) {
$link->setAttribute('target', '_blank');
// Add screen reader text for accessibility
$title = $link->getAttribute('title');
if (empty($title)) {
$link->setAttribute('title', __('External link (opens in a new window)', 'wpnav-links'));
}
}
$redirect_url = $this->security->get_redirect_url($href);
$link->setAttribute('href', $redirect_url);
$link->setAttribute('data-wpnav-external', '1');
$modified = true;
}
}
if (!$modified) {
return $content;
}
$new_html = $dom->saveHTML($dom->documentElement);
$new_html = preg_replace('/<\?xml encoding="UTF-8"\?>/', '', $new_html);
$new_html = preg_replace('/<\/?div>/', '', $new_html);
return $new_html;
}
private function is_external_link($url) {
if (strpos($url, '://') === false && strpos($url, '//') !== 0) {
return false;
}
$parsed_url = parse_url($url);
if (empty($parsed_url) || !isset($parsed_url['host'])) {
return false;
}
$site_domain = parse_url(home_url(), PHP_URL_HOST);
$is_external = ($parsed_url['host'] !== $site_domain);
if ($is_external) {
if (!empty($this->options['auto_whitelist'])) {
if (!empty($this->options['auto_whitelist']['same_root'])) {
$site_root = $this->get_root_domain($site_domain);
$url_root = $this->get_root_domain($parsed_url['host']);
if ($site_root === $url_root) {
$is_external = false;
}
}
if ($is_external && !empty($this->options['auto_whitelist']['search_engines'])) {
$search_engines = array('google.', 'bing.', 'yahoo.', 'baidu.', 'yandex.', 'duckduckgo.');
foreach ($search_engines as $engine) {
if (strpos($parsed_url['host'], $engine) !== false) {
$is_external = false;
break;
}
}
}
}
}
return $is_external;
}
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;
}
public function add_footer_script() {
?>
<script type="text/javascript">
(function($) {
function processAjaxContent() {
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes && mutation.addedNodes.length > 0) {
var $links = $(mutation.addedNodes).find('a');
if ($links.length) {
processExternalLinks($links);
}
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
function processExternalLinks($elements) {
$elements.each(function() {
var $link = $(this);
var href = $link.attr('href');
if (!href) return;
if ($link.attr('data-wpnav-external')) return;
if ($link.hasClass(wpnav_params.exclude_class)) return;
if (isExternalLink(href) && !isWhitelistedDomain(href)) {
$link.attr('rel', 'nofollow noopener noreferrer');
if (wpnav_params.open_new_tab) {
$link.attr('target', '_blank');
// Add accessibility attributes
var currentTitle = $link.attr('title') || '';
if (!currentTitle.includes('<?php echo esc_js(__('opens in a new window', 'wpnav-links')); ?>')) {
var newTitle = currentTitle ?
currentTitle + ' <?php echo esc_js(__('(opens in a new window)', 'wpnav-links')); ?>' :
'<?php echo esc_js(__('External link (opens in a new window)', 'wpnav-links')); ?>';
$link.attr('title', newTitle);
}
}
$link.attr('href', getRedirectUrl(href));
$link.attr('data-wpnav-external', '1');
}
});
}
function isExternalLink(url) {
if (url.indexOf('://') === -1 && url.indexOf('//') !== 0) {
return false;
}
var parser = document.createElement('a');
parser.href = url;
return parser.hostname !== wpnav_params.site_domain;
}
function isWhitelistedDomain(url) {
if (!wpnav_params.whitelist_domains || wpnav_params.whitelist_domains.length === 0) {
return false;
}
var parser = document.createElement('a');
parser.href = url;
var hostname = parser.hostname;
return wpnav_params.whitelist_domains.indexOf(hostname) !== -1;
}
function getRedirectUrl(url) {
var encodedUrl = encodeUrl(url);
var currentPage = encodeURIComponent(window.location.href);
switch(wpnav_params.redirect_method) {
case 'path':
if (wpnav_params.permalink_structure) {
return wpnav_params.home_url + '/go/' + encodedUrl;
} else {
return wpnav_params.home_url + '?wpnav_redirect=1&url_param=' + encodeURIComponent(encodedUrl) + '&ref=' + currentPage;
}
break;
case 'target':
return wpnav_params.home_url + '?wpnav_redirect=1&target=' + encodeURIComponent(url) + '&ref=' + currentPage;
break;
default:
return wpnav_params.home_url + '?wpnav_redirect=1&url=' + encodeURIComponent(encodedUrl) + '&ref=' + currentPage;
break;
}
}
function encodeUrl(url) {
if (wpnav_params.redirect_method === 'target') {
return url;
}
try {
return btoa(url).replace(/[+/=]/g, function(match) {
return {'+': '-', '/': '_', '=': ''}[match];
});
} catch (e) {
return encodeURIComponent(url);
}
}
$(document).ready(function() {
processExternalLinks($('a:not([data-wpnav-external])'));
processAjaxContent();
});
})(jQuery);
</script>
<?php
}
}

494
class-wpnav-links.php Normal file
View file

@ -0,0 +1,494 @@
<?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 . '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 . 'redirect-template.php'
);
foreach ($template_locations as $template) {
if (file_exists($template)) {
include $template;
return;
}
}
include WPNAV_LINKS_PLUGIN_DIR . '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;
}
}

135
external-indicator.css Normal file
View file

@ -0,0 +1,135 @@
a[data-wpnav-external] {
position: relative;
}
a[data-wpnav-external] .wpnav-external-icon {
display: inline-block;
font-size: 0.8em;
margin-left: 3px;
opacity: 0.6;
transition: opacity 0.2s ease;
vertical-align: super;
line-height: 1;
color: #646970;
text-decoration: none;
}
a[data-wpnav-external]:hover .wpnav-external-icon {
opacity: 1;
color: #1d2327;
}
a[data-wpnav-external]:focus .wpnav-external-icon {
opacity: 1;
color: #2271b1;
}
.wpnav-external-icon-box::after {
content: "⧉";
font-size: 0.7em;
margin-left: 2px;
opacity: 0.5;
color: #646970;
}
.wpnav-external-icon-arrow::after {
content: "↗";
font-size: 0.8em;
margin-left: 2px;
opacity: 0.6;
color: #646970;
}
.entry-content a[data-wpnav-external] .wpnav-external-icon,
.comment-content a[data-wpnav-external] .wpnav-external-icon {
color: #2271b1;
opacity: 0.7;
}
.entry-content a[data-wpnav-external]:hover .wpnav-external-icon,
.comment-content a[data-wpnav-external]:hover .wpnav-external-icon {
opacity: 1;
}
.widget a[data-wpnav-external] .wpnav-external-icon {
color: #50575e;
opacity: 0.6;
}
.widget a[data-wpnav-external]:hover .wpnav-external-icon {
opacity: 1;
color: #2271b1;
}
.menu a[data-wpnav-external] .wpnav-external-icon,
.nav-menu a[data-wpnav-external] .wpnav-external-icon {
font-size: 0.7em;
margin-left: 2px;
opacity: 0.5;
}
.site-footer a[data-wpnav-external] .wpnav-external-icon {
color: #8c8f94;
opacity: 0.6;
}
.site-footer a[data-wpnav-external]:hover .wpnav-external-icon {
opacity: 1;
color: #50575e;
}
.wp-block-button__link[data-wpnav-external] .wpnav-external-icon,
.button[data-wpnav-external] .wpnav-external-icon {
color: inherit;
opacity: 0.8;
}
@media (prefers-contrast: high) {
a[data-wpnav-external] .wpnav-external-icon {
opacity: 1;
font-weight: bold;
color: #000;
}
a[data-wpnav-external]:hover .wpnav-external-icon {
color: #2271b1;
}
}
@media (prefers-reduced-motion: reduce) {
a[data-wpnav-external] .wpnav-external-icon {
transition: none;
}
}
@media print {
.wpnav-external-icon {
display: none;
}
}
.screen-reader-text.wpnav-external-text {
position: absolute !important;
clip: rect(1px, 1px, 1px, 1px);
word-wrap: normal !important;
overflow: hidden;
}
a[data-wpnav-external]:focus-visible {
outline: 2px solid #2271b1;
outline-offset: 2px;
}
@media screen and (max-width: 768px) {
a[data-wpnav-external] .wpnav-external-icon {
font-size: 0.75em;
margin-left: 2px;
}
}
@media screen and (max-width: 480px) {
a[data-wpnav-external] .wpnav-external-icon {
font-size: 0.7em;
margin-left: 1px;
}
}

837
frontend.css Normal file
View file

@ -0,0 +1,837 @@
.wpnav-redirect-page *,
.wpnav-redirect-page *::before,
.wpnav-redirect-page *::after {
box-sizing: border-box;
}
.wpnav-redirect-page {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
background: #f0f2f5;
color: #2c3e50;
min-height: 100vh;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.wpnav-redirect-page.wpnav-color-blue {
background: #e3f2fd;
}
.wpnav-redirect-page.wpnav-color-green {
background: #e8f5e8;
}
.wpnav-redirect-page.wpnav-color-red {
background: #ffebee;
}
.wpnav-page-overlay {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
position: relative;
overflow-x: hidden;
}
.wpnav-container {
max-width: 780px;
width: 100%;
margin: 0 auto;
padding: 40px;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
border: 1px solid #e1e5e9;
text-align: center;
position: relative;
animation: wpnav-slideUp 0.6s ease-out;
}
@keyframes wpnav-slideUp {
from {
opacity: 0;
transform: translateY(30px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.wpnav-simple {
max-width: 480px;
padding: 30px 25px;
text-align: center;
}
.wpnav-simple .wpnav-title {
font-size: 22px;
margin-bottom: 20px;
font-weight: 500;
}
.wpnav-simple .wpnav-url-container {
margin: 20px 0;
}
.wpnav-simple .wpnav-url {
padding: 15px;
margin-bottom: 20px;
}
.wpnav-simple .wpnav-url-domain {
font-size: 16px;
font-weight: 600;
}
.wpnav-simple .wpnav-buttons {
gap: 10px;
}
.wpnav-simple .wpnav-btn {
padding: 12px 20px;
font-size: 14px;
}
.wpnav-simple .wpnav-countdown {
margin-top: 15px;
font-size: 13px;
}
.wpnav-minimal {
max-width: 680px;
padding: 30px;
}
.wpnav-full {
max-width: 880px;
padding: 50px;
text-align: left;
}
.wpnav-logo img {
max-height: 60px;
margin-bottom: 20px;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
width: auto;
height: 60px;
}
@keyframes wpnav-pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
.wpnav-title {
font-size: 28px;
font-weight: 600;
margin: 0 0 10px;
color: #2c3e50;
letter-spacing: -0.5px;
}
.wpnav-subtitle {
font-size: 16px;
color: #7f8c8d;
margin: 0 0 30px;
font-weight: 400;
}
.wpnav-minimal .wpnav-title {
font-size: 24px;
}
.wpnav-full .wpnav-title {
font-size: 36px;
text-align: center;
}
.wpnav-header {
margin-bottom: 40px;
text-align: center;
}
.wpnav-warning {
background: #fff3cd;
border: 1px solid #f39c12;
border-radius: 8px;
font-size: 16px;
padding: 20px;
margin: 30px 0;
color: #8b4513;
font-weight: 400;
box-shadow: 0 2px 4px rgba(243, 156, 18, 0.1);
}
.wpnav-url-container {
margin: 30px 0;
}
.wpnav-url-label {
font-size: 14px;
color: #7f8c8d;
margin-bottom: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.wpnav-url {
background: #f8f9fa;
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 20px;
margin: 15px 0;
word-break: break-all;
position: relative;
transition: all 0.3s ease;
}
.wpnav-url:hover {
border-color: #667eea;
}
.wpnav-url-domain {
font-weight: 700;
font-size: 18px;
color: #2c3e50;
display: block;
margin-bottom: 5px;
}
.wpnav-url-path {
color: #7f8c8d;
font-size: 14px;
}
.wpnav-url-full {
font-size: 12px;
color: #95a5a6;
margin-top: 10px;
font-family: 'Monaco', 'Consolas', monospace;
}
.wpnav-security-status {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #e9ecef;
}
.wpnav-security-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.wpnav-security-label {
font-weight: 600;
color: #2c3e50;
font-size: 14px;
}
.wpnav-https-badge {
display: inline-block;
padding: 4px 8px;
border-radius: 12px;
color: white;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
min-width: 60px;
text-align: center;
}
.wpnav-https-badge.secure {
background-color: #00a32a;
}
.wpnav-https-badge.insecure {
background-color: #d63638;
}
.wpnav-security-warning {
background: rgba(231, 76, 60, 0.1);
border: 1px solid rgba(231, 76, 60, 0.3);
border-radius: 8px;
padding: 10px;
margin-top: 10px;
font-size: 13px;
color: #c0392b;
}
.wpnav-security-tips {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
text-align: left;
}
.wpnav-security-tips h4 {
margin: 0 0 15px;
color: #2c3e50;
font-size: 16px;
font-weight: 600;
}
.wpnav-security-tips ul {
margin: 0;
padding-left: 20px;
color: #5a6c7d;
}
.wpnav-security-tips li {
margin-bottom: 8px;
font-size: 14px;
}
.wpnav-copy-btn {
position: absolute;
top: 15px;
right: 15px;
background: #667eea;
color: white;
border: none;
border-radius: 6px;
padding: 8px 12px;
font-size: 12px;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
transition: all 0.3s ease;
}
.wpnav-copy-btn:hover {
background: #5a67d8;
transform: scale(1.05);
}
.wpnav-info {
display: grid;
grid-template-columns: 1fr;
gap: 30px;
margin: 40px 0;
}
.wpnav-info-item {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 25px;
}
.wpnav-info-title {
font-weight: 700;
font-size: 18px;
margin: 0 0 15px;
color: #2c3e50;
display: flex;
align-items: center;
gap: 10px;
}
.wpnav-tips-list {
margin: 0;
padding-left: 0;
list-style: none;
color: #5a6c7d;
}
.wpnav-tips-list li {
font-weight: 400;
font-size: 16px;
margin-bottom: 10px;
padding-left: 25px;
position: relative;
}
.wpnav-tips-list li:before {
content: "✓";
position: absolute;
left: 0;
color: #27ae60;
font-weight: bold;
}
.wpnav-warning-tips {
background: rgba(231, 76, 60, 0.1);
border: 1px solid rgba(231, 76, 60, 0.3);
border-radius: 8px;
padding: 15px;
margin-top: 20px;
}
.wpnav-warning-tips h4 {
color: #e74c3c;
margin: 0 0 10px;
font-size: 14px;
}
.wpnav-actions {
margin-top: 40px;
}
.wpnav-buttons {
display: flex;
gap: 15px;
justify-content: center;
margin-bottom: 30px;
flex-wrap: wrap;
}
.wpnav-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 10px 20px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
font-family: inherit;
min-width: 140px;
position: relative;
overflow: hidden;
}
.wpnav-btn:before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
}
.wpnav-btn:hover:before {
left: 100%;
}
.wpnav-btn-primary {
background: #667eea;
color: white;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.wpnav-btn-primary:hover {
background: #5a67d8;
}
.wpnav-btn-secondary {
background: #f8f9fa;
color: #5a6c7d;
border: 2px solid #e9ecef;
}
.wpnav-btn-secondary:hover {
background: white;
color: #2c3e50;
border-color: #bdc3c7;
}
.wpnav-options {
margin: 30px 0;
}
.wpnav-checkbox {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
cursor: pointer;
font-size: 14px;
color: #5a6c7d;
transition: color 0.3s ease;
user-select: none;
}
.wpnav-checkbox:hover {
color: #2c3e50;
}
.wpnav-checkbox input[type="checkbox"] {
display: none;
}
.checkmark {
width: 20px;
height: 20px;
border: 2px solid #bdc3c7;
border-radius: 4px;
position: relative;
transition: all 0.3s ease;
flex-shrink: 0;
}
.wpnav-checkbox input:checked + .checkmark {
background: #667eea;
border-color: #667eea;
}
.wpnav-checkbox input:checked + .checkmark:after {
content: '✓';
position: absolute;
color: white;
font-size: 12px;
font-weight: bold;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.wpnav-countdown {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
font-size: 14px;
color: #7f8c8d;
margin-top: 20px;
padding: 15px;
background: rgba(52, 152, 219, 0.1);
border-radius: 8px;
border: 1px solid rgba(52, 152, 219, 0.2);
}
.countdown-circle {
position: relative;
animation: wpnav-rotate 1s linear infinite;
}
@keyframes wpnav-rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
#countdown-progress {
transition: stroke-dashoffset 1s linear;
}
.wpnav-footer {
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid rgba(233, 236, 239, 0.8);
text-align: center;
}
.wpnav-footer-text {
font-size: 12px;
color: #95a5a6;
}
.wpnav-footer-text a {
color: #667eea;
text-decoration: none;
font-weight: 600;
}
.wpnav-toast {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%) translateY(-100px);
background: rgba(39, 174, 96, 0.95);
color: white;
padding: 12px 24px;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
z-index: 10000;
transition: transform 0.3s ease;
}
.wpnav-toast.show {
transform: translateX(-50%) translateY(0);
}
.wpnav-color-blue .wpnav-container {
border-color: #e3f2fd;
}
.wpnav-color-blue .wpnav-btn-primary {
background: #3498db;
}
.wpnav-color-blue .wpnav-btn-primary:hover {
background: #2980b9;
}
.wpnav-color-blue .wpnav-copy-btn {
background: #3498db;
}
.wpnav-color-green .wpnav-container {
border-color: #e8f5e8;
}
.wpnav-color-green .wpnav-btn-primary {
background: #27ae60;
}
.wpnav-color-green .wpnav-btn-primary:hover {
background: #229954;
}
.wpnav-color-green .wpnav-copy-btn {
background: #27ae60;
}
.wpnav-color-red .wpnav-container {
border-color: #ffebee;
}
.wpnav-color-red .wpnav-btn-primary {
background: #e74c3c;
}
.wpnav-color-red .wpnav-btn-primary:hover {
background: #c0392b;
}
.wpnav-color-red .wpnav-copy-btn {
background: #e74c3c;
}
@media screen and (max-width: 768px) {
.wpnav-page-overlay {
padding: 15px;
align-items: flex-start;
padding-top: 40px;
}
.wpnav-container {
padding: 30px 25px;
margin: 0;
border-radius: 8px;
max-width: none;
width: 100%;
}
.wpnav-simple {
padding: 25px 20px;
}
.wpnav-simple .wpnav-title {
font-size: 20px;
}
.wpnav-simple .wpnav-url {
padding: 12px;
}
.wpnav-simple .wpnav-btn {
padding: 10px 16px;
font-size: 13px;
min-width: auto;
}
.wpnav-full {
padding: 30px 25px;
}
.wpnav-title {
font-size: 24px;
}
.wpnav-minimal .wpnav-title {
font-size: 22px;
}
.wpnav-full .wpnav-title {
font-size: 26px;
}
.wpnav-buttons {
flex-direction: column;
gap: 12px;
}
.wpnav-btn {
width: 100%;
padding: 18px 20px;
font-size: 16px;
}
.wpnav-info {
grid-template-columns: 1fr;
gap: 20px;
margin: 30px 0;
}
.wpnav-info-item {
padding: 20px;
}
.wpnav-url {
padding: 15px;
}
.wpnav-copy-btn {
position: static;
width: 100%;
margin-top: 10px;
justify-content: center;
}
.wpnav-warning {
padding: 15px;
margin: 20px 0;
}
.wpnav-countdown {
flex-direction: column;
gap: 10px;
text-align: center;
}
.wpnav-security-tips {
padding: 15px;
margin: 15px 0;
}
}
@media screen and (max-width: 480px) {
.wpnav-container {
padding: 25px 20px;
border-radius: 6px;
}
.wpnav-simple {
padding: 20px 15px;
max-width: none;
}
.wpnav-simple .wpnav-title {
font-size: 18px;
margin-bottom: 15px;
}
.wpnav-simple .wpnav-url-container {
margin: 15px 0;
}
.wpnav-simple .wpnav-url {
padding: 10px;
margin-bottom: 15px;
}
.wpnav-simple .wpnav-url-domain {
font-size: 14px;
}
.wpnav-simple .wpnav-buttons {
gap: 8px;
flex-direction: column;
}
.wpnav-simple .wpnav-btn {
width: 100%;
padding: 12px 16px;
font-size: 14px;
min-width: auto;
}
.wpnav-title {
font-size: 20px;
}
.wpnav-btn {
padding: 16px 20px;
font-size: 15px;
min-width: auto;
}
.wpnav-url-domain {
font-size: 16px;
}
}
@media (hover: none) and (pointer: coarse) {
.wpnav-btn:hover {
transform: none;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.wpnav-btn:active {
transform: scale(0.95);
}
.wpnav-url:hover {
transform: none;
border-color: #e9ecef;
box-shadow: none;
}
}
.wpnav-btn-rounded {
border-radius: 8px;
}
.wpnav-btn-square {
border-radius: 0;
}
.wpnav-btn-pill {
border-radius: 25px;
}
.wpnav-progress-bar {
width: 100%;
height: 4px;
background: rgba(0, 0, 0, 0.1);
border-radius: 2px;
margin-top: 10px;
overflow: hidden;
}
.wpnav-progress-fill {
height: 100%;
background: #667eea;
border-radius: 2px;
width: 100%;
transition: width 1s linear;
}
.wpnav-color-blue .wpnav-progress-fill {
background: #3498db;
}
.wpnav-color-green .wpnav-progress-fill {
background: #27ae60;
}
.wpnav-color-red .wpnav-progress-fill {
background: #e74c3c;
}
@media (prefers-reduced-motion: reduce) {
.wpnav-redirect-page *,
.wpnav-redirect-page *::before,
.wpnav-redirect-page *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

Binary file not shown.

View file

@ -0,0 +1,964 @@
# Chinese (China) translation for WPNav Links
# Copyright (C) 2024 WPNav Links
# This file is distributed under the same license as the WPNav Links package.
msgid ""
msgstr ""
"Project-Id-Version: WPNav Links 2.0.0\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wpnav-links\n"
"POT-Creation-Date: 2024-01-01 12:00+0000\n"
"PO-Revision-Date: 2025-06-01 06:26+0800\n"
"Last-Translator: WPNav Links Team\n"
"Language-Team: Chinese (China)\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 3.6\n"
#: wpnav-links.php:100
msgid "External Links Monitor"
msgstr "外链监控"
#: wpnav-links.php:150
msgid "Active"
msgstr "已启用"
#: wpnav-links.php:150
msgid "Inactive"
msgstr "未启用"
#: wpnav-links.php:153
msgid "Manual Only"
msgstr "仅手动"
#: wpnav-links.php:159
msgid "Total Redirects"
msgstr "总跳转数"
#: wpnav-links.php:164
msgid "Today"
msgstr "今天"
#: wpnav-links.php:172
msgid "Last 7 Days"
msgstr "最近7天"
#: wpnav-links.php:177
msgid "Whitelisted"
msgstr "白名单"
#: wpnav-links.php:184
msgid "Top Domains This Week"
msgstr "本周热门域名"
#: wpnav-links.php:199
msgid "No external link activity yet."
msgstr "暂无外链活动记录。"
#: wpnav-links.php:201
msgid "Enable the plugin to start tracking external links."
msgstr "启用插件开始跟踪外链。"
#: wpnav-links.php:208
msgid "Enable Plugin"
msgstr "启用插件"
#: wpnav-links.php:210
msgid "View Details"
msgstr "查看详情"
#: wpnav-links.php:213
msgid "Manage Whitelist"
msgstr "管理白名单"
#: wpnav-links.php:322
msgid "Settings"
msgstr "设置"
#: wpnav-links.php:323
msgid "Support"
msgstr "支持"
#: wpnav-links.php:324
msgid "Documentation"
msgstr "文档"
#: class-wpnav-links.php:50 class-admin.php:21
msgid "External Links"
msgstr "外部链接"
#: class-wpnav-links.php:80
msgid "Invalid or missing URL parameter"
msgstr "无效或缺失的URL参数"
#: class-wpnav-links.php:85
msgid "Invalid URL format"
msgstr "无效的URL格式"
#: class-wpnav-links.php:169
msgid "Redirect Error: "
msgstr "跳转错误:"
#: class-wpnav-links.php:170
msgid "External Link Redirect Error"
msgstr "外链跳转错误"
#: class-admin.php:20
msgid "External Links Redirect"
msgstr "外链跳转"
#: class-admin.php:49
msgid "Security check failed"
msgstr "安全检查失败"
#: class-admin.php:53
msgid "Insufficient permissions"
msgstr "权限不足"
#: class-admin.php:60
msgid "Target URL"
msgstr "目标URL"
#: class-admin.php:61 admin-page.php:776
msgid "Source Page"
msgstr "来源页面"
#: class-admin.php:62
msgid "Click Time"
msgstr "点击时间"
#: class-admin.php:63
msgid "IP Address"
msgstr "IP地址"
#: class-admin.php:64
msgid "User Agent"
msgstr "用户代理"
#: admin-page.php:25
msgid "You do not have sufficient permissions to access this page."
msgstr "您没有足够的权限访问此页面。"
#: admin-page.php:47
msgid "Basic settings saved successfully!"
msgstr "基本设置保存成功!"
#: admin-page.php:49
msgid "Failed to save basic settings. Database error."
msgstr "保存基本设置失败。数据库错误。"
#: admin-page.php:67
msgid "Whitelist settings saved successfully!"
msgstr "白名单设置保存成功!"
#: admin-page.php:69
msgid "Failed to save whitelist settings. Database error."
msgstr "保存白名单设置失败。数据库错误。"
#: admin-page.php:99
msgid "Redirect page settings saved successfully!"
msgstr "跳转页面设置保存成功!"
#: admin-page.php:101
msgid "Failed to save redirect page settings. Database error."
msgstr "保存跳转页面设置失败。数据库错误。"
#: admin-page.php:127
msgid "Successfully imported %d domains."
msgstr "成功导入 %d 个域名。"
#: admin-page.php:129
msgid "Failed to import CSV data. Database error."
msgstr "导入CSV数据失败。数据库错误。"
#: admin-page.php:132
msgid "No valid domains found in the CSV file."
msgstr "CSV文件中未找到有效域名。"
#: admin-page.php:135
msgid "Please select a CSV file to upload."
msgstr "请选择要上传的CSV文件。"
#: admin-page.php:145
msgid "Settings saved successfully!"
msgstr "设置保存成功!"
#: admin-page.php:183
msgid "Custom Template Detected:"
msgstr "检测到自定义模板:"
#: admin-page.php:183
msgid ""
"Your theme has a custom redirect template. Settings below may not apply if "
"your custom template doesn't support them."
msgstr ""
"您的主题有自定义跳转模板。如果您的自定义模板不支持,以下设置可能不会生效。"
#: admin-page.php:187
msgid "Basic Settings"
msgstr "基本设置"
#: admin-page.php:190
msgid "Redirect Page"
msgstr "跳转页面"
#: admin-page.php:193
msgid "Whitelist"
msgstr "白名单"
#: admin-page.php:196
msgid "Statistics"
msgstr "统计"
#: admin-page.php:199
msgid "Import/Export"
msgstr "导入/导出"
#: admin-page.php:208
msgid "Enable Redirect"
msgstr "启用跳转"
#: admin-page.php:211
msgid "Enable external link redirect functionality"
msgstr "启用外链跳转功能"
#: admin-page.php:215
msgid "Auto Redirect"
msgstr "自动跳转"
#: admin-page.php:218
msgid "Enable automatic redirect"
msgstr "启用自动跳转"
#: admin-page.php:219
msgid ""
"When enabled, users will be automatically redirected after the specified "
"delay."
msgstr "启用后,用户将在指定延迟后自动跳转。"
#: admin-page.php:223
msgid "Redirect Delay"
msgstr "跳转延迟"
#: admin-page.php:226
msgid "Seconds to wait before automatic redirect (1-30 seconds)."
msgstr "自动跳转前等待的秒数1-30秒。"
#: admin-page.php:230
msgid "Open in New Tab"
msgstr "新标签页打开"
#: admin-page.php:233
msgid "Open external links in new tab/window"
msgstr "在新标签页/窗口中打开外部链接"
#: admin-page.php:237
msgid "URL Format"
msgstr "URL格式"
#: admin-page.php:241
msgid "Query Format (?url=encoded_url)"
msgstr "查询格式 (?url=encoded_url)"
#: admin-page.php:245
msgid "Path Format (/go/encoded_url)"
msgstr "路径格式 (/go/encoded_url)"
#: admin-page.php:249
msgid "Target Format (?target=encoded_url)"
msgstr "目标格式 (?target=encoded_url)"
#: admin-page.php:252
msgid "Choose URL format style for external link redirects."
msgstr "选择外链跳转的URL格式样式。"
#: admin-page.php:257
msgid "Content Processing"
msgstr "内容处理"
#: admin-page.php:260
msgid "Process Post Content"
msgstr "处理文章内容"
#: admin-page.php:263
msgid "Process external links in post and page content"
msgstr "处理文章和页面内容中的外部链接"
#: admin-page.php:267
msgid "Process Comments"
msgstr "处理评论"
#: admin-page.php:270
msgid "Process external links in comments"
msgstr "处理评论中的外部链接"
#: admin-page.php:274
msgid "Process Widgets"
msgstr "处理小工具"
#: admin-page.php:277
msgid "Process external links in widgets"
msgstr "处理小工具中的外部链接"
#: admin-page.php:281
msgid "Exclude CSS Class"
msgstr "排除CSS类"
#: admin-page.php:284
msgid "Links with this CSS class will not be redirected."
msgstr "具有此CSS类的链接将不会被跳转。"
#: admin-page.php:289
msgid "User Experience"
msgstr "用户体验"
#: admin-page.php:292
msgid "Admin Exempt"
msgstr "管理员免跳转"
#: admin-page.php:295
msgid "Skip redirect page for admin users"
msgstr "为管理员用户跳过跳转页面"
#: admin-page.php:299
msgid "Cookie Duration"
msgstr "Cookie持续时间"
#: admin-page.php:302
msgid "Days to remember user's \"Do not show again\" preference."
msgstr "记住用户\"不再显示\"偏好的天数。"
#: admin-page.php:306
msgid "Stats Retention"
msgstr "统计保留"
#: admin-page.php:309
msgid "Days to retain statistics data before automatic cleanup."
msgstr "自动清理前保留统计数据的天数。"
#: admin-page.php:314
msgid "Save Settings"
msgstr "保存设置"
#: admin-page.php:322
msgid "Domain Whitelist Management"
msgstr "域名白名单管理"
#: admin-page.php:324
msgid ""
"Manage domains that should not be redirected through the external link page."
msgstr "管理不应通过外链页面跳转的域名。"
#: admin-page.php:331
msgid "Domain List"
msgstr "域名列表"
#: admin-page.php:334
msgid "Add Common"
msgstr "添加常用"
#: admin-page.php:335
msgid "Clear All"
msgstr "清空全部"
#: admin-page.php:339
msgid ""
"Enter domains that should not redirect, one per line. Example: google.com "
"(do not include http:// or https://)"
msgstr ""
"输入不应跳转的域名每行一个。示例google.com不要包含http://或https://"
#: admin-page.php:343
msgid "Click to add common domains to your whitelist:"
msgstr "点击将常用域名添加到您的白名单:"
#: admin-page.php:345
msgid "Google"
msgstr "谷歌"
#: admin-page.php:346
msgid "Baidu"
msgstr "百度"
#: admin-page.php:347
msgid "Bing"
msgstr "必应"
#: admin-page.php:348
msgid "YouTube"
msgstr "优酷"
#: admin-page.php:349
msgid "Facebook"
msgstr "脸书"
#: admin-page.php:350
msgid "Twitter"
msgstr "推特"
#: admin-page.php:351
msgid "LinkedIn"
msgstr "领英"
#: admin-page.php:352
msgid "Instagram"
msgstr "Instagram"
#: admin-page.php:356
msgid "Auto Whitelist Rules"
msgstr "自动白名单规则"
#: admin-page.php:362
msgid "Same root domain no redirect"
msgstr "相同根域名不跳转"
#: admin-page.php:364
msgid ""
"Subdomains of your site won't redirect (e.g., blog.example.com and "
"shop.example.com)"
msgstr "您网站的子域名不会跳转例如blog.example.com 和 shop.example.com"
#: admin-page.php:371
msgid "Search engines no redirect"
msgstr "搜索引擎不跳转"
#: admin-page.php:373
msgid "Major search engines (Google, Baidu, Bing, etc.) won't redirect"
msgstr "主要搜索引擎(谷歌、百度、必应等)不会跳转"
#: admin-page.php:380
msgid "Save Whitelist"
msgstr "保存白名单"
#: admin-page.php:388
msgid "Redirect Page Design"
msgstr "跳转页面设计"
#: admin-page.php:389
msgid ""
"Customize the appearance and content of the redirect warning page shown to "
"users."
msgstr "自定义显示给用户的跳转警告页面的外观和内容。"
#: admin-page.php:396
msgid "Template Style"
msgstr "模板样式"
#: admin-page.php:401
msgid "Simple"
msgstr "简洁"
#: admin-page.php:401
msgid "Ultra-minimal design with just essentials"
msgstr "极简设计,仅包含必要元素"
#: admin-page.php:405
msgid "Minimal"
msgstr "简约"
#: admin-page.php:405
msgid "Clean and lightweight design"
msgstr "简洁轻量的设计"
#: admin-page.php:409 admin-page.php:851
msgid "Default"
msgstr "默认"
#: admin-page.php:409
msgid "Balanced information display"
msgstr "平衡的信息显示"
#: admin-page.php:413
msgid "Full"
msgstr "完整"
#: admin-page.php:413
msgid "Comprehensive with security info"
msgstr "包含安全信息的综合版"
#: admin-page.php:418
msgid "Color Scheme"
msgstr "配色方案"
#: admin-page.php:421
msgid "Blue - Professional"
msgstr "蓝色 - 专业"
#: admin-page.php:422
msgid "Green - Safe"
msgstr "绿色 - 安全"
#: admin-page.php:423
msgid "Red - Warning"
msgstr "红色 - 警告"
#: admin-page.php:427
msgid "Page Content"
msgstr "页面内容"
#: admin-page.php:430
msgid "Page Title:"
msgstr "页面标题:"
#: admin-page.php:434
msgid "Page Subtitle (optional):"
msgstr "页面副标题(可选):"
#: admin-page.php:438
msgid "URL Label:"
msgstr "URL标签"
#: admin-page.php:443
msgid "Display Options"
msgstr "显示选项"
#: admin-page.php:448
msgid "Show warning message"
msgstr "显示警告信息"
#: admin-page.php:452
msgid "Show site logo"
msgstr "显示站点Logo"
#: admin-page.php:456
msgid "Show full URL below domain"
msgstr "在域名下方显示完整URL"
#: admin-page.php:460
msgid "Show HTTPS security status"
msgstr "显示HTTPS安全状态"
#: admin-page.php:464
msgid "Show security tips and warnings"
msgstr "显示安全提示和警告"
#: admin-page.php:468
msgid "Show back button"
msgstr "显示返回按钮"
#: admin-page.php:473
msgid "Warning Message"
msgstr "警告信息"
#: admin-page.php:476
msgid ""
"Main warning message displayed to users. Leave empty to hide the warning box."
msgstr "显示给用户的主要警告信息。留空则隐藏警告框。"
#: admin-page.php:480
msgid "Button Settings"
msgstr "按钮设置"
#: admin-page.php:483
msgid "Continue Button Text:"
msgstr "继续按钮文本:"
#: admin-page.php:487
msgid "Back Button Text:"
msgstr "返回按钮文本:"
#: admin-page.php:491
msgid "Button Style:"
msgstr "按钮样式:"
#: admin-page.php:494
msgid "Rounded"
msgstr "圆角"
#: admin-page.php:495
msgid "Square"
msgstr "方形"
#: admin-page.php:496
msgid "Pill Shape"
msgstr "药丸形"
#: admin-page.php:501
msgid "Countdown Settings"
msgstr "倒计时设置"
#: admin-page.php:504
msgid "Countdown Text:"
msgstr "倒计时文本:"
#: admin-page.php:506
msgid "Use {seconds} as placeholder for countdown number"
msgstr "使用 {seconds} 作为倒计时数字的占位符"
#: admin-page.php:511
msgid "Show progress bar during countdown"
msgstr "倒计时期间显示进度条"
#: admin-page.php:516
msgid "Custom CSS"
msgstr "自定义CSS"
#: admin-page.php:519
msgid "Add custom CSS to further customize the redirect page appearance"
msgstr "添加自定义CSS进一步自定义跳转页面外观"
#: admin-page.php:524
msgid "Save Redirect Page Settings"
msgstr "保存跳转页面设置"
#: admin-page.php:530
msgid "Live Preview"
msgstr "实时预览"
#: admin-page.php:580 redirect-template.php:252
msgid "Security Tips"
msgstr "安全提示"
#: admin-page.php:582 redirect-template.php:255
msgid "Verify the link is from a trusted source"
msgstr "验证链接来自可信来源"
#: admin-page.php:583 redirect-template.php:258
msgid "Check for secure HTTPS connection"
msgstr "检查安全的HTTPS连接"
#: admin-page.php:608
msgid "Import Whitelist"
msgstr "导入白名单"
#: admin-page.php:609
msgid "Upload CSV file to bulk import domains to your whitelist."
msgstr "上传CSV文件批量导入域名到您的白名单。"
#: admin-page.php:616
msgid "CSV File"
msgstr "CSV文件"
#: admin-page.php:619
msgid "Select a CSV file containing domain names (one per line)"
msgstr "选择包含域名的CSV文件每行一个"
#: admin-page.php:624
msgid "Import CSV"
msgstr "导入CSV"
#: admin-page.php:630
msgid "Export Whitelist"
msgstr "导出白名单"
#: admin-page.php:631
msgid "Download current whitelist domains as CSV file."
msgstr "将当前白名单域名下载为CSV文件。"
#: admin-page.php:635
msgid "Downloads a CSV file with all whitelisted domains."
msgstr "下载包含所有白名单域名的CSV文件。"
#: admin-page.php:640
msgid "Export Statistics"
msgstr "导出统计"
#: admin-page.php:641
msgid "Download statistics data for the selected date range."
msgstr "下载选定日期范围的统计数据。"
#: admin-page.php:647
msgid "Start Date:"
msgstr "开始日期:"
#: admin-page.php:648
msgid "End Date:"
msgstr "结束日期:"
#: admin-page.php:654
msgid "Downloads a CSV file with redirect statistics data."
msgstr "下载包含跳转统计数据的CSV文件。"
#: admin-page.php:662
msgid "Access Statistics"
msgstr "访问统计"
#: admin-page.php:663
msgid "View external link access statistics and logs."
msgstr "查看外链访问统计和日志。"
#: admin-page.php:668
msgid "Top External Links"
msgstr "热门外链"
#: admin-page.php:676
msgid "%s clicks"
msgstr "%s 次点击"
#: admin-page.php:684
msgid ""
"No data available yet. Top links will appear here after users start clicking "
"external links."
msgstr "暂无数据。用户开始点击外链后,热门链接将显示在这里。"
#: admin-page.php:689
msgid "Activity Overview"
msgstr "活动概览"
#: admin-page.php:705
msgid "This Month"
msgstr "本月"
#: admin-page.php:709
msgid "Total Records"
msgstr "总记录数"
#: admin-page.php:713
msgid "Pages"
msgstr "页面"
#: admin-page.php:717
msgid "Daily Avg"
msgstr "日均"
#: admin-page.php:721
msgid "Unique Domains"
msgstr "独立域名"
#: admin-page.php:726
msgid "HTTPS Links"
msgstr "HTTPS链接"
#: admin-page.php:730
msgid "HTTP Links"
msgstr "HTTP链接"
#: admin-page.php:744
msgid "Usage Guide"
msgstr "使用指南"
#: admin-page.php:746
msgid "Enable redirect functionality in basic settings"
msgstr "在基本设置中启用跳转功能"
#: admin-page.php:747
msgid "Configure which content areas to process"
msgstr "配置要处理的内容区域"
#: admin-page.php:748
msgid "Customize redirect page appearance"
msgstr "自定义跳转页面外观"
#: admin-page.php:749
msgid "Add domains to whitelist that should not redirect"
msgstr "将不应跳转的域名添加到白名单"
#: admin-page.php:750
msgid "View access data in statistics tab"
msgstr "在统计标签页中查看访问数据"
#: admin-page.php:761
msgid "Date Range"
msgstr "日期范围"
#: admin-page.php:765
msgid "to"
msgstr "至"
#: admin-page.php:767
msgid "Apply Filter"
msgstr "应用筛选"
#: admin-page.php:768
msgid "Reset"
msgstr "重置"
#: admin-page.php:777
msgid "Time"
msgstr "时间"
#: admin-page.php:779
msgid "Security"
msgstr "安全性"
#: admin-page.php:793
msgid "N/A"
msgstr "不适用"
#: admin-page.php:818
msgid "Page %1$s of %2$s"
msgstr "第 %1$s 页,共 %2$s 页"
#: admin-page.php:828
msgid "No redirect records found for the selected date range."
msgstr "在选定的日期范围内未找到跳转记录。"
#: admin-page.php:829
msgid ""
"Records will appear here after users click on external links processed by "
"the plugin."
msgstr "用户点击插件处理的外链后,记录将显示在这里。"
#: admin-page.php:836
msgid "System Information"
msgstr "系统信息"
#: admin-page.php:841
msgid "WordPress Version"
msgstr "WordPress版本"
#: admin-page.php:845
msgid "PHP Version"
msgstr "PHP版本"
#: admin-page.php:849
msgid "Permalink Structure"
msgstr "固定链接结构"
#: admin-page.php:855
msgid "Database Table"
msgstr "数据库表"
#: admin-page.php:859
msgid "Plugin URL"
msgstr "插件URL"
#: admin-page.php:863
msgid "Custom Template"
msgstr "自定义模板"
#: admin-page.php:865
msgid "Yes - Custom template detected"
msgstr "是 - 检测到自定义模板"
#: admin-page.php:865
msgid "No - Using default template"
msgstr "否 - 使用默认模板"
#: redirect-template.php:25
msgid "External Link Warning"
msgstr "外链警告"
#: redirect-template.php:27
msgid "You are about to visit:"
msgstr "您即将访问:"
#: redirect-template.php:28
msgid ""
"You are about to leave this site and visit an external website. We are not "
"responsible for the content of external websites."
msgstr "您即将离开本站并访问外部网站。我们不对外部网站的内容负责。"
#: redirect-template.php:32
msgid "Continue"
msgstr "继续"
#: redirect-template.php:33
msgid "Back"
msgstr "返回"
#: redirect-template.php:35
msgid "Auto redirect in {seconds} seconds"
msgstr "{seconds} 秒后自动跳转"
#: redirect-template.php:55
msgid "External Link Redirect - %s"
msgstr "外链跳转 - %s"
#: redirect-template.php:146
msgid "Don't show this again"
msgstr "不再显示"
#: redirect-template.php:213
msgid "Destination Information"
msgstr "目标信息"
#: redirect-template.php:230
msgid "Copy"
msgstr "复制"
#: redirect-template.php:238
msgid "Security Status"
msgstr "安全状态"
#: redirect-template.php:244
msgid ""
"This website does not use HTTPS encryption. Your data may not be secure."
msgstr "此网站未使用HTTPS加密。您的数据可能不安全。"
#: redirect-template.php:256
msgid "Avoid entering personal information on HTTP sites"
msgstr "避免在HTTP网站上输入个人信息"
#: redirect-template.php:257
msgid "Be cautious of download requests"
msgstr "谨慎对待下载请求"
#: redirect-template.php:263
msgid "Additional Caution Required"
msgstr "需要额外注意"
#: redirect-template.php:265
msgid "This link does not use secure HTTPS"
msgstr "此链接未使用安全的HTTPS"
#: redirect-template.php:266
msgid "Consider if this site is trustworthy before proceeding"
msgstr "继续之前请考虑此网站是否值得信任"
#: redirect-template.php:295
msgid "Don't show this warning for %d days"
msgstr "%d 天内不显示此警告"
#: redirect-template.php:342
msgid "Protected by %s"
msgstr "受 %s 保护"
#: redirect-template.php:385
msgid "Don't show this again (%d days)"
msgstr "不再显示(%d 天)"
#: redirect-template.php:461
msgid "Link copied to clipboard"
msgstr "链接已复制到剪贴板"
#: redirect-template.php:536
msgid "External link (opens in a new window)"
msgstr "外部链接(在新窗口中打开)"
#: class-public.php:69
msgid "External link"
msgstr "外部链接"
#: class-public.php:70 class-public.php:137
msgid "(opens in a new window)"
msgstr "(在新窗口中打开)"
#: class-public.php:135
msgid "opens in a new window"
msgstr "在新窗口中打开"
# Admin JavaScript strings
msgid "Domain %s is already in the whitelist."
msgstr "域名 %s 已经在白名单中。"
msgid "Added %s to whitelist."
msgstr "已将 %s 添加到白名单。"
msgid "Added common domains to whitelist."
msgstr "已将常用域名添加到白名单。"
msgid "Whitelist cleared."
msgstr "白名单已清空。"
msgid "Are you sure you want to clear all domains from the whitelist?"
msgstr "您确定要清空白名单中的所有域名吗?"
msgid "Whitelist is empty, cannot export."
msgstr "白名单为空,无法导出。"
msgid "Whitelist exported successfully."
msgstr "白名单导出成功。"
msgid "Export not supported in this browser."
msgstr "此浏览器不支持导出功能。"
msgid "Saving..."
msgstr "保存中..."
msgid "Draft saved"
msgstr "草稿已保存"
msgid "A draft was found for this field. Would you like to restore it?"
msgstr "发现此字段的草稿。您要恢复它吗?"

389
languages/wpnav-links.pot Normal file
View file

@ -0,0 +1,389 @@
# Copyright (C) 2024 WPNav Links
# This file is distributed under the same license as the WPNav Links package.
msgid ""
msgstr ""
"Project-Id-Version: WPNav Links 2.0.0\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wpnav-links\n"
"POT-Creation-Date: 2024-01-01 12:00+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
#: wpnav-links.php:100
msgid "External Links Monitor"
msgstr ""
#: wpnav-links.php:150
msgid "Active"
msgstr ""
#: wpnav-links.php:150
msgid "Inactive"
msgstr ""
#: wpnav-links.php:153
msgid "Manual Only"
msgstr ""
#: wpnav-links.php:159
msgid "Total Redirects"
msgstr ""
#: wpnav-links.php:164
msgid "Today"
msgstr ""
#: wpnav-links.php:172
msgid "Last 7 Days"
msgstr ""
#: wpnav-links.php:177
msgid "Whitelisted"
msgstr ""
#: wpnav-links.php:184
msgid "Top Domains This Week"
msgstr ""
#: wpnav-links.php:199
msgid "No external link activity yet."
msgstr ""
#: wpnav-links.php:201
msgid "Enable the plugin to start tracking external links."
msgstr ""
#: wpnav-links.php:208
msgid "Enable Plugin"
msgstr ""
#: wpnav-links.php:210
msgid "View Details"
msgstr ""
#: wpnav-links.php:213
msgid "Manage Whitelist"
msgstr ""
#: wpnav-links.php:322
msgid "Settings"
msgstr ""
#: wpnav-links.php:323
msgid "Support"
msgstr ""
#: wpnav-links.php:324
msgid "Documentation"
msgstr ""
#: class-wpnav-links.php:50
msgid "External Links"
msgstr ""
#: class-wpnav-links.php:80
msgid "Invalid or missing URL parameter"
msgstr ""
#: class-wpnav-links.php:85
msgid "Invalid URL format"
msgstr ""
#: class-wpnav-links.php:169
msgid "Redirect Error: "
msgstr ""
#: class-wpnav-links.php:170
msgid "External Link Redirect Error"
msgstr ""
#: class-admin.php:20
msgid "External Links Redirect"
msgstr ""
#: class-admin.php:21
msgid "External Links"
msgstr ""
#: class-admin.php:49
msgid "Security check failed"
msgstr ""
#: class-admin.php:53
msgid "Insufficient permissions"
msgstr ""
#: class-admin.php:60
msgid "Target URL"
msgstr ""
#: class-admin.php:61
msgid "Source Page"
msgstr ""
#: class-admin.php:62
msgid "Click Time"
msgstr ""
#: class-admin.php:63
msgid "IP Address"
msgstr ""
#: class-admin.php:64
msgid "User Agent"
msgstr ""
#: admin-page.php:25
msgid "You do not have sufficient permissions to access this page."
msgstr ""
#: admin-page.php:47
msgid "Basic settings saved successfully!"
msgstr ""
#: admin-page.php:49
msgid "Failed to save basic settings. Database error."
msgstr ""
#: admin-page.php:67
msgid "Whitelist settings saved successfully!"
msgstr ""
#: admin-page.php:69
msgid "Failed to save whitelist settings. Database error."
msgstr ""
#: admin-page.php:99
msgid "Redirect page settings saved successfully!"
msgstr ""
#: admin-page.php:101
msgid "Failed to save redirect page settings. Database error."
msgstr ""
#: admin-page.php:127
msgid "Successfully imported %d domains."
msgstr ""
#: admin-page.php:129
msgid "Failed to import CSV data. Database error."
msgstr ""
#: admin-page.php:132
msgid "No valid domains found in the CSV file."
msgstr ""
#: admin-page.php:135
msgid "Please select a CSV file to upload."
msgstr ""
#: admin-page.php:145
msgid "Settings saved successfully!"
msgstr ""
#: admin-page.php:183
msgid "Custom Template Detected:"
msgstr ""
#: admin-page.php:183
msgid "Your theme has a custom redirect template. Settings below may not apply if your custom template doesn't support them."
msgstr ""
#: admin-page.php:187
msgid "Basic Settings"
msgstr ""
#: admin-page.php:190
msgid "Redirect Page"
msgstr ""
#: admin-page.php:193
msgid "Whitelist"
msgstr ""
#: admin-page.php:196
msgid "Statistics"
msgstr ""
#: admin-page.php:199
msgid "Import/Export"
msgstr ""
#: admin-page.php:208
msgid "Enable Redirect"
msgstr ""
#: admin-page.php:211
msgid "Enable external link redirect functionality"
msgstr ""
#: admin-page.php:215
msgid "Auto Redirect"
msgstr ""
#: admin-page.php:218
msgid "Enable automatic redirect"
msgstr ""
#: admin-page.php:219
msgid "When enabled, users will be automatically redirected after the specified delay."
msgstr ""
#: admin-page.php:223
msgid "Redirect Delay"
msgstr ""
#: admin-page.php:226
msgid "Seconds to wait before automatic redirect (1-30 seconds)."
msgstr ""
#: admin-page.php:230
msgid "Open in New Tab"
msgstr ""
#: admin-page.php:233
msgid "Open external links in new tab/window"
msgstr ""
#: admin-page.php:237
msgid "URL Format"
msgstr ""
#: admin-page.php:241
msgid "Query Format (?url=encoded_url)"
msgstr ""
#: admin-page.php:245
msgid "Path Format (/go/encoded_url)"
msgstr ""
#: admin-page.php:249
msgid "Target Format (?target=encoded_url)"
msgstr ""
#: admin-page.php:252
msgid "Choose URL format style for external link redirects."
msgstr ""
#: admin-page.php:257
msgid "Content Processing"
msgstr ""
#: admin-page.php:260
msgid "Process Post Content"
msgstr ""
#: admin-page.php:263
msgid "Process external links in post and page content"
msgstr ""
#: redirect-template.php:25
msgid "External Link Warning"
msgstr ""
#: redirect-template.php:27
msgid "You are about to visit:"
msgstr ""
#: redirect-template.php:28
msgid "You are about to leave this site and visit an external website. We are not responsible for the content of external websites."
msgstr ""
#: redirect-template.php:32
msgid "Continue"
msgstr ""
#: redirect-template.php:33
msgid "Back"
msgstr ""
#: redirect-template.php:35
msgid "Auto redirect in {seconds} seconds"
msgstr ""
#: redirect-template.php:55
msgid "External Link Redirect - %s"
msgstr ""
#: redirect-template.php:146
msgid "Don't show this again"
msgstr ""
#: redirect-template.php:213
msgid "Destination Information"
msgstr ""
#: redirect-template.php:230
msgid "Copy"
msgstr ""
#: redirect-template.php:238
msgid "Security Status"
msgstr ""
#: redirect-template.php:244
msgid "This website does not use HTTPS encryption. Your data may not be secure."
msgstr ""
#: redirect-template.php:252
msgid "Security Tips"
msgstr ""
#: redirect-template.php:255
msgid "Verify the link is from a trusted source"
msgstr ""
#: redirect-template.php:256
msgid "Avoid entering personal information on HTTP sites"
msgstr ""
#: redirect-template.php:257
msgid "Be cautious of download requests"
msgstr ""
#: redirect-template.php:258
msgid "Check for secure HTTPS connection"
msgstr ""
#: redirect-template.php:263
msgid "Additional Caution Required"
msgstr ""
#: redirect-template.php:265
msgid "This link does not use secure HTTPS"
msgstr ""
#: redirect-template.php:266
msgid "Consider if this site is trustworthy before proceeding"
msgstr ""
#: redirect-template.php:295
msgid "Don't show this warning for %d days"
msgstr ""
#: redirect-template.php:342
msgid "Protected by %s"
msgstr ""
#: redirect-template.php:385
msgid "Don't show this again (%d days)"
msgstr ""
#: redirect-template.php:461
msgid "Link copied to clipboard"
msgstr ""
#: redirect-template.php:536
msgid "External link (opens in a new window)"
msgstr ""
#: class-public.php:69
msgid "External link"
msgstr ""
#: class-public.php:70
msgid "(opens in a new window)"
msgstr ""

176
readme.txt Normal file
View file

@ -0,0 +1,176 @@
=== WPNav Links ===
Contributors: wpnavlinks
Tags: external links, redirect, security, links, SEO
Requires at least: 5.0
Tested up to: 6.4
Requires PHP: 7.4
Stable tag: 2.0.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
A comprehensive WordPress external link redirect tool with customizable redirect pages, security features, and detailed analytics.
== Description ==
WPNav Links is a powerful WordPress plugin that helps you manage external links on your website by redirecting them through a customizable warning page. This provides better user experience, improved security, and detailed analytics about external link usage.
= Key Features =
* **Customizable Redirect Pages** - Choose from 4 different templates (Simple, Minimal, Default, Full) with 3 color schemes
* **Smart Content Processing** - Automatically processes external links in posts, pages, comments, and widgets
* **Domain Whitelist Management** - Easily manage domains that should not be redirected
* **Detailed Analytics** - Track external link clicks with comprehensive statistics
* **Security Features** - HTTPS detection, security warnings, and safety tips
* **User Experience Options** - Auto-redirect timers, progress bars, and "don't show again" options
* **Accessibility Ready** - Proper ARIA labels, keyboard navigation, and screen reader support
* **Mobile Optimized** - Touch gestures and responsive design for mobile devices
* **SEO Friendly** - Proper nofollow, noopener, and noreferrer attributes
* **Import/Export** - Bulk import/export domain whitelists via CSV
= Template Options =
1. **Simple** - Ultra-minimal design with just the essentials
2. **Minimal** - Clean and lightweight design
3. **Default** - Balanced information display
4. **Full** - Comprehensive with detailed security information
= Color Schemes =
* Blue - Professional appearance
* Green - Safe and trustworthy feel
* Red - Warning and caution emphasis
= Advanced Features =
* **Auto Whitelist Rules** - Automatically whitelist same-root domains and search engines
* **Flexible URL Formats** - Query, Path, or Target parameter formats
* **Content Filtering** - Choose which content areas to process (posts, comments, widgets)
* **Admin Dashboard Widget** - Quick overview of link activity and statistics
* **Custom CSS Support** - Add your own styling to redirect pages
* **Cookie Management** - Remember user preferences for specified durations
* **Data Cleanup** - Automatic old data cleanup with configurable retention periods
== Installation ==
1. Upload the plugin files to the `/wp-content/plugins/wpnav-links` directory, or install the plugin through the WordPress plugins screen directly.
2. Activate the plugin through the 'Plugins' screen in WordPress.
3. Use the Tools → External Links screen to configure the plugin.
4. Customize your redirect page design and add domains to your whitelist.
== Frequently Asked Questions ==
= How does the plugin work? =
The plugin automatically detects external links in your content and redirects them through a customizable warning page. Users see information about the destination before being redirected.
= Can I customize the redirect page? =
Yes! You can choose from 4 different templates, 3 color schemes, customize text content, and even add custom CSS.
= Which domains should I whitelist? =
Common domains to whitelist include social media platforms, search engines, and trusted partner sites. The plugin can automatically whitelist same-root domains and major search engines.
= Does the plugin affect SEO? =
The plugin actually helps SEO by adding proper nofollow, noopener, and noreferrer attributes to external links, which is recommended by search engines.
= Can I see statistics about external link usage? =
Yes! The plugin provides detailed analytics including top clicked links, daily/monthly statistics, HTTPS vs HTTP breakdown, and more.
= Is the plugin accessible? =
Yes, the plugin is built with accessibility in mind, including proper ARIA labels, keyboard navigation, and screen reader support.
= Can I export my whitelist? =
Yes, you can export your domain whitelist as a CSV file and also import domains from CSV files for bulk management.
== Screenshots ==
1. **Admin Dashboard** - Main settings interface with tabbed navigation
2. **Redirect Page Templates** - Four different template styles to choose from
3. **Live Preview** - Real-time preview of your redirect page design
4. **Whitelist Management** - Easy domain whitelist management with quick-add buttons
5. **Statistics Dashboard** - Comprehensive analytics and reporting
6. **Mobile Experience** - Responsive design works perfectly on mobile devices
== Changelog ==
= 2.0.0 =
* Major user experience improvements
* Added warning message show/hide control
* Fixed back button functionality with multiple fallback strategies
* Implemented complete internationalization support
* Enhanced mobile touch gestures and keyboard navigation
* Improved accessibility with better ARIA labels and screen reader support
* Added draft saving for textarea fields
* Enhanced preview functionality with real-time updates
* Better error handling and user feedback
* Optimized database queries and performance improvements
= 1.9.0 =
* Added 4 redirect page templates (Simple, Minimal, Default, Full)
* Introduced 3 color schemes (Blue, Green, Red)
* Enhanced security features with HTTPS detection
* Added progress bar option for countdown timer
* Improved mobile responsiveness and touch support
* Added copy-to-clipboard functionality
* Enhanced statistics dashboard with more metrics
= 1.8.0 =
* Added comprehensive analytics and statistics
* Introduced admin dashboard widget
* Added CSV import/export functionality
* Enhanced whitelist management with quick-add buttons
* Improved auto-redirect functionality with countdown timer
* Added custom CSS support for redirect pages
= 1.7.0 =
* Added domain whitelist functionality
* Introduced auto-whitelist rules for same-root domains and search engines
* Enhanced content processing with better DOM handling
* Added cookie management for user preferences
* Improved admin interface with tabbed navigation
= 1.6.0 =
* Added multiple URL format options (Query, Path, Target)
* Enhanced security with script injection detection
* Improved content filtering options
* Added admin exemption feature
* Better error handling and validation
= 1.5.0 =
* Initial stable release
* Basic external link redirection
* Simple redirect page
* Admin configuration panel
== Upgrade Notice ==
= 2.0.0 =
Major update with enhanced user experience, internationalization support, and improved functionality. Backup your settings before upgrading.
== Support ==
For support, please visit our [support forum](https://example.com/support) or [documentation](https://example.com/docs).
== Contributing ==
We welcome contributions! Please visit our [GitHub repository](https://github.com/example/wpnav-links) to report issues or submit pull requests.
== Privacy Policy ==
This plugin collects basic analytics data about external link clicks including:
- Target URL
- Source page
- Timestamp
- IP address (anonymized)
- User agent string
This data is stored locally on your server and is used solely for analytics purposes. No data is transmitted to external services.
== Credits ==
Developed by the WPNav Links team with contributions from the WordPress community.

561
redirect-template.php Normal file
View file

@ -0,0 +1,561 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
$target_url = get_query_var('target_url', '');
if (empty($target_url) && isset($_GET['target'])) {
$target_url = urldecode($_GET['target']);
}
$source_url = get_query_var('source_url', '');
if (empty($source_url) && isset($_GET['ref'])) {
$source_url = urldecode($_GET['ref']);
}
if (empty($target_url)) {
wp_redirect(home_url());
exit;
}
$options = get_option('wpnav_links_options');
$delay = isset($options['redirect_delay']) ? intval($options['redirect_delay']) : 5;
$auto_redirect_enabled = isset($options['auto_redirect_enabled']) ? $options['auto_redirect_enabled'] : 1;
$color_scheme = isset($options['color_scheme']) ? $options['color_scheme'] : 'blue';
$page_title = isset($options['page_title']) ? $options['page_title'] : __('External Link Warning', 'wpnav-links');
$page_subtitle = isset($options['page_subtitle']) ? $options['page_subtitle'] : '';
$url_label = isset($options['url_label']) ? $options['url_label'] : __('You are about to visit:', 'wpnav-links');
$warning_text = isset($options['warning_text']) ? $options['warning_text'] : __('You are about to leave this site and visit an external website. We are not responsible for the content of external websites.', 'wpnav-links');
$show_warning_message = isset($options['show_warning_message']) ? $options['show_warning_message'] : 1;
$cookie_duration = isset($options['cookie_duration']) ? intval($options['cookie_duration']) : 30;
$template = isset($options['template']) ? $options['template'] : 'default';
$show_logo = isset($options['show_logo']) ? $options['show_logo'] : false;
$show_url_full = isset($options['show_url_full']) ? $options['show_url_full'] : false;
$show_security_info = isset($options['show_security_info']) ? $options['show_security_info'] : 1;
$show_security_tips = isset($options['show_security_tips']) ? $options['show_security_tips'] : 0;
$show_back_button = isset($options['show_back_button']) ? $options['show_back_button'] : 1;
$custom_css = isset($options['custom_css']) ? $options['custom_css'] : '';
$button_text_continue = isset($options['button_text_continue']) ? $options['button_text_continue'] : __('Continue', 'wpnav-links');
$button_text_back = isset($options['button_text_back']) ? $options['button_text_back'] : __('Back', 'wpnav-links');
$button_style = isset($options['button_style']) ? $options['button_style'] : 'rounded';
$countdown_text = isset($options['countdown_text']) ? $options['countdown_text'] : __('Auto redirect in {seconds} seconds', 'wpnav-links');
$show_progress_bar = isset($options['show_progress_bar']) ? $options['show_progress_bar'] : 0;
$domain = parse_url($target_url, PHP_URL_HOST);
$is_https = strpos($target_url, 'https://') === 0;
$color_scheme_class = 'wpnav-color-' . $color_scheme;
$no_redirect_checked = isset($_COOKIE['wpnav_noredirect']) && $_COOKIE['wpnav_noredirect'] == '1';
$is_mobile = wp_is_mobile();
?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex,nofollow">
<title><?php printf(__('External Link Redirect - %s', 'wpnav-links'), get_bloginfo('name')); ?></title>
<?php wp_head(); ?>
<?php if (!empty($custom_css)) : ?>
<style type="text/css">
<?php echo wp_kses($custom_css, array()); ?>
</style>
<?php endif; ?>
</head>
<body class="wpnav-redirect-page <?php echo esc_attr($color_scheme_class); ?> <?php echo $is_mobile ? 'wpnav-mobile' : 'wpnav-desktop'; ?>" ontouchstart="">
<div class="wpnav-page-overlay">
<?php if ($template == 'simple') : ?>
<div class="wpnav-container wpnav-simple">
<h1 class="wpnav-title"><?php echo esc_html($page_title); ?></h1>
<div class="wpnav-url-container">
<div class="wpnav-url" data-url="<?php echo esc_attr($target_url); ?>">
<span class="wpnav-url-domain"><?php echo esc_html($domain); ?></span>
</div>
</div>
<div class="wpnav-buttons">
<button type="button" class="wpnav-btn wpnav-btn-primary wpnav-btn-<?php echo esc_attr($button_style); ?>" id="wpnav-continue" data-target="<?php echo esc_attr($target_url); ?>">
<?php echo esc_html($button_text_continue); ?>
</button>
<?php if ($show_back_button) : ?>
<button type="button" class="wpnav-btn wpnav-btn-secondary wpnav-btn-<?php echo esc_attr($button_style); ?>" id="wpnav-back">
<?php echo esc_html($button_text_back); ?>
</button>
<?php endif; ?>
</div>
<?php if ($auto_redirect_enabled && $delay > 0) : ?>
<div class="wpnav-countdown">
<span id="wpnav-countdown-text"><?php echo str_replace('{seconds}', $delay, esc_html($countdown_text)); ?></span>
</div>
<?php endif; ?>
</div>
<?php elseif ($template == 'minimal') : ?>
<div class="wpnav-container wpnav-minimal">
<?php if ($show_logo && has_custom_logo()) : ?>
<div class="wpnav-logo">
<?php the_custom_logo(); ?>
</div>
<?php endif; ?>
<h1 class="wpnav-title"><?php echo esc_html($page_title); ?></h1>
<?php if (!empty($page_subtitle)) : ?>
<p class="wpnav-subtitle"><?php echo esc_html($page_subtitle); ?></p>
<?php endif; ?>
<?php if ($show_warning_message && !empty(trim($warning_text))) : ?>
<div class="wpnav-warning"><?php echo esc_html($warning_text); ?></div>
<?php endif; ?>
<div class="wpnav-url-container">
<div class="wpnav-url-label"><?php echo esc_html($url_label); ?></div>
<div class="wpnav-url" data-url="<?php echo esc_attr($target_url); ?>">
<span class="wpnav-url-domain"><?php echo esc_html($domain); ?></span>
<?php if ($show_url_full) : ?>
<div class="wpnav-url-full"><?php echo esc_html($target_url); ?></div>
<?php endif; ?>
<?php if ($show_security_info) : ?>
<div class="wpnav-security-status">
<span class="wpnav-https-badge <?php echo $is_https ? 'secure' : 'insecure'; ?>">
<?php echo $is_https ? 'HTTPS' : 'HTTP'; ?>
</span>
</div>
<?php endif; ?>
</div>
</div>
<div class="wpnav-buttons">
<button type="button" class="wpnav-btn wpnav-btn-primary wpnav-btn-<?php echo esc_attr($button_style); ?>" id="wpnav-continue" data-target="<?php echo esc_attr($target_url); ?>">
<?php echo esc_html($button_text_continue); ?>
</button>
<?php if ($show_back_button) : ?>
<button type="button" class="wpnav-btn wpnav-btn-secondary wpnav-btn-<?php echo esc_attr($button_style); ?>" id="wpnav-back">
<?php echo esc_html($button_text_back); ?>
</button>
<?php endif; ?>
</div>
<div class="wpnav-options">
<label class="wpnav-checkbox">
<input type="checkbox" id="wpnav-no-redirect" <?php checked($no_redirect_checked); ?>>
<span class="checkmark"></span>
<?php esc_html_e('Don\'t show this again', 'wpnav-links'); ?>
</label>
</div>
<?php if ($auto_redirect_enabled && $delay > 0) : ?>
<div class="wpnav-countdown">
<span id="wpnav-countdown-text"><?php echo str_replace('{seconds}', $delay, esc_html($countdown_text)); ?></span>
<?php if ($show_progress_bar) : ?>
<div class="wpnav-progress-bar">
<div class="wpnav-progress-fill" id="progress-fill"></div>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<?php elseif ($template == 'full') : ?>
<div class="wpnav-container wpnav-full">
<div class="wpnav-header">
<?php if ($show_logo && has_custom_logo()) : ?>
<div class="wpnav-logo">
<?php the_custom_logo(); ?>
</div>
<?php endif; ?>
<h1 class="wpnav-title"><?php echo esc_html($page_title); ?></h1>
<?php if (!empty($page_subtitle)) : ?>
<p class="wpnav-subtitle"><?php echo esc_html($page_subtitle); ?></p>
<?php endif; ?>
</div>
<?php if ($show_warning_message && !empty(trim($warning_text))) : ?>
<div class="wpnav-warning">
<?php echo esc_html($warning_text); ?>
</div>
<?php endif; ?>
<div class="wpnav-info">
<div class="wpnav-info-item">
<h3 class="wpnav-info-title"><?php esc_html_e('Destination Information', 'wpnav-links'); ?></h3>
<div class="wpnav-url-container">
<div class="wpnav-url-label"><?php echo esc_html($url_label); ?></div>
<div class="wpnav-url" data-url="<?php echo esc_attr($target_url); ?>">
<div class="wpnav-url-parts">
<span class="wpnav-url-domain"><?php echo esc_html($domain); ?></span>
<span class="wpnav-url-path"><?php echo esc_html(str_replace($domain, '', parse_url($target_url, PHP_URL_HOST) . parse_url($target_url, PHP_URL_PATH))); ?></span>
</div>
<?php if ($show_url_full) : ?>
<div class="wpnav-url-full"><?php echo esc_html($target_url); ?></div>
<?php endif; ?>
<button type="button" class="wpnav-copy-btn" data-copy="<?php echo esc_attr($target_url); ?>">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M16 1H4C2.9 1 2 1.9 2 3V17H4V3H16V1ZM19 5H8C6.9 5 6 5.9 6 7V21C6 22.1 6.9 23 8 23H19C20.1 23 21 22.1 21 21V7C21 5.9 20.1 5 19 5ZM19 21H8V7H19V21Z" fill="currentColor"/>
</svg>
<?php esc_html_e('Copy', 'wpnav-links'); ?>
</button>
</div>
</div>
<?php if ($show_security_info) : ?>
<div class="wpnav-security-status">
<div class="wpnav-security-header">
<span class="wpnav-security-label"><?php esc_html_e('Security Status', 'wpnav-links'); ?></span>
<span class="wpnav-https-badge <?php echo $is_https ? 'secure' : 'insecure'; ?>">
<?php echo $is_https ? 'HTTPS' : 'HTTP'; ?>
</span>
</div>
<?php if (!$is_https) : ?>
<div class="wpnav-security-warning">
<p>⚠️ <?php esc_html_e('This website does not use HTTPS encryption. Your data may not be secure.', 'wpnav-links'); ?></p>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<?php if ($show_security_tips) : ?>
<div class="wpnav-info-item">
<h3 class="wpnav-info-title"><?php esc_html_e('Security Tips', 'wpnav-links'); ?></h3>
<div class="wpnav-tips">
<ul class="wpnav-tips-list">
<li><?php esc_html_e('Verify the link is from a trusted source', 'wpnav-links'); ?></li>
<li><?php esc_html_e('Avoid entering personal information on HTTP sites', 'wpnav-links'); ?></li>
<li><?php esc_html_e('Be cautious of download requests', 'wpnav-links'); ?></li>
<li><?php esc_html_e('Check for secure HTTPS connection', 'wpnav-links'); ?></li>
</ul>
</div>
<?php if (!$is_https) : ?>
<div class="wpnav-warning-tips">
<h4>⚠️ <?php esc_html_e('Additional Caution Required', 'wpnav-links'); ?></h4>
<ul>
<li><?php esc_html_e('This link does not use secure HTTPS', 'wpnav-links'); ?></li>
<li><?php esc_html_e('Consider if this site is trustworthy before proceeding', 'wpnav-links'); ?></li>
</ul>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<div class="wpnav-actions">
<div class="wpnav-buttons">
<button type="button" class="wpnav-btn wpnav-btn-primary wpnav-btn-<?php echo esc_attr($button_style); ?>" id="wpnav-continue" data-target="<?php echo esc_attr($target_url); ?>">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" fill="currentColor"/>
</svg>
<?php echo esc_html($button_text_continue); ?>
</button>
<?php if ($show_back_button) : ?>
<button type="button" class="wpnav-btn wpnav-btn-secondary wpnav-btn-<?php echo esc_attr($button_style); ?>" id="wpnav-back">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M12 20l1.41-1.41L7.83 13H20v-2H7.83l5.58-5.59L12 4l-8 8z" fill="currentColor"/>
</svg>
<?php echo esc_html($button_text_back); ?>
</button>
<?php endif; ?>
</div>
<div class="wpnav-options">
<label class="wpnav-checkbox">
<input type="checkbox" id="wpnav-no-redirect" <?php checked($no_redirect_checked); ?>>
<span class="checkmark"></span>
<?php printf(__('Don\'t show this warning for %d days', 'wpnav-links'), $cookie_duration); ?>
</label>
</div>
<?php if ($auto_redirect_enabled && $delay > 0) : ?>
<div class="wpnav-countdown">
<div class="countdown-circle">
<svg width="32" height="32" viewBox="0 0 32 32">
<circle cx="16" cy="16" r="14" stroke="currentColor" stroke-width="2" fill="none" opacity="0.3"/>
<circle id="countdown-progress" cx="16" cy="16" r="14" stroke="currentColor" stroke-width="2" fill="none"
stroke-dasharray="87.96" stroke-dashoffset="0" transform="rotate(-90 16 16)"/>
</svg>
</div>
<span id="wpnav-countdown-text"><?php echo str_replace('{seconds}', $delay, esc_html($countdown_text)); ?></span>
<?php if ($show_progress_bar) : ?>
<div class="wpnav-progress-bar">
<div class="wpnav-progress-fill" id="progress-fill"></div>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<div class="wpnav-footer">
<div class="wpnav-footer-text">
<?php printf(__('Protected by %s', 'wpnav-links'), '<a href="' . esc_url(home_url()) . '">' . esc_html(get_bloginfo('name')) . '</a>'); ?>
</div>
</div>
</div>
<?php else : ?>
<div class="wpnav-container wpnav-default">
<?php if ($show_logo && has_custom_logo()) : ?>
<div class="wpnav-logo">
<?php the_custom_logo(); ?>
</div>
<?php endif; ?>
<h1 class="wpnav-title"><?php echo esc_html($page_title); ?></h1>
<?php if (!empty($page_subtitle)) : ?>
<p class="wpnav-subtitle"><?php echo esc_html($page_subtitle); ?></p>
<?php endif; ?>
<?php if ($show_warning_message && !empty(trim($warning_text))) : ?>
<div class="wpnav-warning">
<?php echo esc_html($warning_text); ?>
</div>
<?php endif; ?>
<div class="wpnav-url-container">
<div class="wpnav-url-label"><?php echo esc_html($url_label); ?></div>
<div class="wpnav-url" data-url="<?php echo esc_attr($target_url); ?>">
<span class="wpnav-url-domain"><?php echo esc_html($domain); ?></span>
<?php if ($show_url_full) : ?>
<div class="wpnav-url-full"><?php echo esc_html($target_url); ?></div>
<?php endif; ?>
<?php if ($show_security_info) : ?>
<div class="wpnav-security-status">
<span class="wpnav-https-badge <?php echo $is_https ? 'secure' : 'insecure'; ?>">
<?php echo $is_https ? 'HTTPS' : 'HTTP'; ?>
</span>
</div>
<?php endif; ?>
</div>
</div>
<?php if ($show_security_tips) : ?>
<div class="wpnav-security-tips">
<h4><?php esc_html_e('Security Tips', 'wpnav-links'); ?></h4>
<ul>
<li><?php esc_html_e('Verify the link is from a trusted source', 'wpnav-links'); ?></li>
<li><?php esc_html_e('Check for secure HTTPS connection', 'wpnav-links'); ?></li>
<li><?php esc_html_e('Be cautious of download requests', 'wpnav-links'); ?></li>
</ul>
</div>
<?php endif; ?>
<div class="wpnav-buttons">
<button type="button" class="wpnav-btn wpnav-btn-primary wpnav-btn-<?php echo esc_attr($button_style); ?>" id="wpnav-continue" data-target="<?php echo esc_attr($target_url); ?>">
<?php echo esc_html($button_text_continue); ?>
</button>
<?php if ($show_back_button) : ?>
<button type="button" class="wpnav-btn wpnav-btn-secondary wpnav-btn-<?php echo esc_attr($button_style); ?>" id="wpnav-back">
<?php echo esc_html($button_text_back); ?>
</button>
<?php endif; ?>
</div>
<div class="wpnav-options">
<label class="wpnav-checkbox">
<input type="checkbox" id="wpnav-no-redirect" <?php checked($no_redirect_checked); ?>>
<span class="checkmark"></span>
<?php printf(__('Don\'t show this again (%d days)', 'wpnav-links'), $cookie_duration); ?>
</label>
</div>
<?php if ($auto_redirect_enabled && $delay > 0) : ?>
<div class="wpnav-countdown">
<span id="wpnav-countdown-text"><?php echo str_replace('{seconds}', $delay, esc_html($countdown_text)); ?></span>
<?php if ($show_progress_bar) : ?>
<div class="wpnav-progress-bar">
<div class="wpnav-progress-fill" id="progress-fill"></div>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<script type="text/javascript">
(function() {
var countdown = <?php echo $auto_redirect_enabled ? esc_js($delay) : 0; ?>;
var countdownElement = document.getElementById('wpnav-countdown-text');
var progressElement = document.getElementById('progress-fill');
var continueButton = document.getElementById('wpnav-continue');
var backButton = document.getElementById('wpnav-back');
var targetUrl = continueButton ? continueButton.getAttribute('data-target') : '';
var noRedirectCheckbox = document.getElementById('wpnav-no-redirect');
var copyButtons = document.querySelectorAll('.wpnav-copy-btn');
var countdownTemplate = <?php echo json_encode($countdown_text); ?>;
var initialCountdown = countdown;
var autoRedirectEnabled = <?php echo $auto_redirect_enabled ? 'true' : 'false'; ?>;
// Enhanced back button functionality
function goBack() {
var referrer = document.referrer;
var sourceUrl = <?php echo json_encode($source_url); ?>;
// Try multiple back strategies
if (referrer && referrer !== window.location.href) {
// If we have a valid referrer, go there
window.location.href = referrer;
} else if (sourceUrl && sourceUrl !== window.location.href) {
// If we have source URL from plugin, use it
window.location.href = sourceUrl;
} else if (window.history.length > 1) {
// Use browser history if available
window.history.back();
} else {
// Fallback to homepage
window.location.href = <?php echo json_encode(home_url()); ?>;
}
}
if (backButton) {
backButton.addEventListener('click', function(e) {
e.preventDefault();
goBack();
});
}
if (noRedirectCheckbox) {
noRedirectCheckbox.addEventListener('change', function() {
var cookieExpiry = new Date();
cookieExpiry.setDate(cookieExpiry.getDate() + <?php echo esc_js($cookie_duration); ?>);
document.cookie = "wpnav_noredirect=" + (this.checked ? "1" : "0") +
"; expires=" + cookieExpiry.toUTCString() +
"; path=/; SameSite=Strict";
});
}
copyButtons.forEach(function(btn) {
btn.addEventListener('click', function() {
var url = this.getAttribute('data-copy');
if (navigator.clipboard) {
navigator.clipboard.writeText(url).then(function() {
showToast(<?php echo json_encode(__('Link copied to clipboard', 'wpnav-links')); ?>);
});
} else {
var input = document.createElement('input');
input.value = url;
document.body.appendChild(input);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
showToast(<?php echo json_encode(__('Link copied to clipboard', 'wpnav-links')); ?>);
}
});
});
function showToast(message) {
var toast = document.createElement('div');
toast.className = 'wpnav-toast';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(function() {
toast.classList.add('show');
}, 100);
setTimeout(function() {
toast.classList.remove('show');
setTimeout(function() {
if (document.body.contains(toast)) {
document.body.removeChild(toast);
}
}, 300);
}, 2000);
}
if (continueButton) {
continueButton.addEventListener('click', function() {
window.location.href = targetUrl;
});
continueButton.addEventListener('touchstart', function() {
this.classList.add('touched');
});
}
if (autoRedirectEnabled && countdown > 0 && countdownElement && targetUrl) {
var countdownTimer = setInterval(function() {
countdown--;
if (countdownElement) {
var displayText = countdownTemplate.replace('{seconds}', countdown);
countdownElement.textContent = displayText;
}
if (progressElement) {
var progress = (initialCountdown - countdown) / initialCountdown;
progressElement.style.width = (100 - (progress * 100)) + '%';
}
if (countdown <= 0) {
clearInterval(countdownTimer);
if (continueButton) {
continueButton.click();
}
}
}, 1000);
}
// Enhanced keyboard navigation
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
e.preventDefault();
goBack();
} else if (e.key === 'Enter' && continueButton) {
e.preventDefault();
continueButton.click();
} else if (e.key === 'Backspace' && backButton) {
e.preventDefault();
goBack();
}
});
// Enhanced touch gestures
var startY = 0;
var currentY = 0;
var isDragging = false;
document.addEventListener('touchstart', function(e) {
startY = e.touches[0].clientY;
isDragging = true;
});
document.addEventListener('touchmove', function(e) {
if (!isDragging) return;
currentY = e.touches[0].clientY;
var diff = currentY - startY;
if (Math.abs(diff) > 100) {
if (diff > 0 && backButton) {
goBack();
} else if (diff < 0 && continueButton) {
continueButton.click();
}
isDragging = false;
}
});
document.addEventListener('touchend', function() {
isDragging = false;
});
// Focus management for accessibility
if (continueButton) {
continueButton.focus();
}
})();
</script>
<?php wp_footer(); ?>
</body>
</html>

343
redirect.js Normal file
View file

@ -0,0 +1,343 @@
(function($) {
'use strict';
var wpnavRedirect = {
init: function() {
this.processExistingLinks();
this.setupMutationObserver();
this.setupEventHandlers();
this.optimizeForMobile();
},
processExistingLinks: function() {
var self = this;
$('a:not([data-wpnav-processed])').each(function() {
self.processLink($(this));
});
},
processLink: function($link) {
var href = $link.attr('href');
if (!href || this.shouldSkipLink(href, $link)) {
$link.attr('data-wpnav-processed', '1');
return;
}
if (this.isExternalLink(href) && !this.isWhitelistedDomain(href)) {
this.processExternalLink($link, href);
}
$link.attr('data-wpnav-processed', '1');
},
shouldSkipLink: function(href, $link) {
if (href.startsWith('#') || href.startsWith('javascript:') || href.startsWith('mailto:') || href.startsWith('tel:')) {
return true;
}
if (wpnav_params.exclude_class && $link.hasClass(wpnav_params.exclude_class)) {
return true;
}
if ($link.attr('data-wpnav-external') || $link.attr('data-wpnav-processed')) {
return true;
}
return false;
},
isExternalLink: function(url) {
if (!url.match(/^(https?:)?\/\//i)) {
return false;
}
try {
var parser = document.createElement('a');
parser.href = url;
var linkDomain = parser.hostname.toLowerCase();
var siteDomain = wpnav_params.site_domain.toLowerCase();
return linkDomain !== siteDomain;
} catch (e) {
return false;
}
},
isWhitelistedDomain: function(url) {
if (!wpnav_params.whitelist_domains || wpnav_params.whitelist_domains.length === 0) {
return false;
}
try {
var parser = document.createElement('a');
parser.href = url;
var hostname = parser.hostname.toLowerCase();
for (var i = 0; i < wpnav_params.whitelist_domains.length; i++) {
var whitelistDomain = wpnav_params.whitelist_domains[i].toLowerCase();
if (hostname === whitelistDomain) {
return true;
}
if (whitelistDomain.indexOf('*') !== -1) {
var pattern = whitelistDomain.replace(/\*/g, '.*');
var regex = new RegExp('^' + pattern + '$', 'i');
if (regex.test(hostname)) {
return true;
}
}
}
} catch (e) {
return false;
}
return false;
},
processExternalLink: function($link, href) {
var rel = $link.attr('rel') || '';
var relValues = rel ? rel.split(' ') : [];
['nofollow', 'noopener', 'noreferrer'].forEach(function(value) {
if (relValues.indexOf(value) === -1) {
relValues.push(value);
}
});
$link.attr('rel', relValues.join(' '));
if (wpnav_params.open_new_tab) {
$link.attr('target', '_blank');
}
var redirectUrl = this.getRedirectUrl(href);
$link.attr('href', redirectUrl);
$link.attr('data-wpnav-external', '1');
this.addExternalIndicator($link);
},
addExternalIndicator: function($link) {
if (!$link.find('.wpnav-external-icon').length) {
var iconHtml = '<span class="wpnav-external-icon" style="font-size: 0.8em; margin-left: 3px; opacity: 0.6; vertical-align: super; color: #646970; transition: opacity 0.2s ease;">↗</span>';
$link.append(iconHtml);
}
},
getRedirectUrl: function(url) {
var currentPage = encodeURIComponent(window.location.href);
var baseParams = 'wpnav_redirect=1&ref=' + currentPage;
switch(wpnav_params.redirect_method) {
case 'path':
var encodedUrl = this.encodeUrl(url);
if (wpnav_params.permalink_structure) {
return wpnav_params.home_url + '/go/' + encodedUrl;
} else {
return wpnav_params.home_url + '?' + baseParams + '&url_param=' + encodeURIComponent(encodedUrl);
}
case 'target':
return wpnav_params.home_url + '?' + baseParams + '&target=' + encodeURIComponent(url);
default:
var encodedUrl = this.encodeUrl(url);
return wpnav_params.home_url + '?' + baseParams + '&url=' + encodeURIComponent(encodedUrl);
}
},
encodeUrl: function(url) {
if (wpnav_params.redirect_method === 'target') {
return url;
}
switch(wpnav_params.url_encoding) {
case 'base64':
try {
return btoa(encodeURIComponent(url)).replace(/[+/=]/g, function(match) {
return {'+': '-', '/': '_', '=': ''}[match];
});
} catch (e) {
return encodeURIComponent(url);
}
case 'urlencode':
return encodeURIComponent(url);
case 'none':
return url;
default:
try {
return btoa(encodeURIComponent(url)).replace(/[+/=]/g, function(match) {
return {'+': '-', '/': '_', '=': ''}[match];
});
} catch (e) {
return encodeURIComponent(url);
}
}
},
setupMutationObserver: function() {
if (!window.MutationObserver) {
return;
}
var self = this;
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach(function(node) {
if (node.nodeType === 1) {
if (node.nodeName === 'A' && !node.hasAttribute('data-wpnav-processed')) {
self.processLink($(node));
}
var $links = $(node).find('a:not([data-wpnav-processed])');
$links.each(function() {
self.processLink($(this));
});
}
});
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
},
setupEventHandlers: function() {
var self = this;
$(document).ajaxComplete(function() {
setTimeout(function() {
self.processExistingLinks();
}, 100);
});
$(document).on('click', 'a[data-wpnav-external]', function(e) {
var $link = $(this);
var href = $link.attr('href');
if (typeof gtag !== 'undefined') {
gtag('event', 'click', {
event_category: 'external_link',
event_label: href,
transport_type: 'beacon'
});
}
$(document).trigger('wpnav.external_link_click', {
link: $link,
url: href,
target: $link.attr('target')
});
});
$(document).keydown(function(e) {
if (e.keyCode === 27 && window.parent !== window) {
if (typeof window.parent.postMessage === 'function') {
window.parent.postMessage('wpnav_close', '*');
}
}
});
$(document).on('mouseenter', 'a[data-wpnav-external]', function() {
$(this).find('.wpnav-external-icon').css('opacity', '1');
});
$(document).on('mouseleave', 'a[data-wpnav-external]', function() {
$(this).find('.wpnav-external-icon').css('opacity', '0.6');
});
},
optimizeForMobile: function() {
if (!('ontouchstart' in window)) {
return;
}
$(document).on('touchstart', 'a[data-wpnav-external]', function(e) {
var $this = $(this);
$this.addClass('wpnav-touch-active');
setTimeout(function() {
$this.removeClass('wpnav-touch-active');
}, 150);
});
var style = document.createElement('style');
style.textContent = `
a[data-wpnav-external].wpnav-touch-active {
opacity: 0.7;
transform: scale(0.98);
transition: all 0.15s ease;
}
@media (hover: none) and (pointer: coarse) {
a[data-wpnav-external]:hover {
opacity: 1;
transform: none;
}
.wpnav-external-icon {
opacity: 0.8 !important;
}
}
`;
document.head.appendChild(style);
},
refresh: function() {
this.processExistingLinks();
}
};
$(document).ready(function() {
wpnavRedirect.init();
});
window.wpnavRedirect = wpnavRedirect;
if (typeof addComment !== 'undefined') {
var originalAddComment = addComment.moveForm;
addComment.moveForm = function() {
var result = originalAddComment.apply(this, arguments);
setTimeout(function() {
wpnavRedirect.processExistingLinks();
}, 100);
return result;
};
}
if (typeof wp !== 'undefined' && wp.data) {
wp.data.subscribe(function() {
setTimeout(function() {
wpnavRedirect.processExistingLinks();
}, 500);
});
}
$(document).on('wpcf7mailsent wpcf7invalid wpcf7spam wpcf7mailfailed', function() {
setTimeout(function() {
wpnavRedirect.processExistingLinks();
}, 100);
});
$(document.body).on('updated_wc_div updated_cart_totals', function() {
setTimeout(function() {
wpnavRedirect.processExistingLinks();
}, 100);
});
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
setTimeout(function() {
wpnavRedirect.processExistingLinks();
}, 1000);
});
}
})(jQuery);

533
wpnav-links.php Normal file
View file

@ -0,0 +1,533 @@
<?php
/**
* Plugin Name: WPNav Links
* Plugin URI: https://wpnav.com/plugins/wpnav-links
* Description: A simplified WordPress external link redirect tool with customizable redirect pages and basic security features.
* Version: 1.2.0
* Author: WPNav
* Author URI: https://wpnaw.com
* Text Domain: wpnav-links
* Domain Path: /languages
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Requires at least: 5.0
* Tested up to: 6.4
* Requires PHP: 7.4
*/
if (!defined('ABSPATH')) {
exit;
}
define('WPNAV_LINKS_VERSION', '1.2.0');
define('WPNAV_LINKS_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WPNAV_LINKS_PLUGIN_URL', plugin_dir_url(__FILE__));
define('WPNAV_LINKS_DB_VERSION', '1.2.0');
define('WPNAV_LINKS_TABLE', 'nav_link_redirects');
require_once WPNAV_LINKS_PLUGIN_DIR . 'class-wpnav-links.php';
require_once WPNAV_LINKS_PLUGIN_DIR . 'class-admin.php';
require_once WPNAV_LINKS_PLUGIN_DIR . 'class-public.php';
function wpnav_activate_plugin() {
$plugin = new WPNAV_Links();
$plugin->create_tables();
$default_options = array(
'enabled' => true,
'auto_redirect_enabled' => true,
'redirect_delay' => 5,
'open_in_new_tab' => true,
'url_format' => 'query',
'auto_whitelist' => array(
'same_root' => true,
'search_engines' => true
),
'whitelist_domains' => "google.com\nbaidu.com\nbing.com\nyoutube.com\nfacebook.com\ntwitter.com",
'intercept_content' => true,
'intercept_comments' => true,
'intercept_widgets' => true,
'exclude_css_class' => 'no-redirect',
'template' => 'default',
'color_scheme' => 'blue',
'page_title' => 'External Link Warning',
'page_subtitle' => '',
'url_label' => 'You are about to visit:',
'warning_text' => 'You are about to leave this site and visit an external website. We are not responsible for the content of external websites.',
'show_warning_message' => true,
'show_logo' => false,
'show_url_full' => false,
'show_security_info' => true,
'show_security_tips' => false,
'show_back_button' => true,
'custom_css' => '',
'button_text_continue' => 'Continue',
'button_text_back' => 'Back',
'button_style' => 'rounded',
'countdown_text' => 'Auto redirect in {seconds} seconds',
'show_progress_bar' => false,
'admin_exempt' => true,
'cookie_duration' => 30,
'stats_retention' => 90
);
$existing_options = get_option('wpnav_links_options', array());
$merged_options = array_merge($default_options, $existing_options);
update_option('wpnav_links_options', $merged_options);
if (!wp_next_scheduled('wpnav_cleanup_old_data')) {
wp_schedule_event(time(), 'daily', 'wpnav_cleanup_old_data');
}
flush_rewrite_rules();
add_option('wpnav_links_activation_redirect', true);
}
register_activation_hook(__FILE__, 'wpnav_activate_plugin');
function wpnav_deactivate_plugin() {
wp_clear_scheduled_hook('wpnav_cleanup_old_data');
flush_rewrite_rules();
}
register_deactivation_hook(__FILE__, 'wpnav_deactivate_plugin');
function wpnav_uninstall_plugin() {
delete_option('wpnav_links_options');
delete_option('wpnav_links_db_version');
delete_option('wpnav_links_activation_redirect');
global $wpdb;
$table_name = $wpdb->prefix . WPNAV_LINKS_TABLE;
$wpdb->query("DROP TABLE IF EXISTS $table_name");
wp_clear_scheduled_hook('wpnav_cleanup_old_data');
}
register_uninstall_hook(__FILE__, 'wpnav_uninstall_plugin');
add_action('wpnav_cleanup_old_data', 'wpnav_clean_old_statistics');
function wpnav_clean_old_statistics() {
$options = get_option('wpnav_links_options');
$retention_days = isset($options['stats_retention']) ? intval($options['stats_retention']) : 90;
$plugin = new WPNAV_Links();
$deleted_count = $plugin->cleanup_old_data($retention_days);
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log("WPNav Links: Cleaned up {$deleted_count} old records");
}
}
function wpnav_run_plugin() {
$plugin = new WPNAV_Links();
$plugin->init();
}
add_action('plugins_loaded', 'wpnav_run_plugin');
add_action('admin_init', 'wpnav_activation_redirect');
function wpnav_activation_redirect() {
if (get_option('wpnav_links_activation_redirect', false)) {
delete_option('wpnav_links_activation_redirect');
if (!isset($_GET['activate-multi'])) {
wp_redirect(admin_url('tools.php?page=wpnav-links&tab=basic_settings'));
exit;
}
}
}
add_action('wp_dashboard_setup', 'wpnav_add_dashboard_widget');
function wpnav_add_dashboard_widget() {
if (current_user_can('manage_options')) {
wp_add_dashboard_widget(
'wpnav_links_stats',
__('External Links Monitor', 'wpnav-links'),
'wpnav_dashboard_widget_content',
null,
null,
'normal',
'high'
);
}
}
function wpnav_dashboard_widget_content() {
$plugin = new WPNAV_Links();
$options = get_option('wpnav_links_options', array());
$total_redirects = $plugin->get_total_count();
$today_redirects = $plugin->get_total_count(date('Y-m-d'), date('Y-m-d'));
$recent_redirects = $plugin->get_total_count(date('Y-m-d', strtotime('-7 days')), date('Y-m-d'));
$last_week_redirects = $plugin->get_total_count(date('Y-m-d', strtotime('-14 days')), date('Y-m-d', strtotime('-7 days')));
$top_urls = $plugin->get_top_urls(3);
$trend = 0;
if ($last_week_redirects > 0) {
$trend = (($recent_redirects - $last_week_redirects) / $last_week_redirects) * 100;
} elseif ($recent_redirects > 0) {
$trend = 100;
}
$plugin_enabled = !empty($options['enabled']);
$auto_redirect = !empty($options['auto_redirect_enabled']);
$whitelist_count = 0;
if (!empty($options['whitelist_domains'])) {
$domains = explode("\n", $options['whitelist_domains']);
$whitelist_count = count(array_filter(array_map('trim', $domains)));
}
echo '<div class="wpnav-dashboard-widget">';
echo '<div class="wpnav-status-bar">';
echo '<span class="wpnav-status-item ' . ($plugin_enabled ? 'active' : 'inactive') . '">';
echo '<span class="wpnav-status-dot"></span>';
echo ($plugin_enabled ? esc_html__('Active', 'wpnav-links') : esc_html__('Inactive', 'wpnav-links'));
echo '</span>';
if ($plugin_enabled && !$auto_redirect) {
echo '<span class="wpnav-warning-badge">' . esc_html__('Manual Only', 'wpnav-links') . '</span>';
}
echo '</div>';
echo '<div class="wpnav-stats-grid">';
echo '<div class="wpnav-stat-card primary">';
echo '<div class="wpnav-stat-number">' . number_format($total_redirects) . '</div>';
echo '<div class="wpnav-stat-label">' . esc_html__('Total Redirects', 'wpnav-links') . '</div>';
echo '</div>';
echo '<div class="wpnav-stat-card">';
echo '<div class="wpnav-stat-number">' . number_format($today_redirects) . '</div>';
echo '<div class="wpnav-stat-label">' . esc_html__('Today', 'wpnav-links') . '</div>';
echo '</div>';
echo '<div class="wpnav-stat-card">';
echo '<div class="wpnav-stat-number">' . number_format($recent_redirects);
if ($trend != 0) {
$trend_class = $trend > 0 ? 'up' : 'down';
$trend_icon = $trend > 0 ? '↗' : '↘';
echo '<span class="wpnav-trend ' . $trend_class . '">' . $trend_icon . abs(round($trend)) . '%</span>';
}
echo '</div>';
echo '<div class="wpnav-stat-label">' . esc_html__('Last 7 Days', 'wpnav-links') . '</div>';
echo '</div>';
echo '<div class="wpnav-stat-card">';
echo '<div class="wpnav-stat-number">' . number_format($whitelist_count) . '</div>';
echo '<div class="wpnav-stat-label">' . esc_html__('Whitelisted', 'wpnav-links') . '</div>';
echo '</div>';
echo '</div>';
if (!empty($top_urls)) {
echo '<div class="wpnav-top-domains">';
echo '<h4>' . esc_html__('Top Domains This Week', 'wpnav-links') . '</h4>';
echo '<ul class="wpnav-domain-list">';
foreach ($top_urls as $index => $url) {
$domain = parse_url($url->target_url, PHP_URL_HOST);
$is_https = strpos($url->target_url, 'https://') === 0;
echo '<li class="wpnav-domain-item">';
echo '<span class="wpnav-rank">' . ($index + 1) . '</span>';
echo '<span class="wpnav-domain">' . esc_html($domain) . '</span>';
echo '<span class="wpnav-https-indicator ' . ($is_https ? 'secure' : 'insecure') . '"></span>';
echo '<span class="wpnav-count">' . number_format($url->count) . '</span>';
echo '</li>';
}
echo '</ul>';
echo '</div>';
} else {
echo '<div class="wpnav-no-data">';
echo '<p>' . esc_html__('No external link activity yet.', 'wpnav-links') . '</p>';
if (!$plugin_enabled) {
echo '<p><small>' . esc_html__('Enable the plugin to start tracking external links.', 'wpnav-links') . '</small></p>';
}
echo '</div>';
}
echo '<div class="wpnav-quick-actions">';
echo '<div class="wpnav-action-buttons">';
if (!$plugin_enabled) {
echo '<a href="' . admin_url('tools.php?page=wpnav-links&tab=basic_settings') . '" class="button button-primary button-small">' . esc_html__('Enable Plugin', 'wpnav-links') . '</a>';
} else {
echo '<a href="' . admin_url('tools.php?page=wpnav-links&tab=logs_statistics') . '" class="button button-small">' . esc_html__('View Details', 'wpnav-links') . '</a>';
}
echo '<a href="' . admin_url('tools.php?page=wpnav-links&tab=whitelist') . '" class="button button-small">' . esc_html__('Manage Whitelist', 'wpnav-links') . '</a>';
echo '</div>';
echo '</div>';
echo '</div>';
echo '<style>
.wpnav-dashboard-widget {
font-size: 13px;
}
.wpnav-status-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #ddd;
}
.wpnav-status-item {
display: flex;
align-items: center;
gap: 6px;
font-weight: 600;
font-size: 12px;
}
.wpnav-status-item.active {
color: #00a32a;
}
.wpnav-status-item.inactive {
color: #d63638;
}
.wpnav-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
flex-shrink: 0;
}
.wpnav-warning-badge {
background: #f0b849;
color: white;
padding: 2px 6px;
border-radius: 10px;
font-size: 10px;
font-weight: 600;
}
.wpnav-stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 15px;
}
.wpnav-stat-card {
background: #f8f9fa;
border: 1px solid #e2e4e7;
border-radius: 4px;
padding: 12px 10px;
text-align: center;
position: relative;
}
.wpnav-stat-card.primary {
background: #e7f3ff;
border-color: #72aee6;
}
.wpnav-stat-number {
font-size: 18px;
font-weight: 700;
color: #2c3338;
line-height: 1.2;
margin-bottom: 4px;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
}
.wpnav-stat-label {
font-size: 11px;
color: #646970;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.wpnav-trend {
font-size: 10px;
padding: 1px 3px;
border-radius: 2px;
font-weight: 600;
}
.wpnav-trend.up {
background: #d1e7dd;
color: #0a3622;
}
.wpnav-trend.down {
background: #f8d7da;
color: #58151c;
}
.wpnav-top-domains h4 {
margin: 0 0 10px;
font-size: 13px;
color: #2c3338;
font-weight: 600;
}
.wpnav-domain-list {
margin: 0;
padding: 0;
list-style: none;
}
.wpnav-domain-item {
display: flex;
align-items: center;
padding: 6px 0;
border-bottom: 1px solid #f0f0f1;
gap: 8px;
}
.wpnav-domain-item:last-child {
border-bottom: none;
}
.wpnav-rank {
background: #2271b1;
color: white;
width: 16px;
height: 16px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 700;
flex-shrink: 0;
}
.wpnav-domain {
flex: 1;
font-size: 12px;
color: #2c3338;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.wpnav-https-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.wpnav-https-indicator.secure {
background: #00a32a;
}
.wpnav-https-indicator.insecure {
background: #d63638;
}
.wpnav-count {
font-size: 11px;
color: #646970;
font-weight: 600;
flex-shrink: 0;
}
.wpnav-no-data {
text-align: center;
padding: 20px 0;
color: #646970;
}
.wpnav-no-data p {
margin: 5px 0;
}
.wpnav-quick-actions {
margin-top: 15px;
padding-top: 10px;
border-top: 1px solid #ddd;
}
.wpnav-action-buttons {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.wpnav-action-buttons .button {
font-size: 11px;
padding: 4px 8px;
height: auto;
line-height: 1.4;
text-decoration: none;
}
@media screen and (max-width: 782px) {
.wpnav-stats-grid {
grid-template-columns: 1fr;
}
.wpnav-action-buttons {
flex-direction: column;
}
.wpnav-action-buttons .button {
text-align: center;
}
}
</style>';
}
add_filter('plugin_row_meta', 'wpnav_plugin_row_meta', 10, 2);
function wpnav_plugin_row_meta($links, $file) {
if (plugin_basename(__FILE__) === $file) {
$row_meta = array(
'settings' => '<a href="' . admin_url('tools.php?page=wpnav-links') . '">' . esc_html__('Settings', 'wpnav-links') . '</a>',
'support' => '<a href="https://example.com/support" target="_blank">' . esc_html__('Support', 'wpnav-links') . '</a>',
'docs' => '<a href="https://example.com/docs" target="_blank">' . esc_html__('Documentation', 'wpnav-links') . '</a>',
);
return array_merge($links, $row_meta);
}
return $links;
}
add_action('init', 'wpnav_check_database_version');
function wpnav_check_database_version() {
$installed_version = get_option('wpnav_links_db_version');
if ($installed_version !== WPNAV_LINKS_DB_VERSION) {
$plugin = new WPNAV_Links();
$plugin->create_tables();
update_option('wpnav_links_db_version', WPNAV_LINKS_DB_VERSION);
}
}
add_action('wp_enqueue_scripts', 'wpnav_enqueue_frontend_styles');
function wpnav_enqueue_frontend_styles() {
$options = get_option('wpnav_links_options');
if (!isset($options['enabled']) || !$options['enabled']) {
return;
}
if (!is_admin()) {
wp_enqueue_style(
'wpnav-external-indicator',
WPNAV_LINKS_PLUGIN_URL . 'external-indicator.css',
array(),
WPNAV_LINKS_VERSION
);
}
}
if (!wp_next_scheduled('wpnav_daily_maintenance')) {
wp_schedule_event(time(), 'daily', 'wpnav_daily_maintenance');
}
add_action('wpnav_daily_maintenance', 'wpnav_daily_maintenance_tasks');
function wpnav_daily_maintenance_tasks() {
wp_cache_flush();
global $wpdb;
$table_name = $wpdb->prefix . WPNAV_LINKS_TABLE;
$wpdb->query("OPTIMIZE TABLE $table_name");
}