📄 Viewing: DataAccess.php
<?php
/* Functions in this class should all reference one of the following variables or support functions that do.
* $wpdb, $_GET, $_POST, $_SERVER, $_.*
* everything $wpdb related.
* everything $_GET, $_POST, (etc) related.
* Read the database, Store to the database,
*/
class ABJ_404_Solution_DataAccess {
const UPDATE_LOGS_HITS_TABLE_HOOK = 'abj404_updateLogsHitsTableAction';
const KEY_REDIRECTS_FOR_VIEW_COUNT = 'abj404_redirects-for-view-count';
private static $instance = null;
public static function getInstance() {
if (self::$instance == null) {
self::$instance = new ABJ_404_Solution_DataAccess();
}
return self::$instance;
}
function getLatestPluginVersion() {
$abj404logging = ABJ_404_Solution_Logging::getInstance();
if (!function_exists('plugins_api')) {
require_once(ABSPATH . 'wp-admin/includes/plugin-install.php');
}
if (!function_exists('plugins_api')) {
$abj404logging->infoMessage("I couldn't find the plugins_api function to check for the latest version.");
return ABJ404_VERSION;
}
$pluginSlug = dirname(ABJ404_NAME);
// set the arguments to get latest info from repository via API ##
$args = array(
'slug' => $pluginSlug,
'fields' => array(
'version' => true,
'last_updated' => true,
)
);
/** Prepare our query */
$call_api = plugins_api('plugin_information', $args);
/** Check for Errors & Display the results */
if (is_wp_error($call_api)) {
$api_error = $call_api->get_error_message();
$abj404logging->infoMessage("There was an API issue checking the latest plugin version ("
. $api_error . ")");
return array('version' => ABJ404_VERSION, 'last_updated' => null);
}
return array('version' => $call_api->version, 'last_updated' => $call_api->last_updated);
}
/** Check wordpress.org for the latest version of this plugin. Return true if the latest version is installed,
* false otherwise.
* @return boolean
*/
function shouldEmailErrorFile() {
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$pluginInfo = $this->getLatestPluginVersion();
$latestVersion = $pluginInfo['version'];
$currentVersion = ABJ404_VERSION;
if ($latestVersion == $currentVersion) {
return true;
}
if (version_compare(ABJ404_VERSION, $latestVersion) == 1) {
$abj404logging->infoMessage("Development version: A more recent version is installed than " .
"what is available on the WordPress site (" . ABJ404_VERSION . " / " .
$latestVersion . ").");
return true;
}
$currentArray = explode(".", $currentVersion);
$latestArray = explode(".", $latestVersion);
// verify that the version numbers were parsed correctly.
if (count($currentArray) != 3 || count($latestArray) != 3) {
$abj404logging->errorMessage("Issue parsing version numbers. " .
$currentVersion . ' / ' . $latestVersion);
} else if ($currentArray[0] == $latestArray[0] && $currentArray[1] == $latestArray[1]) {
// get the difference in the version numbers.
$difference = absint(absint($latestArray[2]) - absint($currentArray[2]));
// if the major versions mostly match then send the error file.
if ($difference <= 1) {
return true;
}
}
return (ABJ404_VERSION == $pluginInfo['version']);
}
/**
* @global type $wpdb
*/
function importDataFromPluginRedirectioner() {
global $wpdb;
$f = ABJ_404_Solution_Functions::getInstance();
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$oldTable = $wpdb->prefix . 'wbz404_redirects';
$newTable = $this->doTableNameReplacements('{wp_abj404_redirects}');
// wp_wbz404_redirects -- old table
// wp_abj404_redirects -- new table
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/importDataFromPluginRedirectioner.sql");
$query = $f->str_replace('{OLD_TABLE}', $oldTable, $query);
$query = $f->str_replace('{NEW_TABLE}', $newTable, $query);
$result = $this->queryAndGetResults($query);
$abj404logging->infoMessage("Importing redirectioner SQL result: " .
wp_kses_post(json_encode($result)));
return $result;
}
function doTableNameReplacements($query) {
global $wpdb;
$f = ABJ_404_Solution_Functions::getInstance();
$replacements = array();
foreach ($wpdb->tables as $tableName) {
$replacements['{wp_' . $tableName . '}'] = $wpdb->prefix . $tableName;
}
$replacements['{wp_users}'] = $wpdb->users;
$replacements['{wp_prefix}'] = $wpdb->prefix;
$replacements['{wp_prefix_lower}'] = $f->strtolower($wpdb->prefix);
// wp database table replacements
$query = $f->str_replace(array_keys($replacements), array_values($replacements), $query);
// custom table replacements.
// for some strings (/404solution-site/%BA%D0%25/) the mb_ereg_replace doesn't work.
$fpreg = ABJ_404_Solution_FunctionsPreg::getInstance();
$query = $fpreg->regexReplace('[{]wp_abj404_(.*?)[}]',
strtolower($wpdb->prefix) . "abj404_\\1", $query);
return $query;
}
/** Returns the create table statement.
* @param string $tableName */
function getCreateTableDDL($tableName) {
$query = "show create table " . $tableName;
$result = $this->queryAndGetResults($query);
$rows = $result['rows'];
$row1 = array_values($rows[0]);
$existingTableSQL = $row1[1];
return $existingTableSQL;
}
/** Return the results of the query in a variable.
* @param string $query
* @param array $options
* @return array
*/
function queryAndGetResults($query, $options = array()) {
global $wpdb;
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$f = ABJ_404_Solution_Functions::getInstance();
$ignoreErrorStrings = array();
$options = array_merge(array('log_errors' => true,
'log_too_slow' => true, 'ignore_errors' => array(),
'query_params' => array()),
$options);
$ignoreErrorStrings = $options['ignore_errors'];
$queryParameters = $options['query_params'];
$query = $this->doTableNameReplacements($query);
if (!empty($queryParameters)) {
$query = $wpdb->prepare($query, $queryParameters);
}
$timer = new ABJ_404_Solution_Timer();
$result = array();
$result['rows'] = $wpdb->get_results($query, ARRAY_A);
$result['elapsed_time'] = $timer->stop();
$result['last_error'] = $wpdb->last_error;
$result['last_result'] = $wpdb->last_result;
$result['rows_affected'] = $wpdb->rows_affected;
if ($wpdb->dbh != null) {
try {
$result['rows_affected'] = $wpdb->rows_affected;
} catch (Exception $ex) {
// don't care. we did our best.
}
}
$result['insert_id'] = $wpdb->insert_id;
if (!is_array($result['rows'])) {
$abj404logging->errorMessage("Query result is not an array. Query: " . $query,
new Exception("Query result is not an array."));
}
if ($options['log_errors'] && $result['last_error'] != '') {
if ($f->strpos($result['last_error'],
" is marked as crashed ") !== false) {
$this->repairTable($result['last_error']);
}
if ($f->strpos($result['last_error'],
"ALTER TABLE causes auto_increment resequencing") !== false &&
$f->strpos($result['last_error'], "resulting in duplicate entry") !== false) {
$this->repairDuplicateIDs($result['last_error'], $query);
}
// ignore any specific errors.
$reportError = true;
foreach ($ignoreErrorStrings as $ignoreThis) {
if (strpos($result['last_error'], $ignoreThis) !== false) {
$reportError = false;
break;
}
}
if ($reportError) {
$stripped_query = 'n/a';
if ($f->strpos($result['last_error'],
"WordPress database error: Could not perform query because it contains invalid data") !== false) {
$stripped_query = $this->get_stripped_query_result($query);
}
$extraDataQuery = "select @@max_join_size as max_join_size, " .
"@@sql_big_selects as sql_big_selects, " .
"@@character_set_database as character_set_database";
$someMySQLVariables = $wpdb->get_results($extraDataQuery, ARRAY_A);
$variables = print_r($someMySQLVariables, true);
if (is_wp_error($query) && $query instanceof WP_Error) {
/** @var WP_Error $query */
$query = "((" . ABJ_404_Solution_WPUtils::stringify_wp_error($query) . "))";
}
if (is_wp_error($variables) && $variables instanceof WP_Error) {
/** @var WP_Error $variables */
$variables = "((" . ABJ_404_Solution_WPUtils::stringify_wp_error($variables) . "))";
}
if (is_wp_error($stripped_query) && $stripped_query instanceof WP_Error) {
/** @var WP_Error $stripped_query */
$stripped_query = "((" . ABJ_404_Solution_WPUtils::stringify_wp_error($stripped_query) . "))";
}
$abj404logging->errorMessage("Ugh. SQL query error: " . $result['last_error'] .
", SQL: " . $query .
", Execution time: " . round($timer->getElapsedTime(), 2) .
", DB ver: " . $wpdb->db_version() .
", Variables: " . $variables .
", stripped_query: " . $stripped_query);
}
} else {
if ($options['log_too_slow'] && $timer->getElapsedTime() > 5) {
$abj404logging->debugMessage("Slow query (" . round($timer->getElapsedTime(), 2) . " seconds): " .
$query);
}
}
return $result;
}
/** Try to call strip_invalid_text_from_query and return the result.
* @param string $query
* @return NULL|string|WP_Error
*/
function get_stripped_query_result($query) {
try {
if (!class_exists('wpdb')) {
return null;
}
if (!method_exists('wpdb', 'strip_invalid_text_from_query')) {
return null;
}
$filename = ABJ404_PATH . 'includes/php/wordpress/WPDBExtension.php';
if (!file_exists($filename)) {
return null;
}
require_once $filename;
$my_custom_db = null;
if (class_exists('ABJ_404_Solution_WPDBExtension_PHP7')) {
$my_custom_db = new ABJ_404_Solution_WPDBExtension_PHP7(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);
} else if (class_exists('ABJ_404_Solution_WPDBExtension_PHP5')) {
$my_custom_db = new ABJ_404_Solution_WPDBExtension_PHP5(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);
}
if ($my_custom_db == null) {
return null;
}
$result = $my_custom_db->public_strip_invalid_text_from_query($query);
// Convert WP_Error to string
if (is_wp_error($result)) {
return 'WP_Error: ' . $result->get_error_message();
}
return $result;
} catch (Exception $e) {
// oh well.
return null;
}
return null;
}
function repairTable($errorMessage) {
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$f = ABJ_404_Solution_Functions::getInstance();
$re = "Table '(.*\/)?(.+)' is marked as crashed and ";
$matches = array();
$f->regexMatch($re, $errorMessage, $matches);
if (!empty($matches) && count($matches) > 2 && $f->strlen($matches[2]) > 0) {
$tableToRepair = $matches[2];
if ($f->strpos($tableToRepair, "abj404") !== false) {
$query = "repair table " . $tableToRepair;
$result = $this->queryAndGetResults($query, array('log_errors' => false));
$abj404logging->infoMessage("Attempted to repair table " . $tableToRepair . ". Result: " .
json_encode($result));
// track how many times we've tried to repair something.
// only for the certain tables. Exclude the redirects table because people
// may have spent time creating entries there. Other tables are generated
// automatically.
if (strpos($tableToRepair, 'redirects') === false) {
$abj404logic = ABJ_404_Solution_PluginLogic::getInstance();
$options = $abj404logic->getOptions();
if (!array_key_exists('repaired_count', $options)) {
$options['repaired_count'] = 0;
}
$options['repaired_count'] = intval($options['repaired_count']) + 1;
$abj404logic->updateOptions($options);
if (intval($options['repaired_count']) > 3 &&
intval($options['repaired_count']) < 7) {
$upgradesEtc = ABJ_404_Solution_DatabaseUpgradesEtc::getInstance();
$this->queryAndGetResults('drop table ' . $tableToRepair);
$upgradesEtc->createDatabaseTables(false);
}
}
} else {
// tell someone the table $tableToRepair is broken.
$abj404logging->warn("The table " . $tableToRepair . " needs to be " .
"repaired with something like: repair table " . $tableToRepair);
}
}
}
function repairDuplicateIDs($errorMessage, $sqlThatWasRun) {
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$f = ABJ_404_Solution_Functions::getInstance();
$reForID = 'resulting in duplicate entry \'(.+)\' for key';
$reForTableName = "ALTER TABLE (.+) ADD ";
$matchesForID = null;
$matchesForTableName = null;
$f->regexMatch($reForID, $errorMessage, $matchesForID);
$f->regexMatch($reForTableName, $sqlThatWasRun, $matchesForTableName);
if ($matchesForID != null && $f->strlen($matchesForID[1]) > 0 &&
$matchesForTableName != null && $f->strlen($matchesForTableName[1]) > 0) {
$idWithDuplicate = $matchesForID[1];
$tableName = $matchesForTableName[1];
if ($idWithDuplicate == 1) {
$idWithDuplicate = 0;
}
$result = $this->queryAndGetResults("delete from " . $tableName . " where id = " .
$idWithDuplicate, array('log_errors' => false));
$abj404logging->infoMessage("Attempted to fix a duplicate entry issue. Table: " .
$tableName . ", Result: " . json_encode($result));
}
}
function executeAsTransaction($statementArray) {
$logger = ABJ_404_Solution_Logging::getInstance();
$exception = null;
$allIsWell = true;
global $wpdb;
try {
$wpdb->query('START TRANSACTION');
foreach ($statementArray as $statement) {
$wpdb->query($statement);
if ($wpdb->last_error != null) {
$allIsWell = false;
$logger->errorMessage("Error executing SQL transaction: " . $wpdb->last_error);
$logger->errorMessage("SQL causing the transaction error: " . $statement);
break;
}
}
} catch (Exception $ex) {
$allIsWell = false;
$exception = $ex;
}
if ($allIsWell && $exception == null) {
$wpdb->query('commit');
} else {
$wpdb->query('rollback');
}
if ($exception != null) {
throw $exception;
}
}
function getOldSlug($post_id) {
$f = ABJ_404_Solution_Functions::getInstance();
// we order by meta_id desc so that the first row will have the most recent value.
$query = "select meta_value from {wp_postmeta} \nwhere post_id = {post_id} " .
" and meta_key = '_wp_old_slug' \n" .
" order by meta_id desc";
$query = $f->str_replace('{post_id}', $post_id, $query);
$results = $this->queryAndGetResults($query);
$rows = $results['rows'];
if ($rows == null || empty($rows)) {
return null;
}
$row = $rows[0];
return $row['meta_value'];
}
function truncatePermalinkCacheTable() {
global $wpdb;
$query = "truncate table {wp_abj404_permalink_cache}";
$this->queryAndGetResults($query);
}
function removeFromPermalinkCache($post_id) {
global $wpdb;
$query = "delete from {wp_abj404_permalink_cache} where id = %d";
$this->queryAndGetResults($query, array('query_params' => array($post_id)));
}
function getIDsNeededForPermalinkCache() {
$abj404logic = ABJ_404_Solution_PluginLogic::getInstance();
$f = ABJ_404_Solution_Functions::getInstance();
// get the valid post types
$options = $abj404logic->getOptions();
$postTypes = $f->explodeNewline($options['recognized_post_types']);
$recognizedPostTypes = '';
foreach ($postTypes as $postType) {
$recognizedPostTypes .= "'" . trim($f->strtolower($postType)) . "', ";
}
$recognizedPostTypes = rtrim($recognizedPostTypes, ", ");
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getIDsNeededForPermalinkCache.sql");
$query = $f->str_replace('{recognizedPostTypes}', $recognizedPostTypes, $query);
$results = $this->queryAndGetResults($query);
return $results['rows'];
}
function getPermalinkFromCache($id) {
$query = "select url from {wp_abj404_permalink_cache} where id = " . $id;
$results = $this->queryAndGetResults($query);
$rows = $results['rows'];
if (empty($rows)) {
return null;
}
$row1 = $rows[0];
return $row1['url'];
}
function getPermalinkEtcFromCache($id) {
$query = "select * from {wp_abj404_permalink_cache} where id = " . $id;
$results = $this->queryAndGetResults($query);
$rows = $results['rows'];
if (empty($rows)) {
return null;
}
return $rows[0];
}
function correctDuplicateLookupValues() {
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/correctLookupTableIssue.sql");
$this->queryAndGetResults($query);
}
function storeSpellingPermalinksToCache($requestedURLRaw, $returnValue) {
$f = ABJ_404_Solution_Functions::getInstance();
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/insertSpellingCache.sql");
$query = $f->str_replace('{url}', esc_sql($requestedURLRaw), $query);
$query = $f->str_replace('{matchdata}', esc_sql(json_encode($returnValue)), $query);
$this->queryAndGetResults($query);
}
function deleteSpellingCache() {
$query = "truncate table {wp_abj404_spelling_cache}";
$this->queryAndGetResults($query);
}
function getSpellingPermalinksFromCache($requestedURLRaw) {
$query = "select * from {wp_abj404_spelling_cache} where url = '" . esc_sql($requestedURLRaw) . "'";
$results = $this->queryAndGetResults($query);
$rows = $results['rows'];
if (empty($rows)) {
return array();
}
$row = $rows[0];
$json = $row['matchdata'];
$returnValue = json_decode($json);
return $returnValue;
}
function getTableEngines() {
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/selectTableEngines.sql");
$results = $this->queryAndGetResults($query);
return $results;
}
function isMyISAMSupported() {
$abj404dao = ABJ_404_Solution_DataAccess::getInstance();
$supportResults = $abj404dao->queryAndGetResults("SELECT ENGINE, SUPPORT " .
"FROM information_schema.ENGINES WHERE lower(ENGINE) = 'myisam'",
array('log_errors' => false));
if (!empty($supportResults) && !empty($supportResults['rows'])) {
$rows = $supportResults['rows'];
if (!empty($rows)) {
$row = $rows[0];
$supportValue = array_key_exists('support', $row) ? $row['support'] :
(array_key_exists('SUPPORT', $row) ? $row['SUPPORT'] : "nope");
return strtolower($supportValue) == 'yes';
}
}
return false;
}
/** Insert data into the database.
* Create my own insert statement because wordpress messes it up when the field
* length is too long. this also returns the correct value for the last_query.
* @global type $wpdb
* @param string $tableName
* @param array $dataToInsert
* @return array
*/
function insertAndGetResults($tableName, $dataToInsert) {
$tableName = $this->doTableNameReplacements($tableName);
$columns = array();
$placeholders = array();
$values = array();
foreach ($dataToInsert as $column => $value) {
$columns[] = '`' . $column . '`';
if ($value === null) {
$placeholders[] = 'NULL';
// Do not add null values to $values array
} else {
$currentDataType = gettype($value);
if ($currentDataType == 'integer' || $currentDataType == 'double') {
$placeholders[] = '%d';
$values[] = $value;
} elseif ($currentDataType == 'boolean') {
$placeholders[] = '%d';
$values[] = $value ? 1 : 0;
} else {
$placeholders[] = '%s';
$values[] = (string)$value;
}
}
}
$sql = 'INSERT INTO `' . $tableName . '` (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $placeholders) . ')';
return $this->queryAndGetResults($sql, ['query_params' => $values]);
}
/**
* @global type $wpdb
* @return int the total number of redirects that have been captured.
*/
function getCapturedCount() {
global $wpdb;
$query = "select count(id) from {wp_abj404_redirects} where status = " . ABJ404_STATUS_CAPTURED;
$query = $this->doTableNameReplacements($query);
$captured = $wpdb->get_col($query, 0);
if (empty($captured)) {
$captured[0] = 0;
}
return intval($captured[0]);
}
/** Get all of the post types from the wp_posts table.
* @return array An array of post type names. */
function getAllPostTypes() {
$query = "SELECT DISTINCT post_type FROM {wp_posts} order by post_type";
$results = $this->queryAndGetResults($query);
$rows = $results['rows'];
$postType = array();
foreach ($rows as $row) {
array_push($postType, $row['post_type']);
}
return $postType;
}
/** Get the approximate number of bytes used by the logs table.
* @global type $wpdb
* @return int
*/
function getLogDiskUsage() {
global $wpdb;
$abj404logging = ABJ_404_Solution_Logging::getInstance();
// we have to analyze the table first for the query to be valid.
$result = $this->queryAndGetResults("OPTIMIZE TABLE {wp_abj404_logsv2}");
if ($result['last_error'] != '') {
$abj404logging->errorMessage("Error: " . esc_html($result['last_error']));
return -1;
}
$query = 'SELECT (data_length+index_length) tablesize FROM information_schema.tables ' .
'WHERE table_name=\'{wp_abj404_logsv2}\'';
$query = $this->doTableNameReplacements($query);
$size = $wpdb->get_col($query, 0);
if (empty($size)) {
$size[0] = 0;
}
return intval($size[0]);
}
/**
* @global type $wpdb
* @param array $types specified types such as ABJ404_STATUS_MANUAL, ABJ404_STATUS_AUTO, ABJ404_STATUS_CAPTURED, ABJ404_STATUS_IGNORED.
* @param int $trashed 1 to only include disabled redirects. 0 to only include enabled redirects.
* @return int the number of records matching the specified types.
*/
function getRecordCount($types = array(), $trashed = 0) {
$recordCount = 0;
if (count($types) >= 1) {
$query = "select count(id) as count from {wp_abj404_redirects} where 1 and (status in (";
$filteredTypes = array();
foreach ($types as $type) {
array_push($filteredTypes, esc_sql($type));
}
$typesForSQL = implode(", ", $filteredTypes);
$query .= $typesForSQL . "))";
$query .= " and disabled = " . esc_sql($trashed);
$result = $this->queryAndGetResults($query);
$rows = $result['rows'];
if (!empty($rows)) {
$row = $rows[0];
$recordCount = $row['count'];
}
}
return $recordCount;
}
/**
* @global type $wpdb
* @param int $logID only return results that correspond to the URL of this $logID. Use 0 to get all records.
* @return int the number of records found.
*/
function getLogsCount($logID) {
global $wpdb;
$f = ABJ_404_Solution_Functions::getInstance();
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getLogsCount.sql");
$query = $this->doTableNameReplacements($query);
if ($logID != 0) {
$query = $f->str_replace('/* {SPECIFIC_ID}', '', $query);
$query = $f->str_replace('{logID}', $logID, $query);
}
$row = $wpdb->get_row($query, ARRAY_N);
if (empty($row)) {
$row[0] = 0;
}
$records = $row[0];
return intval($records);
}
/**
* @global type $wpdb
* @return array
*/
function getRedirectsAll() {
global $wpdb;
$query = "select id, url from {wp_abj404_redirects} order by url";
$query = $this->doTableNameReplacements($query);
$rows = $wpdb->get_results($query, ARRAY_A);
return $rows;
}
function doRedirectsExport($tempFile) {
global $wpdb;
if (file_exists($tempFile)) {
ABJ_404_Solution_Functions::safeUnlink($tempFile);
}
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ .
"/sql/getRedirectsExport.sql");
$query = $this->doTableNameReplacements($query);
// we use mysqli here instead of the normal wordpress get_results in order
// to get one row at a time, so we don't run out of memory by trying to store
// everything in memory all at once.
$result = mysqli_query($wpdb->dbh, $query);
if ($result) {
// write the header
$line = 'from_url,status,type,to_url,wp_type';
file_put_contents($tempFile, $line . "\n", FILE_APPEND);
while (($row = mysqli_fetch_array($result, MYSQLI_ASSOC))) {
$line = $row['from_url'] . ',' .
$row['status'] . ',' .
$row['type'] . ',' .
$row['to_url'] . ', ' .
$row['type_wp'];
file_put_contents($tempFile, $line . "\n", FILE_APPEND);
}
mysqli_free_result($result);
}
}
/** Only return redirects that have a log entry.
* @global type $wpdb
* @global type $abj404dao
* @return array
*/
function getRedirectsWithLogs() {
global $wpdb;
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getRedirectsWithLogs.sql");
$query = $this->doTableNameReplacements($query);
$rows = $wpdb->get_results($query, ARRAY_A);
return $rows;
}
/**
* @global type $wpdb
* @return array
*/
function getRedirectsWithRegEx() {
$query = "select \n {wp_abj404_redirects}.id,\n {wp_abj404_redirects}.url,\n {wp_abj404_redirects}.status,\n"
. " {wp_abj404_redirects}.type,\n {wp_abj404_redirects}.final_dest,\n {wp_abj404_redirects}.code,\n"
. " {wp_abj404_redirects}.timestamp,\n {wp_posts}.id as wp_post_id\n ";
$query .= "from {wp_abj404_redirects}\n " .
" LEFT OUTER JOIN {wp_posts} \n " .
" on {wp_abj404_redirects}.final_dest = {wp_posts}.id \n ";
$query .= "where status in (" . ABJ404_STATUS_REGEX . ") \n " .
" and disabled = 0";
$results = $this->queryAndGetResults($query);
return $results['rows'];
}
/** Returns the redirects that are in place.
* @global type $wpdb
* @param string $sub either "redirects" or "captured".
* @param array $tableOptions filter, order by, paged, perpage etc.
* @return array rows from the redirects table.
*/
function getRedirectsForView($sub, $tableOptions) {
$logger = ABJ_404_Solution_Logging::getInstance();
// for normal page views we limit the rows returned based on user preferences for paginaiton.
$limitStart = ( absint(sanitize_text_field($tableOptions['paged']) - 1)) * absint(sanitize_text_field($tableOptions['perpage']));
$limitEnd = absint(sanitize_text_field($tableOptions['perpage']));
$queryAllRowsAtOnce = ($tableOptions['perpage'] > 5000) || ($tableOptions['orderby'] == 'logshits')
|| ($tableOptions['orderby'] == 'last_used');
$query = $this->getRedirectsForViewQuery($sub, $tableOptions, $queryAllRowsAtOnce,
$limitStart, $limitEnd, false);
// if this takes too long then rewrite how specific URLs are linked to from the redirects table.
// they can use a different ID - not the ID from the logs table.
$ignoreErrorsOoptions = array('log_errors' => false);
$this->queryAndGetResults("set session max_join_size = 18446744073709551615",
$ignoreErrorsOoptions);
$this->queryAndGetResults("set session sql_big_selects = 1", $ignoreErrorsOoptions);
$results = $this->queryAndGetResults($query);
$rows = $results['rows'];
$foundRowsBeforeLogsData = count($rows);
// populate the logs data if we need to
if (!$queryAllRowsAtOnce) {
$rows = $this->populateLogsData($rows);
}
$logger->debugMessage("Found " . $foundRowsBeforeLogsData .
" rows to display before log data and " . count($rows) .
" rows to display after log data for page: ". $sub);
return $rows;
}
function getRedirectsForViewCount($sub, $tableOptions) {
if (array_key_exists(self::KEY_REDIRECTS_FOR_VIEW_COUNT, $_REQUEST) &&
isset($_REQUEST[self::KEY_REDIRECTS_FOR_VIEW_COUNT])) {
return $_REQUEST[self::KEY_REDIRECTS_FOR_VIEW_COUNT];
}
$query = $this->getRedirectsForViewQuery($sub, $tableOptions, false, 0, PHP_INT_MAX,
true);
$ignoreErrorsOoptions = array('log_errors' => false);
$this->queryAndGetResults("set session max_join_size = 18446744073709551615",
$ignoreErrorsOoptions);
$this->queryAndGetResults("set session sql_big_selects = 1", $ignoreErrorsOoptions);
$results = $this->queryAndGetResults($query);
if ($results['last_error'] != null && trim($results['last_error']) != '') {
throw new \Exception("Error getting redirect count: " . esc_html($results['last_error']));
}
$rows = $results['rows'];
if (empty($rows)) {
return -1;
}
$row = $rows[0];
$_REQUEST[self::KEY_REDIRECTS_FOR_VIEW_COUNT] = $row['count'];
return $row['count'];
}
function getRedirectsForViewQuery($sub, $tableOptions, $queryAllRowsAtOnce,
$limitStart, $limitEnd, $selectCountOnly) {
$abj404logging = ABJ_404_Solution_Logging::getInstance();
global $abj404_redirect_types;
global $abj404_captured_types;
$f = ABJ_404_Solution_Functions::getInstance();
$logsTableColumns = '';
$logsTableJoin = '';
$statusTypes = '';
$trashValue = '';
$selectCountReplacement = '/* selecting data as usual */';
/* if we only want the count(*) then comment out everything else. */
if ($selectCountOnly) {
$selectCountReplacement = "\n /*+ SET_VAR(max_join_size=18446744073709551615) */\n" .
"count(*) as count\n /* only selecting for count";
}
// if we're showing all rows include all of the log data in the query already. this makes the query very slow.
// this should be replaced by the dynamic loading of log data using ajax queries as the page is viewed.
if ($queryAllRowsAtOnce) {
$logsTableColumns = "logstable.logshits as logshits, \n" .
"logstable.logsid, \n" .
"logstable.last_used, \n";
} else {
$logsTableColumns = "null as logshits, \n null as logsid, \n null as last_used, \n";
}
if ($queryAllRowsAtOnce) {
// create a temp table and use that instead of a subselect to avoid the sql error
// "The SELECT would examine more than MAX_JOIN_SIZE rows"
$this->maybeUpdateRedirectsForViewHitsTable();
$logsTableJoin = " LEFT OUTER JOIN {wp_abj404_logs_hits} logstable \n " .
" on binary wp_abj404_redirects.url = binary logstable.requested_url \n ";
}
if ($tableOptions['filter'] == 0 || $tableOptions['filter'] == ABJ404_TRASH_FILTER) {
if ($sub == 'abj404_redirects') {
$statusTypes = implode(", ", $abj404_redirect_types);
} else if ($sub == 'abj404_captured') {
$statusTypes = implode(", ", $abj404_captured_types);
} else {
$abj404logging->errorMessage("Unrecognized sub type: " . esc_html($sub));
}
} else if ($tableOptions['filter'] == ABJ404_STATUS_MANUAL) {
$statusTypes = implode(", ", array(ABJ404_STATUS_MANUAL, ABJ404_STATUS_REGEX));
} else {
$statusTypes = $tableOptions['filter'];
}
$statusTypes = preg_replace('/[^\d, ]/', '', trim($statusTypes));
if ($tableOptions['filter'] == ABJ404_TRASH_FILTER) {
$trashValue = 1;
} else {
$trashValue = 0;
}
/* only try to order by if we're actually selecting data and not only
* counting the number of rows. */
$orderByString = '';
if (!$selectCountOnly) {
$orderBy = $f->strtolower($tableOptions['orderby']);
if ($orderBy == "final_dest") {
// TODO change the final dest type to an integer and store external URLs somewhere else.
$orderBy = "case when post_title is null then 1 else 0 end asc, post_title";
} else {
// only allow letters and the underscore in the orderby string.
$orderBy = preg_replace('/[^a-zA-Z_]/', '', trim($orderBy));
}
$order = preg_replace('/[^a-zA-Z_]/', '', trim($tableOptions['order']));
$orderByString = "order by published_status asc, " . $orderBy . " " . $order;
}
$searchFilterForRedirectsExists = "no redirects fiter text found";
$searchFilterForCapturedExists = "no captured 404s filter text found";
$filterText = '';
if ($tableOptions['filterText'] != '') {
if ($sub == 'abj404_redirects') {
$searchFilterForRedirectsExists = ' filtering on text: ' . esc_sql($tableOptions['filterText'] . ' */');
} else if ($sub == 'abj404_captured') {
$searchFilterForCapturedExists = ' filtering on text: ' . esc_sql($tableOptions['filterText'] . ' */');
} else {
throw new Exception("Unrecognized page for filter text request.");
}
}
$filterText = preg_replace('/[^a-zA-Z\d_=\/\-\(\)\*\.]/', '', $tableOptions['filterText']);
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getRedirectsForView.sql");
$query = $f->str_replace('{selecting-for-count-true-false}', $selectCountReplacement, $query);
$query = $f->str_replace('{statusTypes}', $statusTypes, $query);
$query = $f->str_replace('{orderByString}', $orderByString, $query);
$query = $f->str_replace('{limitStart}', $limitStart, $query);
$query = $f->str_replace('{limitEnd}', $limitEnd, $query);
$query = $f->str_replace('{searchFilterForRedirectsExists}', $searchFilterForRedirectsExists, $query);
$query = $f->str_replace('{searchFilterForCapturedExists}', $searchFilterForCapturedExists, $query);
$query = $f->str_replace('{filterText}', $filterText, $query);
$query = $f->str_replace('{logsTableColumns}', $logsTableColumns, $query);
$query = $f->str_replace('{logsTableJoin}', $logsTableJoin, $query);
$query = $f->str_replace('{trashValue}', $trashValue, $query);
$query = $this->doTableNameReplacements($query);
if (array_key_exists('translations', $tableOptions)) {
$keys = array_keys($tableOptions['translations']);
$values = array_values($tableOptions['translations']);
$query = $f->str_replace($keys, $values, $query);
}
$query = $f->doNormalReplacements($query);
return $query;
}
function getExtraDataToPermalinkSuggestions($postIDs) {
$f = ABJ_404_Solution_Functions::getInstance();
$postIDJoined = implode(", ", $postIDs);
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getAdditionalPostData.sql");
$query = $f->str_replace('{IDS_TO_INCLUDE}', $postIDJoined, $query);
$query = $this->doTableNameReplacements($query);
$query = $f->doNormalReplacements($query);
$results = $this->queryAndGetResults($query);
return $results['rows'];
}
/**
* Prepare a WordPress SQL query with placeholders and an associative data array.
*
* @param string $query The SQL query string with {placeholder} style placeholders.
* @param array $data An associative array with keys matching the placeholders in the query.
* @return string The fully prepared SQL query.
*/
function prepare_query_wp($query, $data) {
global $wpdb;
list($prepared_query, $ordered_values) = $this->prepare_query($query, $data);
return $wpdb->prepare($prepared_query, $ordered_values);
}
/**
* Prepare a SQL query with placeholders and an associative data array.
*
* @param string $query The SQL query string with {placeholder} style placeholders.
* @param array $data An associative array with keys matching the placeholders in the query.
* @return array Returns an array containing two elements: the prepared query string with %s or %d placeholders, and an ordered array of values for those placeholders.
*/
function prepare_query($query, $data) {
$ordered_values = [];
$prepared_query = preg_replace_callback('/\{(\w+)\}/', function($matches) use ($data, &$ordered_values) {
$key = $matches[1];
if (!isset($data[$key])) {
// Placeholder key not found in data array, ignore and continue
return $matches[0];
}
$value = $data[$key];
// Append the value to the ordered values array
$ordered_values[] = $value;
// Determine the placeholder type
$placeholder_type = is_int($value) ? '%d' : '%s';
return $placeholder_type;
}, $query);
return [$prepared_query, $ordered_values];
}
function maybeUpdateRedirectsForViewHitsTable() {
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$query = "select table_comment from information_schema.tables where table_name = '{wp_abj404_logs_hits}'";
$results = $this->queryAndGetResults($query);
// if the table already exists then just schedule it to be updated later.
if ($results['rows'] != null && !empty($results['rows'])) {
// the table exists. let's find out how long it took to create the table last time.
$rows = $results['rows'];
$row1 = $rows[0];
// change all to lower
$row1 = array_change_key_case($row1);
$timeToCreatePreviously = 999999;
if (floatval($row1['table_comment']) > 0) {
$timeToCreatePreviously = floatval($row1['table_comment']);
}
if ($timeToCreatePreviously < 1) {
$abj404logging->debugMessage(__FUNCTION__ . " creating immediately because create time was " .
$timeToCreatePreviously . " seconds.");
// it took less than 5 seconds less time so let's just do it again right now.
$this->createRedirectsForViewHitsTable();
} else {
$abj404logging->debugMessage(__FUNCTION__ . " creating later because create time was " .
$timeToCreatePreviously . " seconds.");
// it takes too long to make the user wait. we'll update it in the background.
wp_schedule_single_event(1, self::UPDATE_LOGS_HITS_TABLE_HOOK);
}
} else {
$abj404logging->debugMessage(__FUNCTION__ . " creating now because the table doesn't exist.");
// if the table does not exist then create it right away.
$this->createRedirectsForViewHitsTable();
}
}
function createRedirectsForViewHitsTable() {
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$finalDestTable = $this->doTableNameReplacements("{wp_abj404_logs_hits}");
$tempDestTable = $this->doTableNameReplacements("{wp_abj404_logs_hits}_temp");
$ttSelectQuery = ABJ_404_Solution_Functions::readFileContents(__DIR__ .
"/sql/getRedirectsForViewTempTable.sql");
$ttSelectQuery = $this->doTableNameReplacements($ttSelectQuery);
// create a temp table
$this->queryAndGetResults("drop table if exists " . $tempDestTable);
$createTempTableQuery = ABJ_404_Solution_Functions::readFileContents(__DIR__ .
"/sql/createLogsHitsTempTable.sql");
$createTempTableQuery = $this->doTableNameReplacements($createTempTableQuery);
$this->queryAndGetResults($createTempTableQuery);
$this->queryAndGetResults("truncate table " . $tempDestTable);
// insert the data into the temp table (this may take time).
$ttInsertQuery = "insert into " . $tempDestTable . " (requested_url, logsid, " .
"last_used, logshits) \n " . $ttSelectQuery;
$results = $this->queryAndGetResults($ttInsertQuery, array('log_too_slow' => false));
$elapsedTime = $results['elapsed_time'];
$addComment = "ALTER TABLE " . $tempDestTable . " COMMENT '" . $results['elapsed_time'] . "'";
$this->queryAndGetResults($addComment);
// drop the old hits table and rename the temp table to the hits table as a transaction
$statements = array(
"drop table if exists " . $finalDestTable,
"rename table " . $tempDestTable . ' to ' . $finalDestTable
);
$this->executeAsTransaction($statements);
$abj404logging->debugMessage(__FUNCTION__ . " refreshed " . $finalDestTable . " in " . $elapsedTime .
" seconds.");
}
/**
* @param array $rows
*/
function populateLogsData($rows) {
// note: according to https://stackoverflow.com/a/10121508 we should not used a pointer here to modify
// the data that we're currently looping through.
foreach ($rows as &$row) {
if ($row['url'] != null && !empty($row['url'])) {
$logsData = $this->getLogsIDandURL($row['url']);
if (!empty($logsData)) {
$row['logsid'] = $logsData[0]['logsid'];
$row['logshits'] = $logsData[0]['logshits'];
$row['last_used'] = $logsData[0]['last_used'];
}
}
}
return $rows;
}
/**
* @global type $wpdb
* @param string $specificURL
* @return array
*/
function getLogsIDandURL($specificURL = '') {
$f = ABJ_404_Solution_Functions::getInstance();
$whereClause = '';
if ($specificURL != '') {
$whereClause = "where requested_url = '" . $specificURL . "'";
}
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getLogsIDandURL.sql");
$query = $f->str_replace('{where_clause_here}', $whereClause, $query);
$results = $this->queryAndGetResults($query);
$rows = $results['rows'];
return $rows;
}
/**
* @param string $specificURL
* @param string $limitResults
* @return array
*/
function getLogsIDandURLLike($specificURL, $limitResults) {
$f = ABJ_404_Solution_Functions::getInstance();
$whereClause = '';
if ($specificURL != '') {
$whereClause = "where lower(requested_url) like lower('" . $specificURL . "')\n";
$whereClause .= "and min_log_id = true";
}
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getLogsIDandURLForAjax.sql");
$query = $f->str_replace('{where_clause_here}', $whereClause, $query);
$query = $f->str_replace('{limit-results}', 'limit ' . $limitResults, $query);
$results = $this->queryAndGetResults($query);
$rows = $results['rows'];
return $rows;
}
/**
* @global type $wpdb
* @param array $tableOptions orderby, paged, perpage, etc.
* @return array rows from querying the logs table.
*/
function getLogRecords($tableOptions) {
$f = ABJ_404_Solution_Functions::getInstance();
$abj404logic = ABJ_404_Solution_PluginLogic::getInstance();
$logsid_included = '';
$logsid = '';
if ($tableOptions['logsid'] != 0) {
$logsid_included = 'specific logs id included. */';
$logsid = esc_sql($abj404logic->sanitizeForSQL($tableOptions['logsid']));
}
$orderby = esc_sql(sanitize_text_field(
$abj404logic->sanitizeForSQL($tableOptions['orderby'])));
$order = esc_sql(sanitize_text_field(
$abj404logic->sanitizeForSQL($tableOptions['order'])));
$start = ( absint(sanitize_text_field($tableOptions['paged']) - 1)) * absint(sanitize_text_field($tableOptions['perpage']));
$perpage = absint(sanitize_text_field($tableOptions['perpage']));
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getLogRecords.sql");
$query = $f->str_replace('{logsid_included}', $logsid_included, $query);
$query = $f->str_replace('{logsid}', $logsid, $query);
$query = $f->str_replace('{orderby}', $orderby, $query);
$query = $f->str_replace('{order}', $order, $query);
$query = $f->str_replace('{start}', $start, $query);
$query = $f->str_replace('{perpage}', $perpage, $query);
$results = $this->queryAndGetResults($query);
return $results['rows'];
}
/**
* Log that a redirect was done. Insert into the logs table.
* @param string $requestedURL
* @param string $action
* @param string $matchReason
* @param string $requestedURLDetail the exact URL that was requested, for cases when a regex URL was matched.
*/
function logRedirectHit($requested_url, $action, $matchReason, $requestedURLDetail = null) {
global $wpdb;
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$abj404logic = ABJ_404_Solution_PluginLogic::getInstance();
$f = ABJ_404_Solution_Functions::getInstance();
$logTableName = $this->doTableNameReplacements("{wp_abj404_logsv2}");
$now = time();
// remove ridiculous non-printable characters
$requested_url = preg_replace('/[^\x20-\x7E]/', '', $requested_url); // Remove non-printable ASCII characters
// if the database can't handle utf8 characters then convert them to latin1.
try {
$getCharsetQuery = $wpdb->prepare("SELECT character_set_name as charset_name \n " .
"FROM information_schema.columns \n " .
"WHERE lower(table_schema) = lower(%s) \n " .
"AND lower(table_name) = lower(%s) \n " .
"AND lower(column_name) = lower(%s) ",
DB_NAME, $logTableName, 'requested_url');
$resultArray = $wpdb->get_results($getCharsetQuery, ARRAY_A);
if (!empty($resultArray)) {
$charsetFromDB = $resultArray[0]['charset_name'] ?? $resultArray[0]['CHARSET_NAME'];
if (strpos(strtolower($charsetFromDB), 'utf8') === false) {
$requested_url = $f->selectivelyURLEncode($requested_url);
$abj404logging->warn("The logs table is inconsistent because your character encoding doesn't support utf8mb4 characters.");
}
}
} catch (Exception $e) {
// not so important.
$abj404logging->debugMessage(__FUNCTION__ .
" error. Issue getting character set for table: " . $logTableName .
", column: requested_url. Error message: " . $e->getMessage());
}
// no nonce here because redirects are not user generated.
$options = $abj404logic->getOptions(true);
$referer = wp_get_referer();
if ($referer !== null && $referer !== false) {
$referer = esc_url_raw($referer);
// this length matches the maximum length of the data field on the logs table.
$referer = substr($referer, 0, 512);
} else {
$referer = '';
}
$current_user = wp_get_current_user();
$current_user_name = null;
if (isset($current_user)) {
$current_user_name = $current_user->user_login;
}
$ipAddressToSave = $_SERVER['REMOTE_ADDR'];
$ipAddressToSave = filter_var($ipAddressToSave, FILTER_VALIDATE_IP) ?
esc_sql($ipAddressToSave) : '';
if (!array_key_exists('log_raw_ips', $options) || $options['log_raw_ips'] != '1') {
$ipAddressToSave = $f->md5lastOctet($ipAddressToSave);
}
if (!empty($ipAddressToSave)) {
$ipAddressToSave = substr($ipAddressToSave, 0, 512);
} else {
$ipAddressToSave = '(Unknown)';
}
// we have to know what to set for the $minLogID value
$minLogID = false;
$checkMinIDQuery = $wpdb->prepare("SELECT id FROM `" . $logTableName . "` \n " .
"WHERE CAST(requested_url AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_unicode_ci = %s \n " .
"LIMIT 1", $requested_url);
$checkMinIDQueryResults = $wpdb->get_results($checkMinIDQuery, ARRAY_A);
if (empty($checkMinIDQueryResults)) {
$minLogID = true;
}
// extra escaping suggestions from chatgpt
$action = esc_url_raw($action);
// ------------ debug message begin
$helperFunctions = ABJ_404_Solution_Functions::getInstance();
$reasonMessage = trim(implode(", ",
array_filter(
array($_REQUEST[ABJ404_PP]['ignore_doprocess'], $_REQUEST[ABJ404_PP]['ignore_donotprocess']))));
$permalinksKept = '(not set)';
if ($abj404logging->isDebug() && array_key_exists(ABJ404_PP, $_REQUEST) &&
array_key_exists('permalinks_found', $_REQUEST[ABJ404_PP])) {
$permalinksKept = $_REQUEST[ABJ404_PP]['permalinks_kept'];
}
$abj404logging->debugMessage("Logging redirect. Referer: " . esc_html($referer) .
" | Current user: " . $current_user_name . " | From: " . urldecode($_SERVER['REQUEST_URI']) .
esc_html(" to: ") . esc_html($action) . ', Reason: ' . $matchReason . ", Ignore msg(s): " .
$reasonMessage . ', Execution time: ' . round($helperFunctions->getExecutionTime(), 2) .
' seconds, permalinks found: ' . $permalinksKept);
// ------------ debug message end
// insert the username into the lookup table and get the ID from the lookup table.
$usernameLookupID = $this->insertLookupValueAndGetID($current_user_name);
$this->insertAndGetResults($logTableName, array(
'timestamp' => $now,
'user_ip' => $ipAddressToSave,
'referrer' => $referer,
'dest_url' => $action,
'requested_url' => esc_url_raw($requested_url),
'requested_url_detail' => $requestedURLDetail,
'username' => $usernameLookupID,
'min_log_id' => $minLogID,
));
}
/** Insert a value into the lookup table and return the ID of the value.
* @param string $valueToInsert
*/
function insertLookupValueAndGetID($valueToInsert) {
$f = ABJ_404_Solution_Functions::getInstance();
$lookupID = intval($this->getLookupIDForUser($valueToInsert));
if ($lookupID >= 0) {
return $lookupID;
}
// insert the value since it's not there already.
$query = "INSERT INTO {wp_abj404_lookup} (lkup_value) values ('{lkup_value}')";
$query = $f->str_replace('{lkup_value}', $valueToInsert, $query);
$this->queryAndGetResults($query, array('ignore_errors' =>
array("Duplicate entry")));
$lookupID = $this->getLookupIDForUser($valueToInsert);
return $lookupID;
}
function getLookupIDForUser($userName) {
$query = "select id from {wp_abj404_lookup} where lkup_value = '" . $userName . "'";
$results = $this->queryAndGetResults($query);
if (sizeof($results['rows']) > 0) {
// the value already exists so we only need to return the ID.
$rows = $results['rows'];
$row1 = $rows[0];
$id = $row1['id'];
return intval($id);
}
return -1;
}
/**
* @global type $wpdb
* @param int $id
*/
function deleteRedirect($id) {
global $wpdb;
$cleanedID = absint(sanitize_text_field($id));
// no nonce here because this action is not always user generated.
if ($cleanedID >= 0 && is_numeric($id)) {
$query = "delete from {wp_abj404_redirects} where id = %d";
$this->queryAndGetResults($query, array('query_params' => array($cleanedID)));
}
}
/** Delete old redirects based on how old they are. This runs daily.
* @global type $wpdb
* @global type $abj404dao
* @global type $abj404logic
*/
function deleteOldRedirectsCron() {
global $wpdb;
$abj404dao = ABJ_404_Solution_DataAccess::getInstance();
$abj404logic = ABJ_404_Solution_PluginLogic::getInstance();
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$f = ABJ_404_Solution_Functions::getInstance();
$options = $abj404logic->getOptions();
$now = time();
$capturedURLsCount = 0;
$autoRedirectsCount = 0;
$manualRedirectsCount = 0;
$oldLogRowsDeleted = 0;
// If true then the user clicked the button to execute the mantenance.
$manually_fired = $abj404dao->getPostOrGetSanitize('manually_fired', false);
if ($f->strtolower($manually_fired) == 'true') {
$manually_fired = true;
}
$upgradesEtc = ABJ_404_Solution_DatabaseUpgradesEtc::getInstance();
$upgradesEtc->createDatabaseTables(false);
// delete the export file
$tempFile = $abj404logic->getExportFilename();
if (file_exists($tempFile)) {
ABJ_404_Solution_Functions::safeUnlink($tempFile);
}
// reset the crashed table count
$options['repaired_count'] = 0;
$abj404logic->updateOptions($options);
$duplicateRowsDeleted = $abj404dao->removeDuplicatesCron();
//Remove Captured URLs
if ($options['capture_deletion'] != '0') {
$capture_time = $options['capture_deletion'] * 86400;
$then = $now - $capture_time;
//Find unused urls
$status_list = ABJ404_STATUS_CAPTURED . ", " . ABJ404_STATUS_IGNORED . ", " . ABJ404_STATUS_LATER;
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getMostUnusedRedirects.sql");
$query = $f->str_replace('{status_list}', $status_list, $query);
$query = $f->str_replace('{timelimit}', $then, $query);
// Find unused redirects
$results = $this->queryAndGetResults($query);
$rows = $results['rows'];
foreach ($rows as $row) {
// Remove Them
$abj404logging->debugMessage("Captured 404 for \"" . $row['from_url'] .
'" deleted (unused since ' . $row['last_used_formatted'] . ').');
$abj404dao->deleteRedirect($row['id']);
$capturedURLsCount++;
}
}
// Remove Automatic Redirects
if (array_key_exists('auto_deletion', $options) && isset($options['auto_deletion']) && $options['auto_deletion'] != '0') {
$auto_time = $options['auto_deletion'] * 86400;
$then = $now - $auto_time;
$status_list = ABJ404_STATUS_AUTO;
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getMostUnusedRedirects.sql");
$query = $f->str_replace('{status_list}', $status_list, $query);
$query = $f->str_replace('{timelimit}', $then, $query);
// Find unused redirects
$results = $this->queryAndGetResults($query);
$rows = $results['rows'];
$rows = $results['rows'];
foreach ($rows as $row) {
// Remove Them
$abj404logging->debugMessage("Automatic redirect from: " . $row['from_url'] . ' to: ' .
$row['best_guess_dest'] . ' deleted (unused since ' . $row['last_used_formatted'] . ').');
$abj404dao->deleteRedirect($row['id']);
$autoRedirectsCount++;
}
}
//Remove Manual Redirects
if (array_key_exists('manual_deletion', $options) && isset($options['manual_deletion']) && $options['manual_deletion'] != '0') {
$manual_time = $options['manual_deletion'] * 86400;
$then = $now - $manual_time;
$status_list = ABJ404_STATUS_MANUAL . ", " . ABJ404_STATUS_REGEX;
//Find unused urls
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getMostUnusedRedirects.sql");
$query = $f->str_replace('{wp_posts}', $wpdb->posts, $query);
$query = $f->str_replace('{wp_options}', $wpdb->options, $query);
$query = $f->str_replace('{status_list}', $status_list, $query);
$query = $f->str_replace('{timelimit}', $then, $query);
$results = $this->queryAndGetResults($query);
$rows = $results['rows'];
foreach ($rows as $row) {
// Remove Them
$abj404logging->debugMessage("Manual redirect from: " . $row['from_url'] . ' to: ' .
$row['best_guess_dest'] . ' deleted (unused since ' . $row['last_used_formatted'] . ').');
$abj404dao->deleteRedirect($row['id']);
$manualRedirectsCount++;
}
}
//Clean up old logs. prepare the query. get the disk usage in bytes. compare to the max requested
// disk usage (MB to bytes). delete 1k rows at a time until the size is acceptable.
$logsSizeBytes = $abj404dao->getLogDiskUsage();
$maxLogSizeBytes = $options['maximum_log_disk_usage'] * 1024 * 1000;
$totalLogLines = $abj404dao->getLogsCount(0);
$averageSizePerLine = max($logsSizeBytes, 1) / max($totalLogLines, 1);
$logLinesToKeep = ceil($maxLogSizeBytes / $averageSizePerLine);
$logLinesToDelete = max($totalLogLines - $logLinesToKeep, 0);
if ($logLinesToDelete == null || trim($logLinesToDelete) == '') {
$logLinesToDelete = 0;
}
if ($logLinesToDelete > 0) {
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/deleteOldLogs.sql");
$query = $f->str_replace('{lines_to_delete}', $logLinesToDelete, $query);
$results = $this->queryAndGetResults($query);
$oldLogRowsDeleted = $results['rows_affected'];
}
$logsSizeBytes = $abj404dao->getLogDiskUsage();
$logSizeMB = round($logsSizeBytes / (1024 * 1000), 2);
$renamed = $abj404dao->limitDebugFileSize();
$renamed = $renamed ? "true" : "false";
$message = "deleteOldRedirectsCron. Old captured URLs removed: " .
$capturedURLsCount . ", Old automatic redirects removed: " . $autoRedirectsCount .
", Old manual redirects removed: " . $manualRedirectsCount .
", Old log lines removed: " . $oldLogRowsDeleted . ", New log size: " . $logSizeMB . "MB" .
", Duplicate rows deleted: " . $duplicateRowsDeleted . ", Debug file size limited: " .
$renamed;
// only send a 404 notification email during daily maintenance.
if (array_key_exists('admin_notification_email', $options) && isset($options['admin_notification_email']) &&
$f->strlen(trim($options['admin_notification_email'])) > 5) {
if ($manually_fired) {
$message .= ', The admin email notification option is skipped for user '
. 'initiated maintenance runs.';
} else {
$message .= ', ' . $abj404logic->emailCaptured404Notification();
}
} else {
$message .= ', Admin email notification option turned off.';
}
if (array_key_exists('send_error_logs', $options) && isset($options['send_error_logs']) &&
$options['send_error_logs'] == '1') {
if ($abj404logging->emailErrorLogIfNecessary()) {
$message .= ", Log file emailed to developer.";
}
}
// add some entries to the permalink cache if necessary
$abj404permalinkCache = ABJ_404_Solution_PermalinkCache::getInstance();
$rowsUpdated = $abj404permalinkCache->updatePermalinkCache(15);
$message .= ", Permlink cache rows updated: " . $rowsUpdated;
$manually_fired_String = ($manually_fired) ? 'true' : 'false';
$message .= ", User initiated: " . $manually_fired_String;
$abj404logging->infoMessage($message);
// fix any lingering errors
$upgradesEtc = ABJ_404_Solution_DatabaseUpgradesEtc::getInstance();
$upgradesEtc->createDatabaseTables();
$this->queryAndGetResults("optimize table {wp_abj404_redirects}");
$upgradesEtc->updatePluginCheck();
return $message;
}
function limitDebugFileSize() {
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$renamed = false;
$mbFileSize = $abj404logging->getDebugFileSize() / 1024 / 1000;
if ($mbFileSize > 10) {
$abj404logging->limitDebugFileSize();
$renamed = true;
}
return $renamed;
}
/** Remove duplicates.
* @global type $wpdb
*/
function removeDuplicatesCron() {
$rowsDeleted = 0;
$query = "SELECT COUNT(id) as repetitions, url FROM {wp_abj404_redirects} GROUP BY url HAVING repetitions > 1 ";
$result = $this->queryAndGetResults($query);
$outerRows = $result['rows'];
foreach ($outerRows as $row) {
$url = $row['url'];
$queryr1 = "select id from {wp_abj404_redirects} where url = '" . esc_sql(esc_url($url)) . "' order by timestamp desc limit 0,1";
$result = $this->queryAndGetResults($queryr1);
$innerRows = $result['rows'];
if (count($innerRows) >= 1) {
$row = $innerRows[0];
$original = $row['id'];
$queryl = "delete from {wp_abj404_redirects} where url='" . esc_sql(esc_url($url)) . "' and id != " . esc_sql($original);
$this->queryAndGetResults($queryl);
$rowsDeleted++;
}
}
return $rowsDeleted;
}
/**
* Store a redirect for future use.
* @global type $wpdb
* @param string $fromURL
* @param string $status ABJ404_STATUS_MANUAL etc
* @param string $type ABJ404_TYPE_POST, ABJ404_TYPE_CAT, ABJ404_TYPE_TAG, etc.
* @param string $final_dest
* @param string $code
* @param int $disabled
* @return int
*/
function setupRedirect($fromURL, $status, $type, $final_dest, $code, $disabled = 0) {
global $wpdb;
$abj404logging = ABJ_404_Solution_Logging::getInstance();
// nonce is verified outside of this method. We can't verify here because
// automatic redirects are sometimes created without user interaction.
if (!is_numeric($type)) {
$abj404logging->errorMessage("Wrong data type for redirect. TYPE is non-numeric. From: " .
esc_url($fromURL) . " to: " . esc_url($final_dest) . ", Type: " .esc_html($type) . ", Status: " . $status);
} else if (absint($type) < 0) {
$abj404logging->errorMessage("Wrong range for redirect TYPE. From: " .
esc_url($fromURL) . " to: " . esc_url($final_dest) . ", Type: " .esc_html($type) . ", Status: " . $status);
} else if (!is_numeric($status)) {
$abj404logging->errorMessage("Wrong data type for redirect. STATUS is non-numeric. From: " .
esc_url($fromURL) . " to: " . esc_url($final_dest) . ", Type: " .esc_html($type) . ", Status: " . $status);
}
// if we should not capture a 404 then don't.
if (!array_key_exists(ABJ404_PP, $_REQUEST) ||
!array_key_exists('ignore_doprocess', $_REQUEST[ABJ404_PP]) ||
!@$_REQUEST[ABJ404_PP]['ignore_doprocess']) {
$now = time();
$redirectsTable = $this->doTableNameReplacements("{wp_abj404_redirects}");
$wpdb->insert($redirectsTable, array(
'url' => esc_sql($fromURL),
'status' => esc_sql($status),
'type' => esc_sql($type),
'final_dest' => esc_sql($final_dest),
'code' => esc_sql($code),
'disabled' => esc_sql($disabled),
'timestamp' => esc_sql($now)
), array(
'%s',
'%d',
'%d',
'%s',
'%d',
'%d',
'%d'
)
);
}
return $wpdb->insert_id;
}
/** Get the redirect for the URL.
* @global type $wpdb
* @param string $url
* @return array
*/
function getActiveRedirectForURL($url) {
$f = ABJ_404_Solution_Functions::getInstance();
$redirect = array();
// remove ridiculous non-printable characters
$url = preg_replace('/[^\x20-\x7E]/', '', $url); // Remove non-printable ASCII characters
// we look for two URLs that might match. one with a trailing slash and one without.
// the one the user entered takes priority in case the admin added separate redirects for
// cases with and without the slash (and for backward compatibility).
$url1 = $url;
$url2 = $url;
if (substr($url, -1) === '/') {
$url2 = rtrim($url, '/');
} else {
$url2 = $url2 . '/';
}
// join to the wp_posts table to make sure the post exists.
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getPermalinkFromURL.sql");
$query = $f->str_replace('{url1}', esc_sql($url1), $query);
$query = $f->str_replace('{url2}', esc_sql($url2), $query);
$query = $this->doTableNameReplacements($query);
$query = $f->doNormalReplacements($query);
$results = $this->queryAndGetResults($query);
$rows = $results['rows'];
if (is_array($rows)) {
if (empty($rows)) {
$redirect['id'] = 0;
} else {
foreach ($rows[0] as $key => $value) {
$redirect[$key] = $value;
}
}
}
return $redirect;
}
/** Get the redirect for the URL.
* @param string $url
* @return array
*/
function getExistingRedirectForURL($url) {
$redirect = array();
// remove ridiculous non-printable characters
$url = preg_replace('/[^\x20-\x7E]/', '', $url); // Remove non-printable ASCII characters
// a disabled value of '1' means in the trash.
$query = $this->prepare_query_wp('select * from {wp_abj404_redirects} where BINARY url = BINARY {url} ' .
" and disabled = 0 ", array("url" => $url));
$results = $this->queryAndGetResults($query);
$rows = $results['rows'];
if (is_array($rows)) {
if (empty($rows)) {
$redirect['id'] = 0;
} else {
foreach ($rows[0] as $key => $value) {
$redirect[$key] = $value;
}
}
}
return $redirect;
}
/** Returns rows with the IDs of the published items.
* @global type $wpdb
* @global type $abj404logic
* @global type $abj404dao
* @global type $abj404logging
* @param string $slug only get results for this slug. (empty means all posts)
* @param string $searchTerm use this string in a LIKE on the sql.
* @param string $extraWhereClause use this string in a where on the sql.
* @return array
*/
function getPublishedPagesAndPostsIDs($slug = '', $searchTerm = '',
$limitResults = '', $orderResults = '', $extraWhereClause = '') {
global $wpdb;
$abj404logic = ABJ_404_Solution_PluginLogic::getInstance();
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$f = ABJ_404_Solution_Functions::getInstance();
// get the valid post types
$options = $abj404logic->getOptions();
$postTypes = $f->explodeNewline($options['recognized_post_types']);
$recognizedPostTypes = '';
foreach ($postTypes as $postType) {
$recognizedPostTypes .= "'" . trim($f->strtolower($postType)) . "', ";
}
$recognizedPostTypes = rtrim($recognizedPostTypes, ", ");
// ----------------
if ($slug != "") {
$specifiedSlug = " */\n and CAST(wp_posts.post_name AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_unicode_ci = "
. "'" . esc_sql($slug) . "' \n ";
} else {
$specifiedSlug = '';
}
if ($searchTerm != "") {
$searchTerm = " */\n and lower(wp_posts.post_title) like "
. "'%" . esc_sql($f->strtolower($searchTerm)) . "%' \n ";
} else {
$searchTerm = '';
}
if ($extraWhereClause != "") {
$extraWhereClause = " */\n " . $extraWhereClause;
}
if (!empty($limitResults)) {
$limitResults = " */\n limit " . $limitResults;
}
if (!empty($orderResults)) {
$orderResults = " */\n order by " . $orderResults;
}
// load the query and do the replacements.
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getPublishedPagesAndPostsIDs.sql");
$query = $this->doTableNameReplacements($query);
$query = $f->str_replace('{recognizedPostTypes}', $recognizedPostTypes, $query);
$query = $f->str_replace('{specifiedSlug}', $specifiedSlug, $query);
$query = $f->str_replace('{searchTerm}', $searchTerm, $query);
$query = $f->str_replace('{extraWhereClause}', $extraWhereClause, $query);
$query = $f->str_replace('{limit-results}', $limitResults, $query);
$query = $f->str_replace('{order-results}', $orderResults, $query);
$rows = $wpdb->get_results($query);
// check for errors
if ($wpdb->last_error) {
$abj404logging->errorMessage("Error executing query. Err: " . $wpdb->last_error . ", Query: " . $query);
}
return $rows;
}
/** Returns rows with the IDs of the published images.
* @return array
*/
function getPublishedImagesIDs() {
global $wpdb;
$abj404logic = ABJ_404_Solution_PluginLogic::getInstance();
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$f = ABJ_404_Solution_Functions::getInstance();
// get the valid post types
$options = $abj404logic->getOptions();
$postTypes = $f->explodeNewline($options['recognized_post_types']);
$recognizedPostTypes = '';
foreach ($postTypes as $postType) {
$recognizedPostTypes .= "'" . trim($f->strtolower($postType)) . "', ";
}
$recognizedPostTypes = rtrim($recognizedPostTypes, ", ");
// ----------------
// load the query and do the replacements.
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getPublishedImageIDs.sql");
$query = $this->doTableNameReplacements($query);
$query = $f->str_replace('{recognizedPostTypes}', $recognizedPostTypes, $query);
$rows = $wpdb->get_results($query);
// check for errors
if ($wpdb->last_error) {
$abj404logging->errorMessage("Error executing query. Err: " . $wpdb->last_error . ", Query: " . $query);
}
return $rows;
}
/** Returns rows with the defined terms (tags).
* @global type $wpdb
* @return array
*/
function getPublishedTags($slug = null) {
global $wpdb;
$abj404logic = ABJ_404_Solution_PluginLogic::getInstance();
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$f = ABJ_404_Solution_Functions::getInstance();
// get the valid post types
$options = $abj404logic->getOptions();
$categories = $f->explodeNewline($options['recognized_categories']);
$recognizedCategories = '';
foreach ($categories as $category) {
$recognizedCategories .= "'" . trim($f->strtolower($category)) . "', ";
}
$recognizedCategories = rtrim($recognizedCategories, ", ");
if ($slug != null) {
$slug = "*/ and wp_terms.slug = '" . esc_sql($slug) . "'\n";
}
// load the query and do the replacements.
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getPublishedTags.sql");
$query = $f->str_replace('{slug}', $slug, $query);
$query = $this->doTableNameReplacements($query);
$query = $f->str_replace('{recognizedCategories}', $recognizedCategories, $query);
$rows = $wpdb->get_results($query);
// check for errors
if ($wpdb->last_error) {
$abj404logging->errorMessage("Error executing query. Err: " . $wpdb->last_error . ", Query: " . $query);
}
$rows = $this->addURLToTermsRows($rows);
return $rows;
}
function addURLToTermsRows($rows) {
// add url data
global $wp_rewrite;
$extraPermaStructureCache = array();
foreach ($rows as $row) {
$taxonomy = $row->taxonomy;
if (!array_key_exists($taxonomy, $extraPermaStructureCache)) {
$extraPermaStructureCache[$taxonomy] = $wp_rewrite->get_extra_permastruct($taxonomy);
}
$struct = $extraPermaStructureCache[$taxonomy];
$url = str_replace('%' . $taxonomy . '%', $row->slug, $struct);
// TODO verify one of the urls?
/*
if (!$verifiedOne) {
$id = $row->term_id;
$link = get_tag_link($id);
$link = get_category_link($id);
// $link should equal $url
$verifiedOne = true;
}
*/
$row->url = $url;
}
return $rows;
}
/** Returns rows with the defined categories.
* @global type $wpdb
* @param int $term_id
* @return array
*/
function getPublishedCategories($term_id = null, $slug = null) {
global $wpdb;
$abj404logic = ABJ_404_Solution_PluginLogic::getInstance();
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$f = ABJ_404_Solution_Functions::getInstance();
// get the valid post types
$options = $abj404logic->getOptions();
$categories = $f->explodeNewline($options['recognized_categories']);
$recognizedCategories = '';
if (empty($categories)) {
$recognizedCategories = "''";
}
foreach ($categories as $category) {
$recognizedCategories .= "'" . trim($f->strtolower($category)) . "', ";
}
$recognizedCategories = rtrim($recognizedCategories, ", ");
if ($term_id != null) {
$term_id = "*/ and {wp_terms}.term_id = " . $term_id . "\n";
}
if ($slug != null) {
$slug = "*/ and {wp_terms}.slug = '" . esc_sql($slug) . "'\n";
}
// load the query and do the replacements.
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/getPublishedCategories.sql");
$query = $f->str_replace('{recognizedCategories}', $recognizedCategories, $query);
$query = $f->str_replace('{term_id}', $term_id, $query);
$query = $f->str_replace('{slug}', $slug, $query);
$query = $this->doTableNameReplacements($query);
$rows = $wpdb->get_results($query);
// check for errors
if ($wpdb->last_error) {
$abj404logging->errorMessage("Error executing query. Err: " . $wpdb->last_error . ", Query: " . $query);
}
$rows = $this->addURLToTermsRows($rows);
return $rows;
}
/** Delete stored redirects based on passed in POST data.
* @global type $wpdb
* @return string
*/
function deleteSpecifiedRedirects() {
global $wpdb;
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$message = "";
// nonce already verified.
if (!array_key_exists('sanity_purge', $_POST) || $_POST['sanity_purge'] != "1") {
$message = __('Error: You didn\'t check the I understand checkbox. No purging of records for you!', '404-solution');
return $message;
}
if (!array_key_exists('types', $_POST) || !isset($_POST['types']) || $_POST['types'] == '') {
$message = __('Error: No redirect types were selected. No purges will be done.', '404-solution');
return $message;
}
if (is_array($_POST['types'])) {
$type = array_map('sanitize_text_field', $_POST['types']);
} else {
$type = sanitize_text_field($_POST['types']);
}
if (!is_array($type)) {
$message = __('An unknown error has occurred.', '404-solution');
return $message;
}
$redirectTypes = array();
foreach ($type as $aType) {
if (('' . $aType != ABJ404_TYPE_HOME) && ('' . $aType != ABJ404_TYPE_HOME)) {
array_push($redirectTypes, absint($aType));
}
}
if (empty($redirectTypes)) {
$message = __('Error: No valid redirect types were selected. Exiting.', '404-solution');
$abj404logging->debugMessage("Error: No valid redirect types were selected. Types: " .
wp_kses_post(json_encode($redirectTypes)));
return $message;
}
$purge = sanitize_text_field($_POST['purgetype']);
if ($purge != 'abj404_logs' && $purge != 'abj404_redirects') {
$message = __('Error: An invalid purge type was selected. Exiting.', '404-solution');
$abj404logging->debugMessage("Error: An invalid purge type was selected. Type: " .
wp_kses_post(json_encode($purge)));
return $message;
}
// always add the type "0" because it's an invalid type that may exist in the databse.
// Adding it here does some cleanup if any is necessary.
array_push($redirectTypes, 0);
$typesForSQL = implode(',', $redirectTypes);
if ($purge == 'abj404_redirects') {
$query = "update {wp_abj404_redirects} set disabled = 1 where status in (" . $typesForSQL . ")";
$query = $this->doTableNameReplacements($query);
$redirectCount = $wpdb->query($query);
$message .= sprintf( _n( '%s redirect entry was moved to the trash.',
'%s redirect entries were moved to the trash.', $redirectCount, '404-solution'), $redirectCount);
}
return $message;
}
/**
* This returns only the first column of the first row of the result.
* @global type $wpdb
* @param string $query a query that starts with "select count(id) from ..."
* @param array $valueParams values to use to prepare the query.
* @return int the count (result) of the query.
*/
function getStatsCount($query, array $valueParams) {
global $wpdb;
if ($query == '') {
return 0;
}
$results = $wpdb->get_col($wpdb->prepare($query, $valueParams));
if (sizeof($results) == 0) {
throw new Exception("No results for query: " . esc_html($query));
}
return intval($results[0]);
}
/**
* @global type $wpdb
* @return int
* @throws Exception
*/
function getEarliestLogTimestamp() {
global $wpdb;
$query = 'SELECT min(timestamp) as timestamp FROM {wp_abj404_logsv2}';
$query = $this->doTableNameReplacements($query);
$results = $wpdb->get_col($query);
if (sizeof($results) == 0) {
return -1;
}
return intval($results[0]);
}
/** Look at $_POST and $_GET for the specified option and return the default value if it's not set.
* @param string $name The key to retrieve the value for.
* @param string $defaultValue The value to return if the value is not set.
* @return string The sanitized value.
*/
function getPostOrGetSanitize($name, $defaultValue = null) {
$returnValue = isset($_GET[$name]) ? $_GET[$name] : (isset($_POST[$name]) ? $_POST[$name] : null);
if ($returnValue !== null) {
if (is_array($returnValue)) {
$returnValue = array_map('sanitize_text_field', $returnValue);
} else {
$returnValue = sanitize_text_field($returnValue);
}
}
return $returnValue ?? $defaultValue;
}
/**
* @global type $wpdb
* @param array $ids
* @return array
*/
function getRedirectsByIDs($ids) {
global $wpdb;
$validids = array_map('absint', $ids);
$multipleIds = implode(',', $validids);
$query = "select id, url, type, status, final_dest, code from {wp_abj404_redirects} " .
"where id in (" . $multipleIds . ")";
$query = $this->doTableNameReplacements($query);
$rows = $wpdb->get_results($query, ARRAY_A);
return $rows;
}
/** Change the status to "trash" or "ignored," for example.
* @global type $wpdb
* @param int $id
* @param string $newstatus
* @return string
*/
function updateRedirectTypeStatus($id, $newstatus) {
$query = "update {wp_abj404_redirects} set status = '" .
esc_sql($newstatus) . "' where id = '" . esc_sql($id) . "'";
$result = $this->queryAndGetResults($query);
return $result['last_error'];
}
/** Move a redirect to the "trash" folder.
* @global type $wpdb
* @param int $id
* @param int $trash 1 for trash, 0 for not trash.
* @return string
*/
function moveRedirectsToTrash($id, $trash) {
global $wpdb;
$f = ABJ_404_Solution_Functions::getInstance();
$message = "";
$result = false;
if ($f->regexMatch('[0-9]+', '' . $id)) {
$redirectsTable = $this->doTableNameReplacements("{wp_abj404_redirects}");
$result = $wpdb->update($redirectsTable,
array('disabled' => esc_html($trash)), array('id' => absint($id)), array('%d'), array('%d')
);
}
if ($result == false) {
$message = __('Error: Unknown Database Error!', '404-solution');
}
return $message;
}
function updatePermalinkCache() {
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ .
"/sql/updatePermalinkCache.sql");
$results = $this->queryAndGetResults($query);
return $results;
}
function updatePermalinkCacheParentPages() {
$query = ABJ_404_Solution_Functions::readFileContents(__DIR__ .
"/sql/updatePermalinkCacheParentPages.sql");
// depthSoFar makes sure we don't have an infinite loop somehow.
$depthSoFar = 0;
$results = array();
do {
$results = $this->queryAndGetResults($query);
$depthSoFar++;
} while ($results['rows_affected'] != 0 && $depthSoFar < 15);
return $results;
}
/**
* @global type $wpdb
* @global type $abj404logging
* @param int $type ABJ404_EXTERNAL, ABJ404_POST, ABJ404_CAT, or ABJ404_TAG.
* @param string $dest
* @param string $fromURL
* @param int $idForUpdate
* @param string $redirectCode
* @param string $statusType ABJ404_STATUS_MANUAL or ABJ404_STATUS_REGEX
* @return string
*/
function updateRedirect($type, $dest, $fromURL, $idForUpdate, $redirectCode, $statusType) {
global $wpdb;
$abj404logging = ABJ_404_Solution_Logging::getInstance();
if (($type < 0) || ($idForUpdate <= 0)) {
$abj404logging->errorMessage("Bad data passed for update redirect request. Type: " .
esc_html($type) . ", Dest: " . esc_html($dest) . ", ID(s): " . esc_html($idForUpdate));
echo __('Error: Bad data passed for update redirect request.', '404-solution');
return;
}
$redirectsTable = $this->doTableNameReplacements("{wp_abj404_redirects}");
$wpdb->update($redirectsTable, array(
'url' => $fromURL,
'status' => $statusType,
'type' => absint($type),
'final_dest' => $dest,
'code' => esc_attr($redirectCode)
), array(
'id' => absint($idForUpdate)
), array(
'%s',
'%d',
'%d',
'%s',
'%d'
), array(
'%d'
)
);
// move this redirect out of the trash.
$this->moveRedirectsToTrash(absint($idForUpdate), 0);
}
/**
* @return int
*/
function getCapturedCountForNotification() {
$abj404dao = ABJ_404_Solution_DataAccess::getInstance();
return $abj404dao->getRecordCount(array(ABJ404_STATUS_CAPTURED));
}
}
🌑 DarkStealth — WP Plugin Edition
Directory: /home/httpd/html/matrixmodels.com/public_html/wp-content/plugins/404-solution/includes