OwlCyberSecurity - MANAGER
Edit File: PluginLogic.php
<?php /* the glue that holds it together / everything else. */ class ABJ_404_Solution_PluginLogic { private $f = null; private $urlHomeDirectory = null; private $urlHomeDirectoryLength = null; private $options = null; /** Track whether we're already in the method that updates the database that may be called recursively. * @var bool */ private static $instance = null; private static $uniqID = null; /** Use this to avoid an infinite loop when checking if a user has admin access or not. */ private static $checkingIsAdmin = false; /** @return ABJ_404_Solution_PluginLogic The singleton instance of the class. */ public static function getInstance() { if (self::$instance == null) { self::$instance = new ABJ_404_Solution_PluginLogic(); self::$uniqID = uniqid("", true); // these filters allow non-admins to have admin access to the plugin. add_filter( 'user_has_cap', 'ABJ_404_Solution_PluginLogic::override_user_can_access_admin_page', 10, 4 ); } return self::$instance; } function __construct() { $urlPath = parse_url(get_home_url(), PHP_URL_PATH); if ($urlPath == null) { $urlPath = ''; } $this->f = ABJ_404_Solution_Functions::getInstance(); $this->urlHomeDirectory = rtrim($urlPath, '/'); $this->urlHomeDirectoryLength = $this->f->strlen($this->urlHomeDirectory); } /** This replaces the current_user_can('administrator') function. * * Use the following to add a filter. * // ------- * add_filter( 'abj404_userIsPluginAdmin', 'my_custom_function' ); * function my_custom_function( $value ) { * // validate user can access the plugin here. * return $value; * } * // ------- * * @return bool true if $abj404logic->userIsPluginAdmin() */ function userIsPluginAdmin() { // avoid an infinite loop. if (ABJ_404_Solution_PluginLogic::$checkingIsAdmin) { return false; } // begin function. ABJ_404_Solution_PluginLogic::$checkingIsAdmin = true; $abj404logic = ABJ_404_Solution_PluginLogic::getInstance(); $options = $abj404logic->getOptions(); $f = $abj404logic->f; global $current_user; // admins have access $isPluginAdmin = current_user_can('administrator'); // check extra admins. $extraAdmins = $options['plugin_admin_users']; $current_user_name = null; if (isset($current_user)) { $current_user_name = $current_user->user_login; } if ($current_user_name != null && $current_user_name != false) { $check = false; if (is_array($extraAdmins)) { $extraAdmins = array_filter($extraAdmins, array($f, 'removeEmptyCustom')); $check = true; } else if (is_string($extraAdmins)) { $extraAdmins = $f->explodeNewline($extraAdmins); $check = true; } if ($check && in_array($current_user_name, $extraAdmins)) { $isPluginAdmin = true; } } // do the filter in case someone wants to add one $isPluginAdmin = apply_filters('abj404_userIsPluginAdmin', $isPluginAdmin); // allow calling the function again. ABJ_404_Solution_PluginLogic::$checkingIsAdmin = false; return $isPluginAdmin; } /** Allow the user to be an admin for the plugin. * @param $allcaps * @param $caps * @param $args * @param $user * @return array an array of the capabilities */ static function override_user_can_access_admin_page( $allcaps, $caps, $args, $user ) { // if it's not an admin page then we don't change anything. if (!is_admin()) { return $allcaps; } $abj404logic = ABJ_404_Solution_PluginLogic::getInstance(); $isPluginAdmin = false; $isViewing404AdminPage = false; // is the user supposed to have access? if ($abj404logic->userIsPluginAdmin()) { $isPluginAdmin = true; } if ($isPluginAdmin) { $userRequest = ABJ_404_Solution_UserRequest::getInstance(); $queryParts = $userRequest->getQueryString(); // are we viewing a 404 plugin page? if (strpos($queryParts, ABJ404_PP) !== false) { $isViewing404AdminPage = true; } } if ($isPluginAdmin && $isViewing404AdminPage) { $allcaps['manage_options'] = true; } return $allcaps; } /** If a page's URL is /blogName/pageName then this returns /pageName. * @param string $urlRequest * @return string */ function removeHomeDirectory($urlRequest) { $f = $this->f; $urlHomeDirectory = $this->urlHomeDirectory; if ($f->substr($urlRequest, 0, $this->urlHomeDirectoryLength) == $urlHomeDirectory) { $urlRequest = $f->substr($urlRequest, ($this->urlHomeDirectoryLength + 1)); } return $urlRequest; } /** Forward to a real page for queries like ?p=10 * @global type $wp_query * @param array $options */ function tryNormalPostQuery($options) { global $wp_query; $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); // this is for requests like website.com/?p=123 $query = $wp_query->query; // if it's not set then don't use it. if (!array_key_exists('p', $query) || !isset($query['p'])) { return; } $pageid = $query['p']; if (!empty($pageid)) { $permalink = urldecode(get_permalink($pageid)); $status = get_post_status($pageid); if (($permalink != false) && (in_array($status, array('publish', 'published')))) { $homeURL = get_home_url(); if ($homeURL == null) { $homeURL = ''; } $urlHomeDirectory = parse_url($homeURL, PHP_URL_PATH); if ($urlHomeDirectory == null) { $urlHomeDirectory = ''; } $urlHomeDirectory = rtrim($urlHomeDirectory, '/'); $fromURL = $urlHomeDirectory . '/?p=' . $pageid; $redirect = $abj404dao->getExistingRedirectForURL($fromURL); if (!isset($redirect['id']) || $redirect['id'] == 0) { $abj404dao->setupRedirect($fromURL, ABJ404_STATUS_AUTO, ABJ404_TYPE_POST, $pageid, $options['default_redirect'], 0); } $abj404dao->logRedirectHit($fromURL, $permalink, 'page ID'); $this->forceRedirect($permalink, esc_html($options['default_redirect'])); exit; } } } /** * @global type $abj404logging * @global type $abj404logic * @param string $urlRequest the requested URL. e.g. /404killer/aboutt * @param string $urlSlugOnly only the slug. e.g. /aboutt */ function initializeIgnoreValues($urlRequest, $urlSlugOnly) { $abj404logging = ABJ_404_Solution_Logging::getInstance(); $abj404logic = ABJ_404_Solution_PluginLogic::getInstance(); $f = ABJ_404_Solution_Functions::getInstance(); $options = $abj404logic->getOptions(); $ignoreReasonDoNotProcess = null; $ignoreReasonDoProcess = null; $httpUserAgent = array_key_exists('HTTP_USER_AGENT', $_SERVER) ? $f->strtolower($_SERVER['HTTP_USER_AGENT']) : ''; // Note: is_admin() does not mean the user is an admin - it returns true when the user is on an admin screen. // ignore requests that are supposed to be for an admin. $adminURL = parse_url(admin_url(), PHP_URL_PATH); if (is_admin() || $f->substr($urlRequest, 0, $f->strlen($adminURL)) == $adminURL) { $abj404logging->debugMessage("Ignoring admin URL: " . $urlRequest); $ignoreReasonDoNotProcess = 'Admin URL'; } // The user agent Zemanta Aggregator http://www.zemanta.com causes a lot of false positives on // posts that are still drafts and not actually published yet. It's from the plugin "WordPress Related Posts" // by https://www.sovrn.com/. $userAgents = $f->explodeNewline($options['ignore_dontprocess']); foreach ($userAgents as $agentToIgnore) { if (stripos($httpUserAgent, trim($agentToIgnore)) !== false) { $abj404logging->debugMessage("Ignoring user agent (do not redirect): " . esc_html($_SERVER['HTTP_USER_AGENT']) . " for URL: " . esc_html($urlRequest)); $ignoreReasonDoNotProcess = 'User agent (do not redirect): ' . $_SERVER['HTTP_USER_AGENT']; } } // ----- ignore based on regex file path $patternsToIgnore = $options['folders_files_ignore_usable']; if (!empty($patternsToIgnore)) { foreach ($patternsToIgnore as $patternToIgnore) { $patternToIgnoreNoSlashes = stripslashes($patternToIgnore); $_REQUEST[ABJ404_PP]['debug_info'] = 'Applying regex pattern to ignore\"' . $patternToIgnoreNoSlashes . '" to URL slug: ' . $urlSlugOnly; $matches = array(); if ($f->regexMatch($patternToIgnoreNoSlashes, $urlSlugOnly, $matches)) { $abj404logging->debugMessage("Ignoring file/folder (do not redirect) for URL: " . esc_html($urlSlugOnly) . ", pattern used: " . $patternToIgnoreNoSlashes); $ignoreReasonDoNotProcess = 'Files and folders (do not redirect) pattern: ' . esc_html($patternToIgnoreNoSlashes); } $_REQUEST[ABJ404_PP]['debug_info'] = 'Cleared after regex pattern to ignore.'; } } $_REQUEST[ABJ404_PP]['ignore_donotprocess'] = $ignoreReasonDoNotProcess; // ----- // ignore and process $userAgents = $f->explodeNewline($options['ignore_doprocess']); foreach ($userAgents as $agentToIgnore) { if (stripos($httpUserAgent, trim($agentToIgnore)) !== false) { $abj404logging->debugMessage("Ignoring user agent (process ok): " . esc_html($_SERVER['HTTP_USER_AGENT']) . " for URL: " . esc_html($urlRequest)); $ignoreReasonDoProcess = 'User agent (process ok): ' . $agentToIgnore; } } $_REQUEST[ABJ404_PP]['ignore_doprocess'] = $ignoreReasonDoProcess; } function readCookieWithPreviousRqeuestShort() { $cookieName = ABJ404_PP . '_REQUEST_URI'; $cookieNameShort = $cookieName . '_SHORT'; if (array_key_exists($cookieNameShort, $_COOKIE) && array_key_exists($cookieName, $_COOKIE)) { return $_COOKIE[$cookieName]; } return ''; } /** Set a cookie with the requested URL. */ function setCookieWithPreviousRequest() { $abj404logging = ABJ_404_Solution_Logging::getInstance(); $requested_url = urldecode($_SERVER['REQUEST_URI']); // remove ridiculous non-printable characters $requested_url = preg_replace('/[^\x20-\x7E]/', '', $requested_url); // Remove non-printable ASCII characters // this may be used later when displaying suggestions. $cookieName = ABJ404_PP . '_REQUEST_URI'; $cookieNameShort = $cookieName . '_SHORT'; try { setcookie($cookieName, $requested_url, time() + (60 * 4), "/"); setcookie($cookieNameShort, $requested_url, time() + (5), "/"); // only set the update_URL if it's not already set. // this is because multiple redirects might happen and we want to store // only the user's original requested page. if (!isset($_COOKIE[$cookieName . '_UPDATE_URL']) || empty($_COOKIE[$cookieName . '_UPDATE_URL'])) { setcookie($cookieName . '_UPDATE_URL', urldecode($_SERVER['REQUEST_URI']), time() + (60 * 4), "/"); } } catch (Exception $e) { $abj404logging->debugMessage("There was an issue setting a cookie: " . $e->getMessage()); // This javascript redirect will only appear if the header redirect did not work for some reason. // document.cookie = "username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC"; $expireTime = date("D, d M Y H:i:s T", time() + (60 * 4)); $c = "\n" . '<script>document.cookie = "' . $cookieName . '=' . urldecode($_SERVER['REQUEST_URI']) . '; expires=' . $expireTime . '";</script>' . "\n"; echo $c; } $_REQUEST[ABJ404_PP][$cookieName] = $requested_url; } /** The passed in reason will be appended to the automatically generated reason. * @param string $reason */ function sendTo404Page($requestedURL, $reason = '', $useUserSpecified404 = true) { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404logic = ABJ_404_Solution_PluginLogic::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); $options = $abj404logic->getOptions(); // --------------------------------------- // if there's a default 404 page specified then use that. $dest404page = (array_key_exists('dest404page', $options) && isset($options['dest404page']) ? $options['dest404page'] : ABJ404_TYPE_404_DISPLAYED . '|' . ABJ404_TYPE_404_DISPLAYED); if ($useUserSpecified404 && $this->thereIsAUserSpecified404Page($dest404page)) { // $idAndType OK on regular 404 $permalink = ABJ_404_Solution_Functions::permalinkInfoToArray($dest404page, 0, null, $options); // make sure the page exists if (!in_array($permalink['status'], array('publish', 'published'))) { $msg = __("The user specified 404 page wasn't found. " . "Please update the user-specified 404 page on the Options page.", '404-solution'); $abj404logging->infoMessage($msg); } else { // dipslay the user specified 404 page. // get the existing redirect before adding a new one. $redirect = $abj404dao->getExistingRedirectForURL($requestedURL); if (!isset($redirect['id']) || $redirect['id'] == 0) { $abj404dao->setupRedirect($requestedURL, ABJ404_STATUS_CAPTURED, $permalink['type'], $permalink['id'], $options['default_redirect'], 0); } $abj404dao->logRedirectHit($requestedURL, $permalink['link'], 'user specified 404 page. ' . $reason); // set cookie here to remmeber to use a 404 status when displaying the 404 page setcookie(ABJ404_PP . '_STATUS_404', 'true', time() + 20, "/"); // the 404 page... $abj404logic->forceRedirect(esc_url($permalink['link']), esc_html($options['default_redirect']), '404Solution-404-page'); exit; } } // --------------------------------------- // give up. log the 404. if (@$options['capture_404'] == '1') { // get the existing redirect before adding a new one. $redirect = $abj404dao->getExistingRedirectForURL($requestedURL); if (!isset($redirect['id']) || $redirect['id'] == 0) { $abj404dao->setupRedirect($requestedURL, ABJ404_STATUS_CAPTURED, ABJ404_TYPE_404_DISPLAYED, ABJ404_TYPE_404_DISPLAYED, $options['default_redirect'], 0); } } else { $abj404logging->debugMessage("No permalink found to redirect to. capture_404 is off. Requested URL: " . $requestedURL . " | Redirect: (none)" . " | is_single(): " . is_single() . " | " . "is_page(): " . is_page() . " | is_feed(): " . is_feed() . " | is_trackback(): " . is_trackback() . " | is_preview(): " . is_preview() . " | options: " . wp_kses_post(json_encode($options))); } } /** Returns true if there is a custom 404 page. */ function thereIsAUserSpecified404Page($dest404page) { if ($dest404page == null) { return false; } $check1 = ($dest404page !== (ABJ404_TYPE_404_DISPLAYED . '|' . ABJ404_TYPE_404_DISPLAYED)); $check2 = ($dest404page !== ABJ404_TYPE_404_DISPLAYED); return $check1 && $check2; } /** * @param bool $skip_db_check * @return array */ function getOptions($skip_db_check = false) { if ($this->options == null) { $this->options = get_option('abj404_settings'); } $options = $this->options; if (!is_array($options)) { add_option('abj404_settings', '', '', 'no'); $options = array(); } // Check to make sure we aren't missing any new options. $defaults = $this->getDefaultOptions(); $missing = false; foreach ($defaults as $key => $value) { if (!isset($options) || $options == '' || !array_key_exists($key, $options) || !isset($options[$key]) || '' == $options[$key]) { $options[$key] = $value; $missing = true; } } if ($missing) { $this->updateOptions($options); } if ($skip_db_check == false) { if (!array_key_exists('DB_VERSION', $options) || $options['DB_VERSION'] != ABJ404_VERSION) { $options = $this->updateToNewVersion($options); } } return $options; } function updateOptions($options) { update_option('abj404_settings', $options); $this->options = $options; } /** Do any maintenance when upgrading to a new version. * @global type $abj404logging * @param array $options * @return array */ function updateToNewVersion($options) { $abj404logging = ABJ_404_Solution_Logging::getInstance(); $syncUtils = ABJ_404_Solution_SynchronizationUtils::getInstance(); $synchronizedKeyFromUser = "update_db_version"; $uniqueID = $syncUtils->synchronizerAcquireLockTry($synchronizedKeyFromUser); if ($uniqueID == '' || $uniqueID == null) { $abj404logging->debugMessage("Avoiding infinite loop on database update."); return $options; } try { $returnValue = $this->updateToNewVersionAction($options); } catch (Exception $e) { $abj404logging->errorMessage("Error updating to new version. ", $e); } $syncUtils->synchronizerReleaseLock($uniqueID, $synchronizedKeyFromUser); // update the permalink cache because updating the plugin version may affect it. $permalinkCache = ABJ_404_Solution_PermalinkCache::getInstance(); $permalinkCache->updatePermalinkCache(1); return $returnValue; } /** Do any maintenance when upgrading to a new version. * @global type $abj404logic * @global type $abj404logging * @global type $wpdb * @param array $options * @return array */ function updateToNewVersionAction($options) { global $wpdb; $abj404logging = ABJ_404_Solution_Logging::getInstance(); $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $f = ABJ_404_Solution_Functions::getInstance(); $currentDBVersion = "(unknown)"; if (array_key_exists('DB_VERSION', $options)) { $currentDBVersion = $options['DB_VERSION']; } $abj404logging->infoMessage(self::$uniqID . ": Updating database version from " . $currentDBVersion . " to " . ABJ404_VERSION . " (begin)."); // remove old log files. added in 2.28.0 $fileUtils = ABJ_404_Solution_Functions::getInstance(); $fileUtils->deleteDirectoryRecursively(ABJ404_PATH . 'temp/'); // wp_abj404_logsv2 exists since 1.7. $upgradesEtc = ABJ_404_Solution_DatabaseUpgradesEtc::getInstance(); $upgradesEtc->createDatabaseTables(true); // abj404_duplicateCronAction is no longer needed as of 1.7. wp_clear_scheduled_hook('abj404_duplicateCronAction'); ABJ_404_Solution_PluginLogic::doUnregisterCrons(); // added in 1.8.2 ABJ_404_Solution_PluginLogic::doRegisterCrons(); // since 1.9.0. ignore_doprocess add SeznamBot, Pinterestbot, UptimeRobot and "Slurp" -> "Yahoo! Slurp" if (version_compare($currentDBVersion, '1.9.0') < 0) { $userAgents = $f->explodeNewline($options['ignore_doprocess']); $uasForSearch = $f->explodeNewline($options['ignore_doprocess']); foreach ($userAgents as &$str) { if ($f->strtolower(trim($str)) == "slurp") { $str = "Yahoo! Slurp"; $abj404logging->infoMessage('Changed user agent "Slurp" to "Yahoo! Slurp" in the do not log list.'); } } if (!in_array("seznambot", $uasForSearch)) { $userAgents[] = 'SeznamBot'; $abj404logging->infoMessage('Added user agent "SeznamBot" to do not log list."'); } if (!in_array("pinterestbot", $uasForSearch)) { $userAgents[] = 'Pinterestbot'; $abj404logging->infoMessage('Added user agent "Pinterestbot" to do not log list."'); } if (!in_array("uptimerobot", $uasForSearch)) { $userAgents[] = 'UptimeRobot'; $abj404logging->infoMessage('Added user agent "UptimeRobot" to do not log list."'); } $options['ignore_doprocess'] = implode("\n",$userAgents); $this->updateOptions($options); } // move to the new log table if (version_compare($currentDBVersion, '1.8.0') < 0) { $query = "SHOW TABLES LIKE '{wp_abj404_logs}'"; $result = $abj404dao->queryAndGetResults($query); $rows = $result['rows']; // make sure empty() only sees a variable and not a function for older PHP versions, due to // https://stackoverflow.com/a/2173318 and // https://wordpress.org/support/topic/fatal-error-will-latest-release/ $filteredRows = array_filter($rows); if (!empty($filteredRows)) { $query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/migrateToNewLogsTable.sql"); $query = $abj404dao->doTableNameReplacements($query); $result = $abj404dao->queryAndGetResults($query); // if anything was successfully imported then delete the old table. if ($result['rows_affected'] > 0) { $abj404logging->infoMessage($result['rows_affected'] . ' log rows were migrated to the new table structre.'); // log the rows inserted/migrated. $wpdb->query('drop table ' . $f->strtolower($wpdb->prefix) . 'abj404_logs'); } } } if (version_compare($currentDBVersion, '2.18.0') < 0) { // add .well-known/acme-challenge/*, wp-content/themes/*, wp-content/plugins/* to folders_files_ignore $originalItems = $f->explodeNewline($options['folders_files_ignore']); $newItems = array("wp-content/plugins/*", "wp-content/themes/*", ".well-known/acme-challenge/*"); foreach ($newItems as $newItem) { if (array_search($newItem, $originalItems) === false) { $originalItems[] = $newItem; $abj404logging->infoMessage('Added ' . $newItem . ' to the list of folders to ignore."'); } } $options['folders_files_ignore'] = implode("\n",$originalItems); $this->updateOptions($options); } // add the second part of the default destination page. $dest404page = $options['dest404page']; if ($f->strpos($dest404page, '|') === false) { // not found if ($dest404page == '0') { $dest404page .= "|" . ABJ404_TYPE_404_DISPLAYED; } else { $dest404page .= '|' . ABJ404_TYPE_POST; } $options['dest404page'] = $dest404page; $this->updateOptions($options); } $options = $this->doUpdateDBVersionOption($options); $abj404logging->infoMessage(self::$uniqID . ": Updating database version to " . ABJ404_VERSION . " (end)."); return $options; } /** * @return array */ function getDefaultOptions() { $options = array( 'default_redirect' => '301', 'send_error_logs' => '0', 'capture_404' => '1', 'capture_deletion' => 1095, 'manual_deletion' => '0', 'log_deletion' => '365', 'admin_notification' => '0', 'remove_matches' => '1', 'suggest_minscore' => '25', 'suggest_max' => '5', 'suggest_title' => '<h3>{suggest_title_text}</h3>', 'suggest_before' => '<ol>', 'suggest_after' => '</ol>', 'suggest_entrybefore' => '<li>', 'suggest_entryafter' => '</li>', 'suggest_noresults' => '<p>{suggest_noresults_text}</p>', 'suggest_cats' => '1', 'suggest_tags' => '1', 'update_suggest_url' => '0', 'auto_redirects' => '1', 'auto_score' => '90', 'template_redirect_priority' => '9', 'auto_deletion' => '1095', 'auto_cats' => '1', 'auto_tags' => '1', 'dest404page' => '0|' . ABJ404_TYPE_404_DISPLAYED, 'maximum_log_disk_usage' => '10', 'ignore_dontprocess' => 'zemanta aggregator', 'ignore_doprocess' => "Googlebot\nMediapartners-Google\nAdsBot-Google\ndevelopers.google.com\n" . "Bingbot\nYahoo! Slurp\nDuckDuckBot\nBaiduspider\nYandexBot\nwww.sogou.com\nSogou-Test-Spider\n" . "Exabot\nfacebot\nfacebookexternalhit\nia_archiver\nSeznamBot\nPinterestbot\nUptimeRobot\nMJ12bot", 'recognized_post_types' => "page\npost\nproduct", 'recognized_categories' => "", 'folders_files_ignore' => implode("\n", array("wp-content/plugins/*", "wp-content/themes/*", ".well-known/acme-challenge/*")), 'folders_files_ignore_usable' => "", 'suggest_regex_exclusions' => "", 'suggest_regex_exclusions_usable' => "", 'plugin_admin_users' => "", 'debug_mode' => 0, 'days_wait_before_major_update' => 30, 'DB_VERSION' => '0.0.0', 'menuLocation' => 'underSettings', 'admin_notification_email' => '', 'page_redirects_order_by' => 'url', 'page_redirects_order' => 'ASC', 'captured_order_by' => 'logshits', 'captured_order' => 'DESC', 'excludePages[]' => '', ); return $options; } function doUpdateDBVersionOption($options = null) { if ($options == null) { $options = $this->getOptions(true); } $options['DB_VERSION'] = ABJ404_VERSION; $this->updateOptions($options); return $options; } /** Remove cron jobs. */ static function doUnregisterCrons() { $crons = array('abj404_cleanupCronAction', 'abj404_duplicateCronAction', 'removeDuplicatesCron', 'deleteOldRedirectsCron'); for ($i = 0; $i < count($crons); $i++) { $cron_name = $crons[$i]; $timestamp1 = wp_next_scheduled($cron_name); while ($timestamp1 != False) { wp_unschedule_event($timestamp1, $cron_name); $timestamp1 = wp_next_scheduled($cron_name); } $timestamp2 = wp_next_scheduled($cron_name, ''); while ($timestamp2 != False) { wp_unschedule_event($timestamp2, $cron_name, ''); $timestamp2 = wp_next_scheduled($cron_name, ''); } wp_clear_scheduled_hook($cron_name); } } /** Create database tables. Register crons. etc. * @global type $abj404logic * @global type $abj404dao */ static function runOnPluginActivation() { $abj404logic = ABJ_404_Solution_PluginLogic::getInstance(); $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); add_option('abj404_settings', '', '', 'no'); if (!isset($abj404logging)) { $abj404logging = ABJ_404_Solution_Logging::getInstance(); } if (!isset($abj404dao)) { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); } if (!isset($abj404logic)) { $abj404logic = ABJ_404_Solution_PluginLogic::getInstance(); } $upgradesEtc = ABJ_404_Solution_DatabaseUpgradesEtc::getInstance(); $upgradesEtc->createDatabaseTables(); ABJ_404_Solution_PluginLogic::doRegisterCrons(); $abj404logic->doUpdateDBVersionOption(); } static function doRegisterCrons() { if (!wp_next_scheduled('abj404_cleanupCronAction')) { // we randomize this so that when the geo2ip file is downloaded, there aren't a whole // lot of users that request the file at the same time. $timeForEvent = '0' . rand(0, 5) . ':' . rand(10, 59) . ':' . rand(10, 59); wp_schedule_event(strtotime($timeForEvent), 'daily', 'abj404_cleanupCronAction'); } } function getDebugLogFileLink() { return "?page=" . ABJ404_PP . "&subpage=abj404_debugfile"; } /** Do the passed in action and return the associated message. * @global type $abj404logic * @param string $action * @param string $sub * @return string */ function handlePluginAction($action, &$sub) { $abj404logic = ABJ_404_Solution_PluginLogic::getInstance(); $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); $f = ABJ_404_Solution_Functions::getInstance(); $message = ""; $message = array_key_exists('display-this-message', $_POST) ? sanitize_text_field($_POST['display-this-message']) : ''; if ($action == "updateOptions") { if (wp_verify_nonce($_POST['nonce'], 'abj404UpdateOptions') || !is_admin()) { // delete the debug file and lose all changes, or if (array_key_exists('deleteDebugFile', $_POST) && $_POST['deleteDebugFile']) { $filepath = $abj404logging->getDebugFilePath(); if (!file_exists($filepath)) { $message = sprintf(__("Debug file not found. (%s)", '404-solution'), $filepath); } else if ($abj404logging->deleteDebugFile()) { $message = sprintf(__("Debug file(s) deleted. (%s)", '404-solution'), $filepath); } else { $message = sprintf(__("Issue deleting debug file. (%s)", '404-solution'), $filepath); } return $message; } // save all changes. saveOptions, saveSettings $sub = "abj404_options"; } else { $abj404logging->debugMessage("Unexpected result. How did we get here? is_admin: " . is_admin() . ", Action: " . $action . ", Sub: " . $sub); } } else if ($action == "addRedirect") { if (check_admin_referer('abj404addRedirect') && is_admin()) { $message = $this->addAdminRedirect(); if ($message == "") { $message = __('New Redirect Added Successfully!', '404-solution'); } else { $message .= __('Error: unable to add new redirect.', '404-solution'); } } else { $abj404logging->debugMessage("Unexpected result. How did we get here? is_admin: " . is_admin() . ", Action: " . $action . ", Sub: " . $sub); } } else if ($action == "emptyRedirectTrash") { if (check_admin_referer('abj404_bulkProcess') && is_admin()) { $abj404logic->doEmptyTrash('abj404_redirects'); $message = __('All trashed URLs have been deleted!', '404-solution'); } else { $abj404logging->debugMessage("Unexpected result. How did we get here? is_admin: " . is_admin() . ", Action: " . $action . ", Sub: " . $sub); } } else if ($action == "emptyCapturedTrash") { if (check_admin_referer('abj404_bulkProcess') && is_admin()) { $abj404logic->doEmptyTrash('abj404_captured'); $message = __('All trashed URLs have been deleted!', '404-solution'); } else { $abj404logging->debugMessage("Unexpected result. How did we get here? is_admin: " . is_admin() . ", Action: " . $action . ", Sub: " . $sub); } } else if ($action == "purgeRedirects") { if (check_admin_referer('abj404_purgeRedirects') && is_admin()) { $message = $abj404dao->deleteSpecifiedRedirects(); } else { $abj404logging->debugMessage("Unexpected result. How did we get here? is_admin: " . is_admin() . ", Action: " . $action . ", Sub: " . $sub); } } else if ($action == "runMaintenance") { if (check_admin_referer('abj404_runMaintenance') && is_admin()) { $message = $abj404dao->deleteOldRedirectsCron(); } else { $abj404logging->debugMessage("Unexpected result. How did we get here? is_admin: " . is_admin() . ", Action: " . $action . ", Sub: " . $sub); } } else if ($f->substr($action . '', 0, 4) == "bulk") { if (check_admin_referer('abj404_bulkProcess') && is_admin()) { if (!array_key_exists('idnum', $_POST) || !isset($_POST['idnum'])) { $abj404logging->debugMessage("No ID(s) specified for bulk action: " . esc_html($action)); echo sprintf(__("Error: No ID(s) specified for bulk action. (%s)", '404-solution'), esc_html($action), false); return; } $message = $abj404logic->doBulkAction($action, array_map('absint', $_POST['idnum'])); } else { $abj404logging->debugMessage("Unexpected result. How did we get here? is_admin: " . is_admin() . ", Action: " . $action . ", Sub: " . $sub); } } return $message; } /** Move redirects to trash. * @return string */ function hanldeTrashAction() { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); $message = ""; // Handle Trash Functionality if (array_key_exists('trash', $_GET) && isset($_GET['trash'])) { if (check_admin_referer('abj404_trashRedirect') && is_admin()) { $trash = ""; if ($_GET['trash'] == 0) { $trash = 0; } else if ($_GET['trash'] == 1) { $trash = 1; } else { $abj404logging->errorMessage("Unexpected trash operation: " . esc_html($_GET['trash'])); $message = __('Error: Bad trash operation specified.', '404-solution'); return $message; } $message = $abj404dao->moveRedirectsToTrash(absint($_GET['id']), $trash); if ($message == "") { if ($trash == 1) { $message = __('Redirect moved to trash successfully!', '404-solution'); } else { $message = __('Redirect restored from trash successfully!', '404-solution'); } } else { if ($trash == 1) { $message = __('Error: Unable to move redirect to trash.', '404-solution'); } else { $message = __('Error: Unable to move redirect from trash.', '404-solution'); } } } } return $message; } function handleActionChangeItemsPerRow() { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); if ($abj404dao->getPostOrGetSanitize('action') == 'changeItemsPerRow') { $this->updatePerPageOption(absint($abj404dao->getPostOrGetSanitize('perpage'))); } } function handleActionExport() { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); if (($abj404dao->getPostOrGetSanitize('action') == 'exportRedirects') && $this->userIsPluginAdmin()) { check_admin_referer('abj404_exportRedirects'); // this verifies the nonce $this->doExport(); } } function handleActionImportFile() { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); if ($abj404dao->getPostOrGetSanitize('action') == 'importRedirectsFile') { $result = $this->doImportFile(); return $result; } } function getExportFilename() { $tempFile = abj404_getUploadsDir() . 'export.csv'; return $tempFile; } function doExport() { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $tempFile = $this->getExportFilename(); $abj404dao->doRedirectsExport($tempFile); if (file_exists($tempFile)) { header('Content-Description: File Transfer'); header('Content-Disposition: attachment; filename=' . basename($tempFile)); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($tempFile)); header("Content-Type: text/plain"); readfile($tempFile); exit(); // avoid headers already sent error. avoid other things executing afterwards. } else { $abj404logging = ABJ_404_Solution_Logging::getInstance(); $abj404logging->infoMessage("I don't see any data to export."); } } /** Expected formats are * from_url,status,type,to_url,wp_type * from_url,to_url */ function doImportFile() { $anyIssuesToNote = array(); if (isset($_FILES['import_file']) && $_FILES['import_file']['error'] == UPLOAD_ERR_OK) { // Open the uploaded file for reading $file_handle = fopen($_FILES['import_file']['tmp_name'], 'r'); if (!$file_handle) { return "Error opening the file."; } while (($line = fgets($file_handle)) !== false) { $dataArray = $this->splitCsvLine($line); if (isset($dataArray['error'])) { return $dataArray['error']; } $anyIssuesToNote = array_merge($anyIssuesToNote, $this->loadDataArrayFromFile($dataArray)); } fclose($file_handle); } else { return "File upload error."; } if (count($anyIssuesToNote) > 0) { return 'Error: ' . implode(", <BR/>\n", $anyIssuesToNote); } $msg = __("The file seems to have loaded okay. Please check the redirects page.", '404-solution'); return $msg; } function loadDataArrayFromFile($dataArray) { if ($dataArray['from_url'] == 'from_url' || $dataArray['from_url'] == 'request') { return array(); } $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); $fromURL = $dataArray['from_url']; $status = ABJ404_STATUS_MANUAL; $final_dest = $dataArray['to_url']; $anyIssuesToNote = array(); // avoid duplicates - verify that the from URL doesn't already exist as a redirect. $maybeExisting2 = $abj404dao->getExistingRedirectForURL($fromURL); if ((count($maybeExisting2) > 0 && $maybeExisting2['id'] != 0)) { $msg = "Ignored importing redirect because a redirect with the same " . "from URL already exists. URL: " . $fromURL; $abj404logging->warn($msg); array_push($anyIssuesToNote, $msg); return $anyIssuesToNote; } // Determine $type based on $final_dest if (empty($final_dest)) { $type = ABJ404_TYPE_404_DISPLAYED; // "404" } else if ($final_dest == '5') { $type = ABJ404_TYPE_HOME; // "homepage" } else if (strpos($final_dest, 'http') !== false) { $type = ABJ404_TYPE_EXTERNAL; // "external" // if there's any kind of regular expression character that is NOT a valid // URL character then we'll assume it's a regular expression. $urlPattern = '/[!#$&\'()*+,;=]/'; if (preg_match($urlPattern, $fromURL)) { $status = ABJ404_STATUS_REGEX; } } else if (strpos($final_dest, '/') === 0) { $type = ABJ404_TYPE_POST; // Initially set to "page/post" } else { // Some default or error handling in case $final_dest does not match any expected format $msg = "Unrecognized destination type while importing file. " . "Destination: " . $final_dest; $abj404logging->warn($msg); array_push($anyIssuesToNote, $msg); return $anyIssuesToNote; } // If the type is set to ABJ404_TYPE_404_DISPLAYED if ($type == ABJ404_TYPE_404_DISPLAYED) { $final_dest = ABJ404_TYPE_404_DISPLAYED; } else if (strpos($final_dest, 'http') !== false) { // Check if $final_dest contains "http" $type = ABJ404_TYPE_EXTERNAL; // $final_dest remains unchanged } else if ($type == ABJ404_TYPE_HOME) { // If the type is set to ABJ404_TYPE_HOME $final_dest = ABJ404_TYPE_HOME; } else { // If the type is post, further refine the type and set the ID // Trim slashes for slug compatibility $slug = trim($final_dest, '/'); // Check if slug corresponds to a post $postsFromSlugRows = $abj404dao->getPublishedPagesAndPostsIDs($slug); $postsFromCategoryRows = $abj404dao->getPublishedCategories(null, $slug); $postsFromTagRows = $abj404dao->getPublishedTags($slug); $postFromSlug = isset($postsFromSlugRows[0]) ? $postsFromSlugRows[0] : null; $postFromCategory = isset($postsFromCategoryRows[0]) ? $postsFromCategoryRows[0] : null; $postFromTag = isset($postsFromTagRows[0]) ? $postsFromTagRows[0] : null; if ($postFromSlug) { $type = ABJ404_TYPE_POST; $final_dest = $postFromSlug->id; // Set to post ID } else if ($postFromCategory) { // Check if slug corresponds to a category $type = ABJ404_TYPE_CAT; $final_dest = $postFromCategory->term_id; // Set to category ID } else if ($postFromTag) { // Check if slug corresponds to a tag $type = ABJ404_TYPE_TAG; $final_dest = $postFromTag->term_id; // Set to tag ID } else { $abj404logging->warn("Couldn't find post from slug. slug: " . $slug); } } $abj404dao->setupRedirect($fromURL, $status, $type, $final_dest, 301); return $anyIssuesToNote; } function splitCsvLine($line) { // Split the CSV line into an array $data = array_map('trim', str_getcsv($line)); // Trim each value in the array // Check the format based on the number of columns if (count($data) === 5) { // Format: from_url,status,type,to_url,wp_type return [ 'from_url' => $data[0], 'status' => $data[1], 'type' => $data[2], 'to_url' => $data[3], 'wp_type' => $data[4] ]; } else if (count($data) === 2) { // Format: from_url,to_url return [ 'from_url' => $data[0], 'to_url' => $data[1] ]; } else { // Invalid format or unexpected number of columns return ["error" => "Invalid CSV format. " . count($data) . " found but 2 or 5 expected."]; } } function updatePerPageOption($rows) { $showRows = max($rows, ABJ404_OPTION_MIN_PERPAGE); $showRows = min($showRows, ABJ404_OPTION_MAX_PERPAGE); $options = $this->getOptions(); $options['perpage'] = $showRows; $this->updateOptions($options); } /** * * @global type $abj404dao * @global type $abj404logging * @return string */ function handleActionImportRedirects() { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); $message = ""; if ($abj404dao->getPostOrGetSanitize('action') == 'importRedirects') { if ($abj404dao->getPostOrGetSanitize('sanity_404redirected') != '1') { $message = __("Error: You didn't check the I understand checkbox. No importing for you!", '404-solution'); return $message; } check_admin_referer('abj404_importRedirects'); try { $result = $abj404dao->importDataFromPluginRedirectioner(); if ($result['last_error'] != '') { $message = sprintf(__("Error: No records were imported. SQL result: %s", '404-solution'), wp_kses_post(json_encode($result['last_error']))); } else { $message = sprintf(__("Records imported: %s", '404-solution'), esc_html($result['rows_affected'])); } } catch (Exception $e) { $message = "Error: Importing failed. Message: " . $e->getMessage(); $abj404logging->errorMessage('Error importing redirects.', $e); } } return $message; } /** Delete redirects. * @global type $abj404dao * @return string */ function handleDeleteAction() { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $f = ABJ_404_Solution_Functions::getInstance(); $message = ""; //Handle Delete Functionality if (array_key_exists('remove', $_GET) && @$_GET['remove'] == 1) { if (check_admin_referer('abj404_removeRedirect') && is_admin()) { if ($f->regexMatch('[0-9]+', $_GET['id'])) { $abj404dao->deleteRedirect(absint($_GET['id'])); $message = __('Redirect Removed Successfully!', '404-solution'); } } } return $message; } /** Set a redirect as ignored. * @return string */ function handleIgnoreAction() { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); $f = ABJ_404_Solution_Functions::getInstance(); $message = ""; //Handle Ignore Functionality if (array_key_exists('ignore', $_GET) && isset($_GET['ignore'])) { if (check_admin_referer('abj404_ignore404') && is_admin()) { if ($_GET['ignore'] != 0 && $_GET['ignore'] != 1) { $abj404logging->debugMessage("Unexpected ignore operation: " . esc_html($_GET['ignore'])); $message = __('Error: Bad ignore operation specified.', '404-solution'); return $message; } if ($f->regexMatch('[0-9]+', $_GET['id'])) { if ($_GET['ignore'] == 1) { $newstatus = ABJ404_STATUS_IGNORED; } else { $newstatus = ABJ404_STATUS_CAPTURED; } $message = $abj404dao->updateRedirectTypeStatus(absint($_GET['id']), $newstatus); if ($message == "") { if ($newstatus == ABJ404_STATUS_CAPTURED) { $message = __('Removed 404 URL from ignored list successfully!', '404-solution'); } else { $message = __('404 URL marked as ignored successfully!', '404-solution'); } } else { if ($newstatus == ABJ404_STATUS_CAPTURED) { $message = __('Error: unable to remove URL from ignored list', '404-solution'); } else { $message = __('Error: unable to mark URL as ignored', '404-solution'); } } } } } return $message; } /** Set a redirect as "organize later". * @return string */ function handleLaterAction() { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); $f = ABJ_404_Solution_Functions::getInstance(); $message = ""; //Handle Ignore Functionality if (array_key_exists('later', $_GET) && isset($_GET['later'])) { if (check_admin_referer('abj404_organizeLater') && is_admin()) { if ($_GET['later'] != 0 && $_GET['later'] != 1) { $abj404logging->debugMessage("Unexpected organize later operation: " . esc_html($_GET['later'])); $message = __('Error: Bad organize later operation specified.', '404-solution'); return $message; } if ($f->regexMatch('[0-9]+', $_GET['id'])) { if ($_GET['later'] == 1) { $newstatus = ABJ404_STATUS_LATER; } else { $newstatus = ABJ404_STATUS_CAPTURED; } $message = $abj404dao->updateRedirectTypeStatus(absint($_GET['id']), $newstatus); if ($message == "") { if ($newstatus == ABJ404_STATUS_CAPTURED) { $message = __('Removed 404 URL from organize later list successfully!', '404-solution'); } else { $message = __('404 URL marked as organize later successfully!', '404-solution'); } } else { if ($newstatus == ABJ404_STATUS_CAPTURED) { $message = __('Error: unable to remove URL from organize later list', '404-solution'); } else { $message = __('Error: unable to mark URL as organize later', '404-solution'); } } } } } return $message; } /** Edit redirect data. * @global type $abj404dao * @param string $sub * @param string $action * @return string */ function handleActionEdit(&$sub, &$action) { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $f = ABJ_404_Solution_Functions::getInstance(); $message = ""; //Handle edit posts if (array_key_exists('action', $_POST) && $_POST['action'] == "editRedirect") { $id = $abj404dao->getPostOrGetSanitize('id'); $ids = $abj404dao->getPostOrGetSanitize('ids_multiple'); if (!($id === null && $ids === null) && ($f->regexMatch('[0-9]+', '' . $id) || $f->regexMatch('[0-9]+', '' . $ids))) { if (check_admin_referer('abj404editRedirect') && is_admin()) { $message = $this->updateRedirectData(); if ($message == "") { $message .= __('Redirect Information Updated Successfully!', '404-solution'); $sub = 'abj404_redirects'; $action = ''; } else { $message .= __('Error: Unable to update redirect data.', '404-solution'); } } } } return $message; } /** * @global type $abj404dao * @param string $action * @param array $ids * @return string */ function doBulkAction($action, $ids) { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); $message = ""; // nonce already verified. $abj404logging->debugMessage("In doBulkAction. Action: " . esc_html($action == '' ? '(none)' : $action)) . ", ids: " . wp_kses_post(json_encode($ids)); if ($action == "bulkignore" || $action == "bulkcaptured" || $action == "bulklater" || $action == "bulk_trash_restore") { if ($action == "bulkignore") { $status = ABJ404_STATUS_IGNORED; } else if ($action == "bulkcaptured") { $status = ABJ404_STATUS_CAPTURED; } else if ($action == "bulklater") { $status = ABJ404_STATUS_LATER; } else if ($action == "bulk_trash_restore") { // don't change the status for this case. } else { $abj404logging->errorMessage("Unrecognized bulk action: " . esc_html($action)); echo sprintf(__("Error: Unrecognized bulk action. (%s)", '404-solution'), esc_html($action)); return; } $count = 0; foreach ($ids as $id) { $s = $abj404dao->moveRedirectsToTrash($id, 0); if ($action != "bulk_trash_restore") { $s = $abj404dao->updateRedirectTypeStatus($id, $status); } if ($s == "") { $count++; } } if ($action == "bulkignore") { $message = $count . " " . __('URL(s) marked as Ignored.', '404-solution'); } else if ($action == "bulkcaptured") { $message = $count . " " . __('URL(s) marked as Captured.', '404-solution'); } else if ($action == "bulklater") { $message = $count . " " . __('URL(s) marked as Later.', '404-solution'); } else if ($action == "bulk_trash_restore") { $message = $count . " " . __('URL(s) restored.', '404-solution'); } else { $abj404logging->errorMessage("Unrecognized bulk action: " . esc_html($action)); echo sprintf(__("Error: Unrecognized bulk action. (%s)", '404-solution'), esc_html($action)); } } else if ($action == "bulk_trash_delete_permanently") { $count = 0; foreach ($ids as $id) { $abj404dao->deleteRedirect(absint($id)); $count ++; } $message = $count . " " . __('URL(s) deleted', '404-solution'); } else if ($action == "bulktrash") { $count = 0; foreach ($ids as $id) { $s = $abj404dao->moveRedirectsToTrash($id, 1); if ($s == "") { $count ++; } } $message = $count . " " . __('URL(s) moved to trash', '404-solution'); } else { $abj404logging->errorMessage("Unrecognized bulk action: " . esc_html($action)); echo sprintf(__("Error: Unrecognized bulk action. (%s)", '404-solution'), esc_html($action)); } return $message; } /** * This is for both empty trash buttons (page redirects and captured 404 URLs). * @param string $sub */ function doEmptyTrash($sub) { $abj404logging = ABJ_404_Solution_Logging::getInstance(); global $wpdb; global $abj404_redirect_types; global $abj404_captured_types; $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $query = ""; if ($sub == "abj404_captured") { $query = "delete FROM {wp_abj404_redirects} \n" . "where disabled = 1 \n" . " and status in (" . implode(", ", $abj404_captured_types) . ")"; } else if ($sub == "abj404_redirects") { $query = "delete FROM {wp_abj404_redirects} \n" . "where disabled = 1 \n" . " and status in (" . implode(", ", $abj404_redirect_types) . ")"; } else { $abj404logging->errorMessage("Unrecognized type in doEmptyTrash(" . $sub . ")"); } $result = $abj404dao->queryAndGetResults($query); $abj404logging->debugMessage("doEmptyTrash deleted " . $result['rows_affected'] . " rows total. (" . $sub . ")"); $abj404dao->queryAndGetResults("optimize table {wp_abj404_redirects}"); } /** * @global type $abj404dao * @return string */ function updateRedirectData() { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); $f = ABJ_404_Solution_Functions::getInstance(); $message = ""; $fromURL = ""; $ids_multiple = ""; if ( (!array_key_exists('url', $_POST) || $_POST['url'] == "") && (array_key_exists('ids_multiple', $_POST) && $_POST['ids_multiple'] != "")) { $ids_multiple = array_map('absint', explode(',', $_POST['ids_multiple'])); } else if (array_key_exists('url', $_POST) && $_POST['url'] != "" && (!array_key_exists('ids_multiple', $_POST) || $_POST['ids_multiple'] == "")) { $fromURL = stripslashes($_POST['url']); } else { $message .= __('Error: URL is a required field.', '404-solution') . "<BR/>"; } if ($fromURL != "" && $f->substr($_POST['url'], 0, 1) != "/") { $message .= __('Error: URL must start with /', '404-solution') . "<BR/>"; } $typeAndDest = $this->getRedirectTypeAndDest(); if ($typeAndDest['message'] != "") { return $typeAndDest['message']; } if ($typeAndDest['type'] != "" && $typeAndDest['dest'] !== "") { $statusType = ABJ404_STATUS_MANUAL; if (array_key_exists('is_regex_url', $_POST) && isset($_POST['is_regex_url']) && $_POST['is_regex_url'] != '0') { $statusType = ABJ404_STATUS_REGEX; } // decide whether we're updating one or multiple redirects. if ($fromURL != "") { $abj404dao->updateRedirect($typeAndDest['type'], $typeAndDest['dest'], $fromURL, $_POST['id'], $_POST['code'], $statusType); } else if ($ids_multiple != "") { // get the redirect data for each ID. $redirects_multiple = $abj404dao->getRedirectsByIDs($ids_multiple); foreach ($redirects_multiple as $redirect) { $abj404dao->updateRedirect($typeAndDest['type'], $typeAndDest['dest'], $redirect['url'], $redirect['id'], $_POST['code'], $statusType); } } else { $abj404logging->errorMessage("Issue determining which redirect(s) to update. " . "fromURL: " . $fromURL . ", ids_multiple: " . (is_array($ids_multiple) ? implode(',', $ids_multiple) : '')); } } else { $message .= __('Error: Data not formatted properly.', '404-solution') . "<BR/>"; $abj404logging->errorMessage("Update redirect data issue. Type: " . esc_html($typeAndDest['type']) . ", dest: " . esc_html($typeAndDest['dest'])); } return $message; } function getRedirectTypeAndDest() { $abj404logging = ABJ_404_Solution_Logging::getInstance(); $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $f = ABJ_404_Solution_Functions::getInstance(); $response = array(); $response['type'] = ""; $response['dest'] = ""; $response['message'] = ""; if ($_POST['redirect_to_data_field_id'] == ABJ404_TYPE_EXTERNAL . '|' . ABJ404_TYPE_EXTERNAL) { $userEnteredURL = esc_url($abj404dao->getPostOrGetSanitize('redirect_to_user_field')); if ($userEnteredURL == "") { $response['message'] = __('Error: You selected external URL but did not enter a URL.', '404-solution') . "<BR/>"; } else if ($f->strlen($userEnteredURL) < 8) { $response['message'] = __('Error: External URL is too short.', '404-solution') . "<BR/>"; } else if ($f->strpos($userEnteredURL, "://") === false) { $response['message'] = __("Error: External URL doesn't contain ://", '404-solution') . "<BR/>"; } } if ($response['message'] != "") { return $response; } $info = explode("|", sanitize_text_field($_POST['redirect_to_data_field_id'])); if ($_POST['redirect_to_data_field_id'] == ABJ404_TYPE_EXTERNAL . '|' . ABJ404_TYPE_EXTERNAL) { $response['type'] = ABJ404_TYPE_EXTERNAL; $response['dest'] = $_POST['redirect_to_user_field']; } else { if (count($info) == 2) { $response['dest'] = absint($info[0]); $response['type'] = $info[1]; } else { $abj404logging->errorMessage("Unexpected info while updating redirect: " . wp_kses_post(json_encode($info))); } } return $response; } /** * @global type $abj404dao * @return string */ function addAdminRedirect() { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); $f = ABJ_404_Solution_Functions::getInstance(); $message = ""; if ($_POST['manual_redirect_url'] == "") { $message .= __('Error: URL is a required field.', '404-solution') . "<BR/>"; return $message; } if ($f->substr($_POST['manual_redirect_url'], 0, 1) != "/") { $message .= __('Error: URL must start with /', '404-solution') . "<BR/>"; return $message; } $typeAndDest = $this->getRedirectTypeAndDest(); if ($typeAndDest['message'] != "") { return $typeAndDest['message']; } if ($typeAndDest['type'] != "" && $typeAndDest['dest'] !== "") { // url match type. regex or normal exact match. $statusType = ABJ404_STATUS_MANUAL; if (array_key_exists('is_regex_url', $_POST) && isset($_POST['is_regex_url']) && $_POST['is_regex_url'] != '0') { $statusType = ABJ404_STATUS_REGEX; } $abj404dao->setupRedirect(esc_url($_POST['manual_redirect_url']), $statusType, $typeAndDest['type'], $typeAndDest['dest'], sanitize_text_field($_POST['code']), 0); } else { $message .= __('Error: Data not formatted properly.', '404-solution') . "<BR/>"; $abj404logging->errorMessage("Add redirect data issue. Type: " . esc_html($typeAndDest['type']) . ", dest: " . esc_html($typeAndDest['dest'])); } return $message; } /** * @param string $pageBeingViewed * @return array */ function getTableOptions($pageBeingViewed) { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $f = ABJ_404_Solution_Functions::getInstance(); $tableOptions = array(); $options = $this->getOptions(true); $translationArray = array( '{ABJ404_STATUS_MANUAL_text}' => __('Man', '404-solution'), '{ABJ404_STATUS_AUTO_text}' => __('Auto', '404-solution'), '{ABJ404_STATUS_REGEX_text}' => __('RegEx', '404-solution'), '{ABJ404_TYPE_EXTERNAL_text}' => __('External', '404-solution'), '{ABJ404_TYPE_CAT_text}' => __('Category', '404-solution'), '{ABJ404_TYPE_TAG_text}' => __('Tag', '404-solution'), '{ABJ404_TYPE_HOME_text}' => __('Home Page', '404-solution'), '{ABJ404_TYPE_404_DISPLAYED_text}' => __('(Default 404 Page)', '404-solution'), '{ABJ404_TYPE_SPECIAL_text}' => __('(Special)', '404-solution'), ); $tableOptions['translations'] = $translationArray; $tableOptions['filter'] = intval($abj404dao->getPostOrGetSanitize("filter", "")); if ($tableOptions['filter'] == "") { if ($abj404dao->getPostOrGetSanitize('subpage') == 'abj404_captured') { $tableOptions['filter'] = ABJ404_STATUS_CAPTURED; } else { $tableOptions['filter'] = '0'; } } $tableOptions['filterText'] = trim($abj404dao->getPostOrGetSanitize("filterText", "")); $tableOptions['filterText'] = $f->str_replace('*/', '', $tableOptions['filterText']); if ($abj404dao->getPostOrGetSanitize('orderby', "") != "") { $tableOptions['orderby'] = $abj404dao->getPostOrGetSanitize('orderby'); if ($pageBeingViewed == 'abj404_redirects') { $options['page_redirects_order_by'] = $tableOptions['orderby']; $this->updateOptions($options); } else if ($pageBeingViewed == 'abj404_captured') { $options['captured_order_by'] = $tableOptions['orderby']; $this->updateOptions($options); } } else if ($pageBeingViewed == "abj404_logs") { $tableOptions['orderby'] = "timestamp"; } else if ($pageBeingViewed == 'abj404_redirects') { $tableOptions['orderby'] = $options['page_redirects_order_by']; } else if ($pageBeingViewed == 'abj404_captured') { $tableOptions['orderby'] = $options['captured_order_by']; } else { $tableOptions['orderby'] = "url"; } if ($abj404dao->getPostOrGetSanitize('order', '') != '') { $tableOptions['order'] = $abj404dao->getPostOrGetSanitize('order'); if ($pageBeingViewed == 'abj404_redirects') { $options['page_redirects_order'] = $tableOptions['order']; $this->updateOptions($options); } else if ($pageBeingViewed == 'abj404_captured') { $options['captured_order'] = $tableOptions['order']; $this->updateOptions($options); } } else if ($tableOptions['orderby'] == "created" || $tableOptions['orderby'] == "lastused" || $tableOptions['orderby'] == "timestamp") { $tableOptions['order'] = "DESC"; } else if ($pageBeingViewed == 'abj404_redirects') { $tableOptions['order'] = $options['page_redirects_order']; } else if ($pageBeingViewed == 'abj404_captured') { $tableOptions['order'] = $options['captured_order']; } else { $tableOptions['order'] = "ASC"; } $tableOptions['paged'] = $abj404dao->getPostOrGetSanitize("paged", 1); $perPageOption = ABJ404_OPTION_DEFAULT_PERPAGE; if (array_key_exists('perpage', $options) && isset($options['perpage'])) { $perPageOption = max(absint($options['perpage']), ABJ404_OPTION_MIN_PERPAGE); } $tableOptions['perpage'] = $abj404dao->getPostOrGetSanitize("perpage", $perPageOption); $tableOptions['logsid'] = 0; if ($abj404dao->getPostOrGetSanitize('subpage') == "abj404_logs") { if (array_key_exists('id', $_GET) && isset($_GET['id']) && $f->regexMatch('[0-9]+', $_GET['id'])) { $tableOptions['logsid'] = absint($_GET['id']); } else if (array_key_exists('redirect_to_data_field_id', $_GET) && isset($_GET['redirect_to_data_field_id']) && $f->regexMatch('[0-9]+', $_GET['redirect_to_data_field_id'])) { $tableOptions['logsid'] = absint($_GET['redirect_to_data_field_id']); } } // sanitize all values. $sanitizedTableOptions = $this->sanitizePostData($tableOptions); return $sanitizedTableOptions; } /** * @param array $postData * @param boolean $restoreNewlines * @return array */ function sanitizePostData($postData, $restoreNewlines = false) { $newData = array(); foreach ($postData as $key => $value) { $key = wp_kses_post($key); if (is_array($value)) { $newData[$key] = $this->sanitizePostData($value, $restoreNewlines); } else { $newData[$key] = wp_kses_post($value); $newData[$key] = esc_sql($newData[$key]); if ($restoreNewlines) { $newData[$key] = str_replace('\n', "\n", $newData[$key]); } } } return $newData; } /** Remove non a-zA-Z0-9 or _ characters. * @param string $str * @return string */ function sanitizeForSQL($str) { if ($str == null || $str == '') { return $str; } $re = '/[^\w_]/'; $result = preg_replace($re, '', $str); return $result; } /** * @return string */ function updateOptionsFromPOST() { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); $f = ABJ_404_Solution_Functions::getInstance(); $message = ""; $options = $this->getOptions(); // get the submitted settings $encodedData = $_POST['encodedData']; $postData = $f->decodeComplicatedData($encodedData); // to return after handling the ajax call. $returnData = array(); $returnData['newURL'] = admin_url() . "options-general.php?page=" . ABJ404_PP . '&subpage=abj404_options'; // verify nonce if (!wp_verify_nonce($postData['nonce'], 'abj404UpdateOptions') || !is_admin()) { $returnData['message'] = 'Task failed successfully.'; echo json_encode($returnData); exit(1); } $_POST = $postData; // delete the debug file if requested. if (array_key_exists('deleteDebugFile', $_POST) && $_POST['deleteDebugFile'] == true) { $sub = ''; $returnData['error'] = ''; $returnData['message'] = $this->handlePluginAction('updateOptions', $sub); } else { // save all options. // options with custom messages. if (array_key_exists('default_redirect', $_POST) && isset($_POST['default_redirect'])) { if ($_POST['default_redirect'] == "301" || $_POST['default_redirect'] == "302") { $options['default_redirect'] = intval($_POST['default_redirect']); } else { $message .= __('Error: Invalid value specified for default redirect type', '404-solution') . ".<BR/>"; } } if (array_key_exists('ignore_dontprocess', $_POST) && isset($_POST['ignore_dontprocess'])) { $options['ignore_dontprocess'] = wp_kses_post($_POST['ignore_dontprocess']); } if (array_key_exists('ignore_doprocess', $_POST) && isset($_POST['ignore_doprocess'])) { $options['ignore_doprocess'] = wp_kses_post($_POST['ignore_doprocess']); } if (array_key_exists('recognized_post_types', $_POST) && isset($_POST['recognized_post_types'])) { $options['recognized_post_types'] = wp_kses_post($_POST['recognized_post_types']); } if (array_key_exists('recognized_categories', $_POST) && isset($_POST['recognized_categories'])) { $options['recognized_categories'] = wp_kses_post($_POST['recognized_categories']); } if (array_key_exists('menuLocation', $_POST) && isset($_POST['menuLocation'])) { $options['menuLocation'] = wp_kses_post($_POST['menuLocation']); } if (array_key_exists('admin_notification', $_POST) && isset($_POST['admin_notification'])) { if (is_numeric($_POST['admin_notification'])) { $options['admin_notification'] = absint($_POST['admin_notification']); } } if (array_key_exists('capture_deletion', $_POST) && isset($_POST['capture_deletion'])) { if (is_numeric($_POST['capture_deletion']) && $_POST['capture_deletion'] >= 0) { $options['capture_deletion'] = absint($_POST['capture_deletion']); } else { $message .= __('Error: Collected URL deletion value must be a number greater than or equal to zero', '404-solution') . ".<BR/>"; } } if (array_key_exists('manual_deletion', $_POST) && isset($_POST['manual_deletion'])) { if (is_numeric($_POST['manual_deletion']) && $_POST['manual_deletion'] >= 0) { $options['manual_deletion'] = absint($_POST['manual_deletion']); } else { $message .= __('Error: Manual redirect deletion value must be a number greater than or equal to zero', '404-solution') . ".<BR/>"; } } if (array_key_exists('log_deletion', $_POST) && isset($_POST['log_deletion'])) { if (is_numeric($_POST['log_deletion']) && $_POST['log_deletion'] >= 0) { $options['log_deletion'] = absint($_POST['log_deletion']); } else { $message .= __('Error: Log deletion value must be a number greater than or equal to zero', '404-solution') . ".<BR/>"; } } if (array_key_exists('days_wait_before_major_update', $_POST) && isset($_POST['days_wait_before_major_update'])) { if (is_numeric($_POST['days_wait_before_major_update'])) { $options['days_wait_before_major_update'] = absint($_POST['days_wait_before_major_update']); } else { $message .= __('Error: The time to wait before an automatic update must be a number ' . 'between 0 and something around ' . PHP_INT_MAX . '.', '404-solution') . "<BR/>"; } } if (array_key_exists('suggest_minscore', $_POST) && isset($_POST['suggest_minscore'])) { if (is_numeric($_POST['suggest_minscore']) && $_POST['suggest_minscore'] >= 0 && $_POST['suggest_minscore'] <= 99) { $options['suggest_minscore'] = min(max(absint($_POST['suggest_minscore']), 10), 90); } else { $message .= __('Error: Suggestion minimum score value must be a number between 1 and 99', '404-solution') . ".<BR/>"; } } if (array_key_exists('suggest_max', $_POST) && isset($_POST['suggest_max'])) { if (is_numeric($_POST['suggest_max']) && $_POST['suggest_max'] >= 1) { if ($options['suggest_max'] != absint($_POST['suggest_max'])) { $abj404logging->debugMessage(__CLASS__ . "/" . __FUNCTION__ . ": Truncating spelling cache because the max suggestions # changed from " . $options['suggest_max'] . ' to ' . absint($_POST['suggest_max'])); // the spelling cache only stores up to X entries. X is based on suggest_max // so the spelling cache has to be reset when this number changes. $abj404dao->deleteSpellingCache(); } $options['suggest_max'] = absint($_POST['suggest_max']); } else { $message .= __('Error: Maximum number of suggest value must be a number greater than or equal to 1', '404-solution') . ".<BR/>"; } } if (array_key_exists('auto_score', $_POST) && isset($_POST['auto_score'])) { if (is_numeric($_POST['auto_score']) && $_POST['auto_score'] >= 0 && $_POST['auto_score'] <= 99) { $options['auto_score'] = absint($_POST['auto_score']); } else { $message .= __('Error: Auto match score value must be a number between 0 and 99', '404-solution') . ".<BR/>"; } } if (array_key_exists('template_redirect_priority', $_POST) && isset($_POST['template_redirect_priority'])) { if (is_numeric($_POST['template_redirect_priority']) && $_POST['template_redirect_priority'] >= 0 && $_POST['template_redirect_priority'] <= 999) { $options['template_redirect_priority'] = absint($_POST['template_redirect_priority']); } else { $message .= __('Error: Template redirect priority value must be a number between 0 and 999', '404-solution') . ".<BR/>"; } } if (array_key_exists('auto_deletion', $_POST) && isset($_POST['auto_deletion'])) { if (is_numeric($_POST['auto_deletion']) && $_POST['auto_deletion'] >= 0) { $options['auto_deletion'] = absint($_POST['auto_deletion']); } else { $message .= __('Error: Auto redirect deletion value must be a number greater than or equal to zero', '404-solution') . ".<BR/>"; } } if (array_key_exists('maximum_log_disk_usage', $_POST) && isset($_POST['maximum_log_disk_usage'])) { if (is_numeric($_POST['maximum_log_disk_usage']) && absint($_POST['maximum_log_disk_usage']) > 0) { $options['maximum_log_disk_usage'] = absint($_POST['maximum_log_disk_usage']); } else { $message .= __('Error: Maximum log disk usage must be a number greater than zero', '404-solution') . ".<BR/>"; } } // these options all default to 0 if they're not specifically set to 1. $optionsList = array('remove_matches', 'debug_mode', 'suggest_cats', 'suggest_tags', 'auto_redirects', 'auto_cats', 'auto_tags', 'capture_404', 'send_error_logs', 'log_raw_ips', 'redirect_all_requests', 'update_suggest_url' ); foreach ($optionsList as $optionName) { $newVal = (array_key_exists($optionName, $_POST) && $_POST[$optionName] == "1") ? 1 : 0; // in case the suggest_cats or suggest_tags is changed. if (!array_key_exists($optionName, $options) || $options[$optionName] != $newVal) { $abj404dao->deleteSpellingCache(); } $options[$optionName] = $newVal; } // the suggest_.* options have html in them. $optionsListSuggest = array('suggest_title', 'suggest_before', 'suggest_after', 'suggest_entrybefore', 'suggest_entryafter', 'suggest_noresults'); foreach ($optionsListSuggest as $optionName) { $options[$optionName] = wp_kses_post($_POST[$optionName]); } if (array_key_exists('redirect_to_data_field_id', $_POST) && isset($_POST['redirect_to_data_field_id'])) { $options['dest404page'] = sanitize_text_field($_POST['redirect_to_data_field_id']); } if (array_key_exists('redirect_to_data_field_title', $_POST) && isset($_POST['redirect_to_data_field_title'])) { $options['dest404pageURL'] = sanitize_text_field($_POST['redirect_to_data_field_title']); if ($options['dest404page'] == ABJ404_TYPE_EXTERNAL . '|' . ABJ404_TYPE_EXTERNAL) { $options['dest404page'] = $options['dest404pageURL'] . '|' . ABJ404_TYPE_EXTERNAL; } } if (array_key_exists('admin_notification_email', $_POST) && isset($_POST['admin_notification_email'])) { $options['admin_notification_email'] = trim(wp_kses_post($_POST['admin_notification_email'])); } if (array_key_exists('folders_files_ignore', $_POST) && isset($_POST['folders_files_ignore'])) { $options['folders_files_ignore'] = wp_unslash(wp_kses_post($_POST['folders_files_ignore'])); // make the regular expressions usable. $patternsToIgnore = $f->explodeNewline($options['folders_files_ignore']); $usableFilePatterns = array(); foreach ($patternsToIgnore as $patternToIgnore) { $newPattern = '^' . preg_quote(trim($patternToIgnore), '/') . '$'; $newPattern = $f->str_replace("\*",".*", $newPattern); $usableFilePatterns[] = $newPattern; } $options['folders_files_ignore_usable'] = $usableFilePatterns; } if ( isset( $_POST['suggest_regex_exclusions'] ) ) { // 1. Sanitize the raw input using the appropriate function for multi-line text without HTML. $sanitized_exclusions = sanitize_textarea_field( wp_unslash( $_POST['suggest_regex_exclusions'] ) ); $options['suggest_regex_exclusions'] = $sanitized_exclusions; // 2. Generate the usable regex patterns *from the sanitized input*. $patternsToIgnore = $f->explodeNewline( $sanitized_exclusions ); $usableFilePatterns = array(); foreach ( $patternsToIgnore as $patternToIgnore ) { $trimmedPattern = trim( $patternToIgnore ); // Only process non-empty lines if ( ! empty( $trimmedPattern ) ) { // Escape regex special characters, then convert literal '*' into '.*' for wildcard matching. $newPattern = '^' . preg_quote( $trimmedPattern, '/' ) . '$'; // Use standard str_replace; $f->str_replace is likely unnecessary here unless it provides specific multibyte handling not needed for '\*'. $newPattern = str_replace( '\*', '.*', $newPattern ); $usableFilePatterns[] = $newPattern; } } $options['suggest_regex_exclusions_usable'] = $usableFilePatterns; } if (array_key_exists('plugin_admin_users', $_POST) && isset($_POST['plugin_admin_users'])) { $pluginAdminUsers = $_POST['plugin_admin_users']; if (is_array($pluginAdminUsers)) { $pluginAdminUsers = array_filter($pluginAdminUsers, array($f, 'removeEmptyCustom')); } $options['plugin_admin_users'] = $pluginAdminUsers; } if (is_array($options['excludePages[]'])) { $abj404logging->warn("Exclude pages settings lost."); $options['excludePages[]'] = ''; } if (array_key_exists('excludePages[]', $_POST) && isset($_POST['excludePages[]'])) { $oldExcludePages = json_decode($options['excludePages[]']); if (!is_array($_POST['excludePages[]'])) { $_POST['excludePages[]'] = array($_POST['excludePages[]']); } $options['excludePages[]'] = json_encode($_POST['excludePages[]']); $newExcludePages = json_decode($options['excludePages[]']); if ($newExcludePages !== $oldExcludePages) { // if any excluded pages changed or if the number of excluded pages changed // then the spelling cache has to be reset. $abj404dao->deleteSpellingCache(); } } else { $oldExcludePages = json_decode($options['excludePages[]']); if (null !== $oldExcludePages) { // if any excluded pages changed or if the number of excluded pages changed // then the spelling cache has to be reset. $abj404dao->deleteSpellingCache(); } $options['excludePages[]'] = null; } // save this for later to sanitize it ourselves. $excludedPages = $options['excludePages[]']; /** Sanitize all data. */ $new_options = array(); // when sanitizing data we keep the newlines (\n) because some data // is entered that way and it shouldn't allow any kind of sql // injection or any other security issues that I foresee at this point. $new_options = $this->sanitizePostData($options, true); // only some characters in the string. $excludedPages = $excludedPages == null ? '' : trim($excludedPages); $excludedPages = preg_replace('/[^\[\",\]a-zA-Z\d\|\\\\ ]/', '', $excludedPages); $new_options['excludePages[]'] = $excludedPages; $this->updateOptions($new_options); // update the permalink cache because the post types included may have changed. $permalinkCache = ABJ_404_Solution_PermalinkCache::getInstance(); $permalinkCache->updatePermalinkCache(2); $returnData['error'] = $message; if ($message == "") { $returnData['message'] = __('Options Saved Successfully!', '404-solution'); } else { $returnData['message'] = __('Some options were not saved successfully.', '404-solution') . ' ' . $message; } } echo json_encode($returnData); exit(); } /** Get the "/commentpage" and the "?query=part" of the URL. * @return string */ function getCommentPartAndQueryPartOfRequest() { $f = ABJ_404_Solution_Functions::getInstance(); $userRequest = ABJ_404_Solution_UserRequest::getInstance(); $queryParts = $f->removePageIDFromQueryString($userRequest->getQueryString()); $queryParts = ($queryParts == '') ? '' : '?' . $queryParts; $commentPart = $userRequest->getCommentPagePart(); return $commentPart . $queryParts; } /** First try a wp_redirect. Then try a redirect with JavaScript. The wp_redirect usually works, but doesn't * if some other plugin has already output any kind of data. * @param string $location * @param number $status * @param number $type only 0 for sending to a 404 page * @param string $requestedURL * @return boolean true if the user is sent to the default 404 page. */ function forceRedirect($location, $status = 302, $type = -1, $requestedURL = '') { $f = ABJ_404_Solution_Functions::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); $commentPartAndQueryPart = $this->getCommentPartAndQueryPartOfRequest(); // Sanitize and encode the base location and query parts $sanitizedLocation = esc_url_raw($location); // Ensure the base URL is safe $sanitizedQueryPart = esc_html($commentPartAndQueryPart); // Encode the query part for safe output $finalDestination = $sanitizedLocation . $sanitizedQueryPart; $previousRequest = $this->readCookieWithPreviousRqeuestShort(); $finalDestNoHome = $f->substr($finalDestination, $f->strpos($finalDestination, '://') + 3); $finalDestNoHome = $f->substr($finalDestNoHome, $f->strpos($finalDestNoHome, '/')); $locationNoHome = $f->substr($location, $f->strpos($location, '://') + 3); $locationNoHome = $f->substr($locationNoHome, $f->strpos($locationNoHome, '/')); // maybe avoid infinite redirects. if (!empty($previousRequest)) { if ($previousRequest == $finalDestNoHome && $previousRequest != $locationNoHome) { $abj404logging->infoMessage("Maybe avoided infite redirects to/from: " . $previousRequest); $finalDestination = $location; } else if ($previousRequest == $finalDestination) { $abj404logging->infoMessage("Avoided infite redirects to/from: " . $previousRequest); return false; } } // if the destination is the default 404 page then send the user there. if ($type == ABJ404_TYPE_404_DISPLAYED) { $abj404logic = ABJ_404_Solution_PluginLogic::getInstance(); $abj404logic->sendTo404Page($requestedURL, '', false); return true; } // try a normal redirect using a header. $this->setCookieWithPreviousRequest(); wp_redirect($finalDestination, $status, ABJ404_NAME); // TODO add an ajax request here that fires after 5 seconds. // upon getting the request the server will log the error. the plugin could then notify an admin. // This javascript redirect will only appear if the header redirect did not work for some reason. $c = '<script>' . 'function doRedirect() {' . "\n" . ' window.location.replace("' . $location . '");' . "\n" . '}' . "\n" . 'setTimeout(doRedirect, 1);' . "\n" . '</script>' . "\n" . 'Page moved: <a href="' . $location . $commentPartAndQueryPart . '">' . $location . '</a>'; echo $c; exit; } /** Order pages and set the page depth for child pages. * Move the children to be underneath the parents. * @param array $pages */ function orderPageResults($pages, $includeMissingParentPages = false) { $abj404logging = ABJ_404_Solution_Logging::getInstance(); $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); // sort by type then title. usort($pages, array($this, "sortByTypeThenTitle")); // run this to see if there are any child pages left. $orderedPages = $this->setDepthAndAddChildren($pages); // The pages are now sorted. We now apply the depth AND we make sure the child pages // always immediately follow the parent pages. // ------------- if ($includeMissingParentPages && (count($orderedPages) != count($pages))) { $iterations = 0; do { $idsOfMissingParentPages = $this->getMissingParentPageIDs($pages); $pageCountBefore = count($pages); $iterations = $iterations + 1; // get the parents of the unused pages. foreach ($idsOfMissingParentPages as $pageID) { $postParent = get_post($pageID); if ($postParent == null) { continue; } $parentPageSlug = $postParent->post_name; $parentPage = $abj404dao->getPublishedPagesAndPostsIDs($parentPageSlug); if (count($parentPage) != 0) { $pages[] = $parentPage[0]; } } if ($iterations > 30) { break; } $idsOfMissingParentPages = $this->getMissingParentPageIDs($pages); // loop until we can't find any more parents. This may happen if a sub-page is published // and the parent page is not published. } while ($pageCountBefore != count($pages)); // sort everything again usort($pages, array($this, "sortByTypeThenTitle")); $orderedPages = $this->setDepthAndAddChildren($pages); } // if there are child pages left over then there's an issue. it means there's a child page that was // returned but the parent for that child was not returned. so we don't have any place to display // the child page. this could be because the parent page is not "published" if (count($orderedPages) != count($pages)) { $unusedPages = array_udiff($pages, $orderedPages, array($this, 'compareByID')); $abj404logging->debugMessage("There was an issue finding the parent pages for some child pages. " . "These pages' parents may not have a 'published' status. Pages: " . wp_kses_post(json_encode($unusedPages))); } return $orderedPages; } /** For custom categories we create a Map<String, List> where the key is the name * of the taxonomy and the list holds the rows that have the category info. * @param array $categoryRows * @return array */ function getMapOfCustomCategories($categoryRows) { $customTagsEtc = array(); foreach ($categoryRows as $cat) { $taxonomy = $cat->taxonomy; if ($taxonomy == 'category') { continue; } // for custom categories we create a Map<String, List> where the key is the name // of the taxonomy and the list holds the rows that have the category info. if (!array_key_exists($taxonomy, $customTagsEtc) || $customTagsEtc[$taxonomy] == null) { $customTagsEtc[$taxonomy] = array($cat); } else { array_push($customTagsEtc[$taxonomy], $cat); } } return $customTagsEtc; } /** Returns a list of parent IDs that can't be found in the passed in pages. * @param array $pages */ function getMissingParentPageIDs($pages) { $listOfIDs = array(); $missingParentPageIDs = array(); foreach ($pages as $page) { $listOfIDs[] = $page->id; } foreach ($pages as $page) { if ($page->post_parent == 0) { continue; } if (in_array($page->post_parent, $listOfIDs)) { continue; } $missingParentPageIDs[] = $page->post_parent; } $missingParentPageIDs = array_merge( array_unique($missingParentPageIDs, SORT_REGULAR), array()); return $missingParentPageIDs; } /** Compare pages based on their ID. */ function compareByID($a, $b) { if ($a->id < $b->id) { return -1; } if ($b->id < $a->id) { return 1; } return 0; } /** Set the depth of each page and add pages under their parents by rebuilding the list * every time we iterate through it and adding the child pages at the right moment every time * the list is built. * @param array $pages * @return array */ function setDepthAndAddChildren($pages) { // find all child pages (pages that have parents). $childPages = $this->findChildPages($pages); // find all pages with no parents. $mainPages = $this->findAllMainPages($pages); $oldChildPageCount = -1; // this do{} loop is here because some child pages have children. do { // add every page to a new list, while looking for parents. $orderedPages = array(); foreach ($mainPages as $page) { // always add the main page. $orderedPages[] = $page; // if this page is the parent of any children then add the children. $removeThese = array(); foreach ($childPages as $child) { if ($child->post_parent == $page->id) { // set the page depth based on the parent's page depth. $child->depth = $page->depth + 1; $removeThese[] = $child; $orderedPages[] = $child; } } // remove any child pages that have been placed already $childPages = $this->removeUsedChildPages($childPages, $removeThese); } // the new list becomes the list that we will iterate over next time. // this prepares us for the next iteration and for child pages with a depth greater than 1. // (for child pages that have children). $mainPages = $orderedPages; // if the count has not changed then there's no point in looping again. if (count($childPages) == $oldChildPageCount) { break; } $oldChildPageCount = count($childPages); // stop the loop once there are no more children to add. } while (count($childPages) > 0); return $orderedPages; } /** * @param array $pages * @return array */ function findAllMainPages($pages) { $mainPages = array(); foreach ($pages as $page) { // if there's no parent then just add the page. if ($page->post_parent == 0) { $mainPages[] = $page; } } return $mainPages; } /** * @param array $childPages * @param array $removeThese * @return array */ function removeUsedChildPages($childPages, $removeThese) { // if any children were added then remove them from the list. foreach ($removeThese as $removeThis) { $key = array_search($removeThis, $childPages); if ($key !== false) { $childPages[$key] = null; unset($childPages[$key]); } } return $childPages; } /** Return pages that have a non-0 parent. * @param array $pages * @return array */ function findChildPages($pages) { $childPages = array(); foreach ($pages as $page) { if ($page->post_parent != 0) { $childPages[] = $page; } } return $childPages; } /** * @param array $a * @param array $b * @return int */ function sortByTypeThenTitle($a, $b) { // first sort by type $result = strcmp($a->post_type, $b->post_type); if ($result != 0) { return $result; } // then by title. return strcmp($a->post_title, $b->post_title); } /** Send an email if a notification should be displayed. Return true if an email is sent, or false otherwise. * @return string */ function emailCaptured404Notification() { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); $options = $this->getOptions(true); $captured404Count = $abj404dao->getCapturedCountForNotification(); if (!$this->shouldNotifyAboutCaptured404s($captured404Count)) { return "Not enough 404s found to send an admin notification email (" . $captured404Count . ")."; } $captured404URLSettings = admin_url() . "options-general.php?page=" . ABJ404_PP . '&subpage=abj404_captured'; $generalSettings = admin_url() . "options-general.php?page=" . ABJ404_PP . '&subpage=abj404_options'; $to = $options['admin_notification_email']; $subject = '404 Solution: Captured 404 Notification'; $body = "There are currently " . $captured404Count . " captured 404s to look at. <BR/><BR/>\n\n"; $body .= 'Visit <a href="' . $captured404URLSettings . '">' . $captured404URLSettings . '</a> to see them.<BR/><BR/>' . "\n"; $body .= 'To stop getting these emails, update the settings at <a href="' . $generalSettings . '">' . $generalSettings . '</a>, or contact the site administrator.' . "<BR/>\n"; $body .= "<BR/><BR/>\n\nSent " . date('Y/m/d h:i:s T') . "<BR/>\n" . "PHP version: " . PHP_VERSION . ", <BR/>\nPlugin version: " . ABJ404_VERSION; $headers = array('Content-Type: text/html; charset=UTF-8'); $headers[] = 'From: ' . get_option('admin_email') . '<' . get_option('admin_email') . '>'; // send the email $abj404logging->debugMessage("Sending captured 404 notification email to: " . $options['admin_notification_email']); wp_mail($to, $subject, $body, $headers); $abj404logging->debugMessage("Captured 404 notification email sent."); return "Captured 404 notification email sent to: " . trim($options['admin_notification_email']); } /** Return true if a notification should be displayed, or false otherwise. * @global type $abj404dao * @param number $captured404Count the number of captured 404s * @return boolean */ function shouldNotifyAboutCaptured404s($captured404Count) { $options = $this->getOptions(true); if (array_key_exists('admin_notification', $options) && isset($options['admin_notification']) && $options['admin_notification'] != '0') { if ($captured404Count >= $options['admin_notification']) { return true; } } return false; } /** 0|0 => "(Default 404 Page)" * 5|5 => "(Home Page)" * 10|1 => "About" * @param string $idAndType * @param string $externalLinkURL * @return string */ function getPageTitleFromIDAndType($idAndType, $externalLinkURL) { $abj404dao = ABJ_404_Solution_DataAccess::getInstance(); $abj404logging = ABJ_404_Solution_Logging::getInstance(); if ($idAndType == '') { return ''; } $meta = explode("|", $idAndType); $id = $meta[0]; $type = $meta[1]; if ($idAndType == ABJ404_TYPE_404_DISPLAYED . '|' . ABJ404_TYPE_404_DISPLAYED) { return __('(Default 404 Page)', '404-solution'); } else if ($idAndType == ABJ404_TYPE_HOME . '|' . ABJ404_TYPE_HOME) { return __('(Home Page)', '404-solution'); } else if ($type == ABJ404_TYPE_EXTERNAL) { return $externalLinkURL; } if ($type == ABJ404_TYPE_POST) { return get_the_title($id); } else if ($type == ABJ404_TYPE_CAT) { $rows = $abj404dao->getPublishedCategories($id); if (empty($rows)) { $abj404logging->debugMessage('No TERM (category) found with ID: ' . $id); return ''; } $firstRow = $rows[0]; return $firstRow->name; } else if ($type == ABJ404_TYPE_TAG) { $tag = get_tag($id); return $tag == '' ? '' : $tag->name; } $abj404logging->errorMessage("Couldn't get page title. No matching type found for type: " . $type); return ''; } }