<?php
/**
* Class EPS_Redirects_Plugin
*
* Inits the EPS_Redirects Plugin's core functionality and admin management.
*
*
*/
// include only file
if (!defined('ABSPATH')) {
die('Do not open this file directly.');
}
class EPS_Redirects_Plugin
{
protected $config = array(
'version' => EPS_REDIRECT_VERSION,
'option_slug' => 'eps_redirects',
'page_slug' => 'eps_redirects',
'page_title' => '301 Redirects',
'menu_location' => 'options',
'page_permission' => 'manage_options',
'directory' => 'eps-301-redirects'
);
protected $dependancies = array();
protected $tables = array();
public $name = '301 Redirects';
public $settings;
protected $resources = array(
'css' => array(
'admin.css'
),
'js' => array(
'admin.js'
)
);
protected $options;
protected $messages = array();
public $filesystem_initialized = false;
public function __construct()
{
$this->config['url'] = plugins_url() . $this->config['directory'] . '/';
$this->config['path'] = EPS_REDIRECT_PATH . $this->config['directory'] . '/';
if (class_exists('EPS_Redirects_Plugin_Options'))
$this->settings = new EPS_Redirects_Plugin_Options($this);
register_activation_hook(__FILE__, array($this, '_activation'));
register_deactivation_hook(__FILE__, array($this, '_deactivation'));
if (!self::is_current_version()) self::update_self();
add_action('init', array($this, 'plugin_resources'));
// Template Hooks
add_action('redirects_admin_tab', array($this, 'admin_tab_redirects'), 10, 1);
add_action('404s_admin_tab', array($this, 'admin_tab_404s'), 10, 1);
add_action('support_admin_tab', array($this, 'admin_tab_support'), 10, 1);
add_action('link-scanner_admin_tab', array($this, 'admin_tab_link_scanner'), 10, 1);
add_action('error_admin_tab', array($this, 'admin_tab_error'), 10, 1);
add_action('import-export_admin_tab', array($this, 'admin_tab_import_export'), 10, 1);
add_action('eps_redirects_panels_left', array($this, 'admin_panel_cache'));
add_action('admin_notices', array($this, 'show_review_notice'));
add_action('admin_action_301_dismiss_notice', array($this, 'dismiss_notice'));
// Actions
add_action('admin_init', array($this, 'check_plugin_actions'));
}
private function resource_path($path, $resource)
{
return strtolower(
$this->config['url']
. $path . '/'
. $resource
);
}
private function resource_name($resource)
{
return strtolower($this->name . '_' . key($resource));
}
public static function _activation()
{
self::_create_redirect_table(); // Maybe create the tables
if (!self::is_current_version()) {
self::update_self();
}
self::reset_pointers();
delete_option('301-redirects-notices');
}
public static function _deactivation()
{ }
// handle dismiss button for notices
static function dismiss_notice()
{
check_admin_referer( '301_dismiss_notice' );
if (empty($_GET['notice'])) {
wp_safe_redirect(admin_url());
exit;
}
$notices = get_option('301-redirects-notices', array());
if (sanitize_text_field(wp_unslash($_GET['notice'])) == 'rate') {
$notices['dismiss_rate'] = true;
} else {
wp_safe_redirect(admin_url());
exit;
}
update_option('301-redirects-notices', $notices);
if (!empty($_GET['redirect'])) {
wp_safe_redirect(wp_unslash($_GET['redirect']));
} else {
wp_safe_redirect(admin_url());
}
exit;
} // dismiss_notice
function show_review_notice()
{
global $wpdb;
$table_name = $wpdb->prefix . "redirects";
if (empty($_GET['page']) || sanitize_text_field(wp_unslash($_GET['page'])) != 'eps_redirects') { //phpcs:ignore
return;
}
$notices = get_option('301-redirects-notices', array());
if (!empty($notices['dismiss_rate'])) {
return false;
}
//phpcs:ignore custom tables
$tmp1 = $wpdb->get_var("SELECT SUM(count) FROM $table_name"); //phpcs:ignore
$tmp2 = $wpdb->get_var("SELECT COUNT(id) FROM $table_name"); //phpcs:ignore
if ($tmp1 < 4 || $tmp2 < 2) {
return;
}
$rate_url = 'https://wordpress.org/support/plugin/eps-301-redirects/reviews/?filter=5&rate=5#new-post';
if(!empty($_SERVER['REQUEST_URI'])){
$redirect = sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI']));
} else {
$redirect = '';
}
$dismiss_url = add_query_arg(array('action' => '301_dismiss_notice', 'notice' => 'rate', 'redirect' => urlencode($redirect)), admin_url('admin.php'));
$dismiss_url = wp_nonce_url($dismiss_url, '301_dismiss_notice');
echo '<div id="301_rate_notice" style="font-size: 14px;" class="notice-info notice"><p>Hi!<br>Saw that you already have ' . esc_attr($tmp2) . ' redirect rules that got used ' . esc_attr($tmp1) . ' times - that\'s awesome! We wanted to ask for your help to <b>make the plugin better</b>.<br>We just need a minute of your time to rate the plugin. It helps us out a lot!';
echo '<br><a target="_blank" href="' . esc_url($rate_url) . '" style="vertical-align: baseline; margin-top: 15px;" class="button-primary">Help make the plugin better by rating it</a>';
echo ' <a href="' . esc_url($dismiss_url) . '">I\'ve already rated the plugin</a>';
echo '<br><br><b>Thank you very much!</b> The 301 Redirects team';
echo '</p></div>';
} // show_review_notice
public function admin_url($vars = array())
{
$vars = array('page' => $this->config['page_slug']) + $vars;
$url = 'options-general.php?' . http_build_query($vars);
return admin_url($url);
}
/**
*
* update_self
*
* This function will check the current version and do any fixes required
*
* @return string - version number.
* @author WebFactory Ltd
*
*/
public function update_self()
{
$version = get_option('eps_redirects_version');
if (version_compare($version, '2.0.0', '<')) {
// migrate old format to new format.
add_action('admin_init', array($this, '_migrate_to_v2'), 1);
}
$this->set_current_version(EPS_REDIRECT_VERSION);
return EPS_REDIRECT_VERSION;
}
/**
*
* _migrate_to_v2
*
* Will migrate the old storage method to the new tables.
*
* @return nothing
* @author WebFactory Ltd
*
*/
public static function _migrate_to_v2()
{
$redirects = get_option(self::$option_slug);
if (empty($redirects)) return false; // No redirects to migrate.
$new_redirects = array();
foreach ($redirects as $from => $to) {
$new_redirects[] = array(
'id' => false,
'url_to' => urldecode($to),
'url_from' => $from,
'type' => 'url',
'status' => '301'
);
}
EPS_Redirects::_save_redirects($new_redirects);
}
/**
*
* _create_tables
*
* Creates the database architecture
*
* @return nothing
* @author WebFactory Ltd
*
*/
public static function _create_redirect_table()
{
global $wpdb;
$table_name = $wpdb->prefix . "redirects";
$sql = "CREATE TABLE $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
url_from VARCHAR(256) DEFAULT '' NOT NULL,
url_to VARCHAR(256) DEFAULT '' NOT NULL,
status VARCHAR(12) DEFAULT '301' NOT NULL,
type VARCHAR(12) DEFAULT 'url' NOT NULL,
count mediumint(9) DEFAULT 0 NOT NULL,
UNIQUE KEY id (id)
);";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
return dbDelta($sql);
}
/**
*
* plugin_resources
*
* Enqueues the resources, and makes sure we have what we need to proceed.
*
* @return nothing
* @author WebFactory Ltd
*
*/
public static function plugin_resources()
{
global $EPS_Redirects_Plugin, $wp_rewrite;
$pointers = get_option('eps_pointers');
//phpcs:ignore can't nonce as page can be opened directly
if (is_admin() && $pointers && !empty($wp_rewrite->permalink_structure) && (empty($_GET['page']) || sanitize_text_field(wp_unslash($_GET['page'])) != $EPS_Redirects_Plugin->config('page_slug'))) { //phpcs:ignore
$pointers['_nonce_dismiss_pointer'] = wp_create_nonce('eps_dismiss_pointer');
wp_enqueue_script('wp-pointer');
wp_enqueue_script('eps-pointers', plugins_url('js/eps-admin-pointers.js', __FILE__), array('jquery'), EPS_REDIRECT_VERSION, true);
wp_enqueue_style('wp-pointer');
wp_localize_script('wp-pointer', 'eps_pointers', $pointers);
}
if (is_admin() && isset($_GET['page']) && sanitize_text_field(wp_unslash($_GET['page'])) == $EPS_Redirects_Plugin->config('page_slug')) { //phpcs:ignore
unset($pointers['welcome']);
$notices = get_option('301-redirects-notices', array());
wp_enqueue_script('jquery');
wp_enqueue_script('eps_redirect_script', EPS_REDIRECT_URL . 'js/scripts.js', array(), self::current_version(), false);
wp_enqueue_style('eps_redirect_styles', EPS_REDIRECT_URL . 'css/eps_redirect.css', array(), self::current_version());
wp_enqueue_style('wp-jquery-ui-dialog');
wp_enqueue_script('jquery-ui-dialog');
$js_vars = array(
'nonce_get_entry' => wp_create_nonce('eps_301_get_entry'),
'nonce_save_redirect' => wp_create_nonce('eps_301_save_redirect'),
'nonce_delete_entry' => wp_create_nonce('eps_301_delete_entry'),
'nonce_get_inline_edit_entry' => wp_create_nonce('eps_301_get_inline_edit_entry'),
'auto_open_pro_dialog' => empty($notices['dismiss_auto_pro_modal']),
);
wp_localize_script('eps_redirect_script', 'eps_301', $js_vars);
$notices['dismiss_auto_pro_modal'] = true;
update_option('301-redirects-notices', $notices);
}
global $wp_rewrite;
if (!isset($wp_rewrite->permalink_structure) || empty($wp_rewrite->permalink_structure)) {
$EPS_Redirects_Plugin->add_admin_message('<b>WARNING:</b> 301 Redirects plugin requires that a permalink structure is set. The default (plain) WordPress permalink structure is not compatible with 301 Redirects.<br>Please update the <a href="options-permalink.php" title="Permalinks">Permalink Structure</a>.', "error");
}
global $wpdb;
$table_name = $wpdb->prefix . "redirects";
//phpcs:ignore custom table
if ($wpdb->get_var("SHOW TABLES LIKE '" . $wpdb->prefix . "redirects'") != $table_name) { //phpcs:ignore
$url = $EPS_Redirects_Plugin->admin_url(array('action' => 'eps_create_tables'));
$EPS_Redirects_Plugin->add_admin_message('WARNING: It looks like we need to <a href="' . $url . '" title="Permalinks">Create the Database Tables First!</a>', "error");
}
}
// reset all pointers to default state - visible
static function reset_pointers() {
$pointers = array();
$pointers['welcome'] = array('target' => '#menu-settings', 'edge' => 'left', 'align' => 'right', 'content' => 'Thank you for installing the <b style="font-weight: 800;">301 Redirects plugin</b>! Please open <a href="' . admin_url('options-general.php?page=eps_redirects'). '">Settings - 301 Redirects</a> to manage redirect rules and view the 404 error log.');
update_option('eps_pointers', $pointers);
} // reset_pointers
/**
*
* check_plugin_actions
*
* This function handles various POST requests.
*
* @return nothing
* @author WebFactory Ltd
*
*/
public function check_plugin_actions()
{
//phpcs:ignore can't nonce as page can be opened directly
if (is_admin() && isset($_GET['page']) && sanitize_text_field(wp_unslash($_GET['page'])) == $this->config('page_slug')) { //phpcs:ignore
if(!isset($_POST['eps_redirect_nonce_submit']) || false === wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['eps_redirect_nonce_submit'])), 'eps_redirect_nonce')){
return false;
}
// Upload a CSV
if (isset($_POST['eps_redirect_upload'])) {
self::_upload();
}
// Export a CSV
if (isset($_POST['eps_redirect_export'])) {
self::export_csv();
}
// Refresh the Transient Cache
if (isset($_POST['eps_redirect_refresh'])) {
$post_types = get_post_types(array('public' => true), 'objects');
foreach ($post_types as $post_type) {
$options = eps_dropdown_pages(array('post_type' => $post_type->name));
set_transient('post_type_cache_' . $post_type->name, $options, HOUR_IN_SECONDS);
}
self::empty_3rd_party_cache();
$this->add_admin_message("Success: Cache Emptied.", "updated");
}
// delete all rules
if (isset($_POST['eps_delete_rules'])) {
self::delete_all_rules();
$this->add_admin_message("Success: All Redirect Rules Deleted.", "updated");
}
// reset redirect hits
if (isset($_POST['eps_reset_stats'])) {
self::reset_stats();
$this->add_admin_message("Success: Redirect hits have been reset.", "updated");
}
// Save Redirects
if (isset($_POST['eps_redirect_submit']) && isset($_POST['redirect'])) {
$redirect = array_map('sanitize_text_field', wp_unslash($_POST['redirect']));
self::_save_redirects(EPS_Redirects::_parse_serial_array($redirect));
}
// Create tables
if (isset($_GET['action']) && sanitize_text_field(wp_unslash($_GET['action'])) == 'eps_create_tables') {
$result = self::_create_redirect_table();
}
}
}
static function delete_all_rules() {
global $wpdb;
$wpdb->query('TRUNCATE TABLE ' . $wpdb->prefix . 'redirects');
return true;
}
static function reset_stats() {
global $wpdb;
//phpcs:ignore as we're using a custom table
$wpdb->query('UPDATE ' . $wpdb->prefix . 'redirects SET count = 0'); //phpcs:ignore
return true;
}
static function empty_3rd_party_cache() {
wp_cache_flush();
if (function_exists('w3tc_flush_all')) {
w3tc_flush_all();
}
if (function_exists('wp_cache_clear_cache')) {
wp_cache_clear_cache();
}
if (method_exists('LiteSpeed_Cache_API', 'purge_all')) {
LiteSpeed_Cache_API::purge_all();
}
if (class_exists('Endurance_Page_Cache')) {
$epc = new Endurance_Page_Cache;
$epc->purge_all();
}
if (class_exists('SG_CachePress_Supercacher') && method_exists('SG_CachePress_Supercacher', 'purge_cache')) {
SG_CachePress_Supercacher::purge_cache(true);
}
if (class_exists('SiteGround_Optimizer\Supercacher\Supercacher')) {
SiteGround_Optimizer\Supercacher\Supercacher::purge_cache();
}
if (isset($GLOBALS['wp_fastest_cache']) && method_exists($GLOBALS['wp_fastest_cache'], 'deleteCache')) {
$GLOBALS['wp_fastest_cache']->deleteCache(true);
}
if (is_callable(array('Swift_Performance_Cache', 'clear_all_cache'))) {
Swift_Performance_Cache::clear_all_cache();
}
if (is_callable(array('Hummingbird\WP_Hummingbird', 'flush_cache'))) {
Hummingbird\WP_Hummingbird::flush_cache(true, false);
}
if (function_exists('rocket_clean_domain')) {
rocket_clean_domain();
}
do_action('cache_enabler_clear_complete_cache');
} // empty_cache
/**
*
* export_csv
*
* @return nothing
* @author WebFactory Ltd
*
*/
public static function export_csv()
{
$entries = EPS_Redirects::get_all();
$filename = sprintf(
"%s-redirects-export.csv",
gmdate('Y-m-d')
);
if ($entries) {
header('Content-disposition: attachment; filename=' . $filename);
header('Content-type: text/csv');
foreach ($entries as $entry) {
$csv = array(
$entry->status,
$entry->url_from,
$entry->url_to,
$entry->count
);
echo esc_attr(implode(',', $csv));
echo "\n";
}
die();
}
}
/**
*
* _upload
*
* This function handles the upload of CSV files, in accordance to the upload method specified.
*
* @return html string
* @author WebFactory Ltd
*
*/
private function _upload()
{
global $wp_filesystem;
if(!isset($_POST['eps_redirect_nonce_submit']) || false === wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['eps_redirect_nonce_submit'])), 'eps_redirect_nonce')){
return false;
}
$this->wp_init_filesystem();
$new_redirects = array();
$counter = array(
'new' => 0,
'updated' => 0,
'skipped' => 0,
'errors' => 0,
'total' => 0
);
$mimes = array(
'text/csv',
'text/tsv',
'text/plain',
'application/csv',
'text/comma-separated-values',
'application/excel',
'application/vnd.ms-excel',
'application/vnd.msexcel',
'text/anytext',
'application/octet-stream',
'application/txt'
);
if (!isset($_FILES['eps_redirect_upload_file']['tmp_name']) || !isset($_FILES['eps_redirect_upload_file']) || !isset($_FILES['eps_redirect_upload_file']['type']) || !in_array($_FILES['eps_redirect_upload_file']['type'], $mimes)) {
$this->add_admin_message(sprintf(
"WARNING: Not a valid CSV file - the Mime Type '%s' is wrong! No new redirects have been added.",
sanitize_textarea_field($_FILES['eps_redirect_upload_file']['type'])
), "error");
return false;
}
// open the file.
$csv_file_contents = $wp_filesystem->get_contents(sanitize_text_field($_FILES['eps_redirect_upload_file']['tmp_name']));
if ( false !== $csv_file_contents) {
$csv_array = explode(PHP_EOL, $csv_file_contents);
$counter['total'] = 1;
foreach($csv_array as $id => $redirect) {
if (empty($redirect)) continue;
$redirect = array_filter(str_getcsv($redirect));
if (empty($redirect)) continue;
$args = count($redirect);
if ($args > 4 || $args < 2) {
// Bad line. Too many/few arguments.
$this->add_admin_message(
sprintf(
"WARNING: Encountered a badly formed entry in your CSV file on line %d (we skipped it).",
$counter['total']
),
"error"
);
$counter['errors']++;
continue;
}
$status = (isset($redirect[0])) ? esc_attr($redirect[0]) : false;
$url_from = (isset($redirect[1])) ? esc_attr($redirect[1]) : false;
$url_to = (isset($redirect[2])) ? esc_attr($redirect[2]) : false;
$count = (isset($redirect[3])) ? esc_attr($redirect[3]) : false;
switch (strtolower($status)) {
case '404':
$status = 404;
break;
case '302':
$status = 302;
break;
case '307':
$status = 307;
break;
case 'off':
case 'no':
case 'inactive':
$status = 'inactive';
break;
default:
$status = 301;
break;
}
// If the user supplied a post_id, is it valid? If so, use it!
if ($url_to && $post_id = url_to_postid($url_to)) {
$url_to = $post_id;
}
// new redirect!
$new_redirect = array(
'id' => false, // new
'url_from' => $url_from,
'url_to' => $url_to,
'type' => (is_numeric($url_to)) ? 'post' : 'url',
'status' => $status,
'count' => $count
);
array_push($new_redirects, $new_redirect);
$counter['total']++;
}
}
if ($new_redirects) {
$save_redirects = array();
foreach ($new_redirects as $redirect) {
// Decide how to handle duplicates:
$upload_method = 'skip';
if(isset($_POST['eps_redirect_upload_method'])){
$upload_method = strtolower(sanitize_text_field(wp_unslash($_POST['eps_redirect_upload_method'])));
}
switch ($upload_method) {
case 'skip':
if (!EPS_Redirects::redirect_exists($redirect)) {
$save_redirects[] = $redirect;
$counter['new']++;
} else {
$counter['skipped']++;
}
break;
case 'update':
if ($entry = EPS_Redirects::redirect_exists($redirect)) {
$redirect['id'] = $entry->id;
$counter['updated']++;
$save_redirects[] = $redirect;
} else {
$save_redirects[] = $redirect;
$counter['new']++;
}
break;
default:
$save_redirects[] = $redirect;
$counter['new']++;
break;
}
}
if (!empty($save_redirects)) {
EPS_Redirects::_save_redirects($save_redirects);
}
$this->add_admin_message(sprintf(
"Success: %d New Redirects, %d Updated, %d Skipped, %d Errors. (Attempted to import %d redirects).",
$counter['new'],
$counter['updated'],
$counter['skipped'],
$counter['errors'],
$counter['total']
), "updated");
} else {
$this->add_admin_message("WARNING: Something's up. No new redirects were added, please review your CSV file.", "error");
}
}
/**
*
* Template Hooks
*
* @author WebFactory Ltd
*
*/
public static function admin_panel_cache()
{
include(EPS_REDIRECT_PATH . 'templates/admin-panel-cache.php');
}
public static function admin_panel_donate()
{
include(EPS_REDIRECT_PATH . 'templates/admin-panel-donate.php');
}
public static function admin_tab_redirects($options)
{
include(EPS_REDIRECT_PATH . 'templates/admin-tab-redirects.php');
}
public static function admin_tab_404s($options)
{
include(EPS_REDIRECT_PATH . 'templates/admin-tab-404s.php');
}
public static function admin_tab_support($options)
{
include(EPS_REDIRECT_PATH . 'templates/admin-tab-support.php');
}
public static function admin_tab_link_scanner($options)
{
include(EPS_REDIRECT_PATH . 'templates/admin-tab-link-scanner.php');
}
public static function admin_tab_import_export($options)
{
include(EPS_REDIRECT_PATH . 'templates/admin-tab-import-export.php');
}
public static function admin_tab_error($options)
{
include(EPS_REDIRECT_PATH . 'templates/admin-tab-error.php');
}
/**
*
* CHECK VERSION
*
* This function will check the current version and do any fixes required
*
* @return string - version number.
* @author WebFactory Ltd
*
*/
public function config($name)
{
return (isset($this->config[$name])) ? $this->config[$name] : false;
}
public static function is_current_version()
{
return version_compare(self::current_version(), EPS_REDIRECT_VERSION, '=') ? true : false; // TODO decouple
}
public static function current_version()
{
return get_option('eps_redirects_version'); // TODO decouple
}
public static function set_current_version($version)
{
update_option('eps_redirects_version', $version);
}
/**
*
* Notices
*
* These functions will output a variable containing the admin ajax url for use in javascript.
*
* @author WebFactory Ltd
*
*/
protected function add_admin_message($message, $code)
{
$this->messages[] = array($code => $message);
add_action('admin_notices', array($this, 'display_admin_messages'));
}
public static function display_admin_messages()
{
global $EPS_Redirects_Plugin;
if (is_array($EPS_Redirects_Plugin->messages) && !empty($EPS_Redirects_Plugin->messages)) {
foreach ($EPS_Redirects_Plugin->messages as $entry) {
$code = key($entry);
$message = reset($entry);
if (!in_array($code, array('error', 'updated'))) {
$code = 'updated';
}
$EPS_Redirects_Plugin->admin_notice($message, $code);
}
}
}
public function admin_notice($string, $type = "updated")
{
printf(
'<div class="%s"><p>%s</p></div>',
esc_attr($type),
wp_kses_post($string)
);
}
public static function protect_from_translation_plugins()
{
global $original_request_uri;
if(isset($_SERVER['REQUEST_URI'])){
$original_request_uri = strtolower(sanitize_text_field(wp_unslash(urldecode($_SERVER['REQUEST_URI']))));
}
}
/**
* Initializes the WordPress filesystem.
*
* @return bool
*/
function wp_init_filesystem()
{
if (! $this->filesystem_initialized) {
if (! class_exists('WP_Filesystem')) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
WP_Filesystem();
$this->filesystem_initialized = true;
}
return true;
}
/**
* Test if we're on WPR's admin page
*
* @return bool
*/
static function is_plugin_page()
{
if ( !function_exists( 'get_current_screen' ) ) {
require_once ABSPATH . '/wp-admin/includes/screen.php';
}
$current_screen = get_current_screen();
if (!empty($current_screen->id) && $current_screen->id == 'tools_page_wp-reset') {
return true;
} else {
return false;
}
} // is_plugin_page
}
// Init the plugin.
$EPS_Redirects_Plugin = new EPS_Redirects_Plugin();