📄 Viewing: Functions.php
<?php
/* Static functions that can be used from anywhere. */
abstract class ABJ_404_Solution_Functions {
private static $instance = null;
public static function getInstance() {
if (self::$instance == null) {
if (extension_loaded('mbstring')) {
self::$instance = new ABJ_404_Solution_FunctionsMBString();
} else {
self::$instance = new ABJ_404_Solution_FunctionsPreg();
}
}
return self::$instance;
}
/**
* This function selectively urlencodes a string. Characters outside of the latin1
* range (0-255) are urlencoded, while characters inside the range are kept as is.
* @param string $string The string to be selectively urlencoded.
* @return string The urlencoded string.
*/
function selectivelyURLEncode($input) {
$f = ABJ_404_Solution_Functions::getInstance();
// Handle array input
if (is_array($input)) {
return array_map([$f, 'selectivelyURLEncode'], $input);
}
if (!is_string($input)) {
$input = strval($input);
}
// Define replacements for unsafe characters
$replacements = [
'<' => '%3C',
'>' => '%3E',
'"' => '%22',
"'" => '%27',
'`' => '%60',
'{' => '%7B',
'}' => '%7D',
'(' => '%28',
')' => '%29',
];
// Perform replacements
$input = strtr($input, $replacements);
$encodedString = '';
// Iterate through each character in the string
for ($i = 0; $i < strlen($input); $i++) {
$char = $input[$i];
$ord = $f->ord($char);
// If the character is outside of latin1 range or is not representable
if ($ord > 255) {
// Convert to hexadecimal representation
$encodedString .= urlencode($char);
} else {
// Keep the original character if it's in the latin1 range
$encodedString .= $char;
}
}
return $encodedString;
}
/**Recursively applies `sanitize_text_field` to strings in an array or other data structure.
* @param mixed $data The data to sanitize. If an array, will recursively
* apply this function to all elements.
* @return mixed The sanitized data. */
function sanitize_text_field_recursive($data) {
if (is_array($data)) {
// Recursively apply to each element
return array_map([$this, 'sanitize_text_field_recursive'], $data);
}
return sanitize_text_field($data);
}
/** Escape a string to avoid Cross Site Scripting (XSS) attacks by encoding unsafe HTML characters.
* @param string $string The string to be escaped.
* @return string The escaped string.
*/
function escapeForXSS($value) {
if (is_array($value)) {
// Recursively sanitize each element in the array
return array_map([$this, 'escapeForXSS'], $value);
} elseif (!is_string($value)) {
// Convert non-string values to strings
$value = strval($value);
}
// Remove control characters and other unsafe characters
$value = preg_replace('/[\x00-\x1F\x7F]/u', '', $value ?? '');
// Remove any other characters you consider unsafe
$value = preg_replace('/[<>"\'`{}()]/u', '', $value ?? '');
return $value;
}
/** Only URL encode emojis from a string.
* @param string $url
* @return string
*/
function urlencodeEmojis($url) {
// Get all emojis in the string.
$matches = [];
$emojiPattern = '/[\x{1F000}-\x{1F6FF}\x{1F900}-\x{1F9FF}\x{2600}-\x{26FF}\x{2700}-\x{27BF}\x{1F300}-\x{1F5FF}\x{1F680}-\x{1F6FF}\x{1F1E6}-\x{1F1FF}]/u';
// next try: = '/[\x{1F6000}-\x{1F64F}\x{1F300}-\x{1F5FF}\x{1F680}-\x{1F6FF}\x{1F700}-\x{1F77F}\x{1F780}-\x{1F7FF}\x{1F800}-\x{1F8FF}\x{1F900}-\x{1F9FF}\x{1FA00}-\x{1FA6F}\x{1FA70}-\x{1FAFF}\x{2600}-\x{26FF}\x{2700}-\x{27BF}\x{2300}-\x{23FF}]/u';
$emojis = preg_match_all($emojiPattern, $url, $matches);
// If there are any emojis in the string, urlencode them.
if ($emojis > 0) {
foreach ($matches[0] as $emoji) {
$url = str_replace($emoji, urlencode($emoji), $url);
}
}
// Return the urlencoded string.
return $url;
}
/** Uses explode() to return an array.
* @param string $string
*/
function explodeNewline($string) {
$normalized = str_replace("\r\n", "\n", $string);
$normalized = str_replace('\n', "\n", $normalized);
$result = array_filter(explode("\n", $this->strtolower($normalized)),
array($this, 'removeEmptyCustom'));
return $result;
}
/** First urldecode then json_decode the data, then return it.
* All of this encoding and decoding is so that [] characters are supported.
* @param string $data
* @return mixed
*/
function decodeComplicatedData($data) {
$dataDecoded = urldecode($data);
// JSON.stringify escapes single quotes and json_decode does not want them to be escaped.
$dataStripped = str_replace("\'", "'", $dataDecoded);
$fixedData = json_decode($dataStripped, true);
$jsonErrorNumber = json_last_error();
if ($jsonErrorNumber != 0) {
$errorMsg = json_last_error_msg();
$lastMessagePart = ", Decoded: " . $dataDecoded;
if ($dataStripped != null && mb_strlen($dataStripped) > 1) {
$lastMessagePart = ", Stripped: " . $dataStripped;
}
$logger = ABJ_404_Solution_Logging::getInstance();
$logger->errorMessage("Error " . $jsonErrorNumber . " parsing JSON in "
. __CLASS__ . "->" . __FUNCTION__ . "(). Error message: " . $errorMsg . $lastMessagePart);
}
return $fixedData;
}
function str_replace($needle, $replacement, $haystack) {
if ($replacement === null) {
$replacement = '';
}
return str_replace($needle, $replacement, $haystack);
}
function single_str_replace($needle, $replacement, $haystack) {
if ($haystack == "" || $this->strlen($haystack) == 0) {
return "";
} else if ($this->strpos($haystack, $needle) === false) {
return $haystack;
}
$splitResult = explode($needle, $haystack);
$implodeResult = implode($replacement, $splitResult);
return $implodeResult;
}
/** Hash the last octet of an IP address.
* @param string $ip
* @return string
*/
function md5lastOctet($ip) {
if (trim($ip) == "") {
return $ip;
}
$partsToStrip = 1;
$separatorChar = ".";
// split into parts
$parts = explode(".", $ip);
if (count($parts) == 1) {
$parts = explode(":", $ip);
// if exploding on : worked then assume we have an IPv6.
if (count($parts) > 1) {
$partsToStrip = max(count($parts) - 3, 1);
$separatorChar = ":";
}
}
$firstPart = implode($separatorChar, array_slice($parts, 0, count($parts) - $partsToStrip));
$partToHash = $parts[count($parts) - $partsToStrip];
$lastPart = $separatorChar . substr(base_convert(md5($partToHash), 16,32), 0, 12);
return $firstPart . $lastPart;
}
abstract function ord($char);
abstract function strtolower($string);
abstract function strlen($string);
abstract function strpos($haystack, $needle, $offset = 0);
abstract function substr($str, $start, $length = null);
abstract function regexMatch($pattern, $string, &$regs = null);
abstract function regexMatchi($pattern, $string, &$regs = null);
abstract function regexReplace($pattern, $replacement, $string);
/** Used with array_filter()
* @param string $value
* @return boolean
*/
function removeEmptyCustom($value) {
if ($value == null) {
return false;
}
return trim($value) !== '';
}
function getExecutionTime() {
if (array_key_exists(ABJ404_PP, $_REQUEST) &&
array_key_exists('process_start_time', $_REQUEST[ABJ404_PP])) {
$elapsedTime = microtime(true) - $_REQUEST[ABJ404_PP]['process_start_time'];
return $elapsedTime;
}
return '';
}
/** Replace constants and translations.
* @param string $text
* @return string
*/
function doNormalReplacements($text) {
global $wpdb;
// known strings that do not exist in the translation file.
$knownReplacements = array(
'{ABJ404_STATUS_AUTO}' => ABJ404_STATUS_AUTO,
'{ABJ404_STATUS_MANUAL}' => ABJ404_STATUS_MANUAL,
'{ABJ404_STATUS_CAPTURED}' => ABJ404_STATUS_CAPTURED,
'{ABJ404_STATUS_IGNORED}' => ABJ404_STATUS_IGNORED,
'{ABJ404_STATUS_LATER}' => ABJ404_STATUS_LATER,
'{ABJ404_STATUS_REGEX}' => ABJ404_STATUS_REGEX,
'{ABJ404_TYPE_404_DISPLAYED}' => ABJ404_TYPE_404_DISPLAYED,
'{ABJ404_TYPE_POST}' => ABJ404_TYPE_POST,
'{ABJ404_TYPE_CAT}' => ABJ404_TYPE_CAT,
'{ABJ404_TYPE_TAG}' => ABJ404_TYPE_TAG,
'{ABJ404_TYPE_EXTERNAL}' => ABJ404_TYPE_EXTERNAL,
'{ABJ404_TYPE_HOME}' => ABJ404_TYPE_HOME,
'{ABJ404_HOME_URL}' => ABJ404_HOME_URL,
'{PLUGIN_NAME}' => PLUGIN_NAME,
'{ABJ404_VERSION}' => ABJ404_VERSION,
'{PHP_VERSION}' => phpversion(),
'{WP_VERSION}' => get_bloginfo('version'),
'{MYSQL_VERSION}' => $wpdb->db_version(),
'{ABJ404_MAX_AJAX_DROPDOWN_SIZE}' => ABJ404_MAX_AJAX_DROPDOWN_SIZE,
'{WP_MEMORY_LIMIT}' => WP_MEMORY_LIMIT,
'{MBSTRING}' => extension_loaded('mbstring') ? 'true' : 'false',
);
// replace known strings that do not exist in the translation file.
$text = $this->str_replace(array_keys($knownReplacements), array_values($knownReplacements), $text);
// Find the strings to replace in the content.
$re = '/\{(.+?)\}/x';
$stringsToReplace = array();
// TODO does this need to be $f->regexMatch?
preg_match_all($re, $text, $stringsToReplace, PREG_PATTERN_ORDER);
// Iterate through each string to replace.
foreach ($stringsToReplace[1] as $stringToReplace) {
$regexSearchString = '{' . $stringToReplace . '}';
$text = $this->str_replace($regexSearchString,
__($stringToReplace, '404-solution'), $text);
}
return $text;
}
/**
* @param string $directory
* @return boolean
*/
function createDirectoryWithErrorMessages($directory) {
if (!is_dir($directory)) {
if (file_exists($directory) || file_exists(rtrim($directory, '/'))) {
unlink($directory);
if (file_exists($directory) || file_exists(rtrim($directory, '/'))) {
error_log("ABJ-404-SOLUTION (ERROR) " . date('Y-m-d H:i:s T') . ": Error creating the directory " .
$directory . ". A file with that name alraedy exists.");
return false;
}
} else if (!mkdir($directory, 0755, true)) {
error_log("ABJ-404-SOLUTION (ERROR) " . date('Y-m-d H:i:s T') . ": Error creating the directory " .
$directory . ". Unknown issue.");
return false;
}
}
return true;
}
/** Turns ID|TYPE, SCORE into an array with id, type, score, link, and title.
*
* @param string $idAndType e.g. 15|POST is a page ID of 15 and a type POST.
* @param int $linkScore
* @param string $rowType if this is "image" then wp_get_attachment_image_src() is used.
* @param array $options in case an external URL is used.
* @return array an array with id, type, score, link, and title.
*/
static function permalinkInfoToArray($idAndType, $linkScore, $rowType = null, $options = null) {
$abj404logging = ABJ_404_Solution_Logging::getInstance();
$permalink = array();
if ($idAndType == NULL) {
$permalink['score'] = -999;
return $permalink;
}
$meta = explode("|", $idAndType);
$permalink['id'] = $meta[0];
$permalink['type'] = $meta[1];
$permalink['score'] = $linkScore;
$permalink['status'] = 'unknown';
$permalink['link'] = 'dunno';
if ($permalink['type'] == ABJ404_TYPE_POST) {
if ($rowType == 'image') {
$imageURL = wp_get_attachment_image_src($permalink['id'], "attached-image");
$permalink['link'] = $imageURL[0];
} else {
$permalink['link'] = get_permalink($permalink['id']);
}
$permalink['title'] = get_the_title($permalink['id']);
$permalink['status'] = get_post_status($permalink['id']);
} else if ($permalink['type'] == ABJ404_TYPE_TAG) {
$permalink['link'] = get_tag_link($permalink['id']);
$tag = get_term($permalink['id'], 'post_tag');
if ($tag != null) {
$permalink['title'] = $tag->name;
} else {
$permalink['title'] = $permalink['link'];
}
if ($permalink['title'] == null || $permalink['title'] == '') {
$permalink['status'] = 'trash';
} else {
$permalink['status'] = 'published';
}
} else if ($permalink['type'] == ABJ404_TYPE_CAT) {
$permalink['link'] = get_category_link($permalink['id']);
$cat = get_term($permalink['id'], 'category');
if ($cat != null) {
$permalink['title'] = $cat->name;
} else {
$permalink['title'] = $permalink['link'];
}
if ($permalink['title'] == null || $permalink['title'] == '') {
$permalink['status'] = 'trash';
} else {
$permalink['status'] = 'published';
}
} else if ($permalink['type'] == ABJ404_TYPE_HOME) {
$permalink['link'] = get_home_url();
$permalink['title'] = get_bloginfo('name');
$permalink['status'] = 'published';
} else if ($permalink['type'] == ABJ404_TYPE_EXTERNAL) {
$permalink['link'] = $permalink['id'];
if ($permalink['link'] == ABJ404_TYPE_EXTERNAL) {
if ($options == null) {
$abj404logic = ABJ_404_Solution_PluginLogic::getInstance();
$options = $abj404logic->getOptions();
}
$urlDestination = (array_key_exists('dest404pageURL', $options) &&
isset($options['dest404pageURL']) ? $options['dest404pageURL'] :
'External URL not found in options ABJ404 Solution Error');
$permalink['link'] = $urlDestination;
}
$permalink['status'] = 'published';
} else if ($permalink['type'] == ABJ404_TYPE_404_DISPLAYED) {
$permalink['link'] = '404';
$permalink['status'] = 'published';
} else {
$abj404logging->errorMessage("Unrecognized permalink type: " .
wp_kses_post(json_encode($permalink)));
}
if ($permalink['status'] === false) {
$permalink['status'] = 'trash';
}
// decode anything that might be encoded to support utf8 characters
if (array_key_exists('link', $permalink)) {
$permalink['link'] = urldecode($permalink['link']);
}
$permalink['title'] = array_key_exists('title', $permalink) ? urldecode($permalink['title']) : '';
return $permalink;
}
/** Returns true if the file does not exist after calling this method.
* @param string $path
* @return boolean
*/
static function safeUnlink($path) {
if (file_exists($path)) {
return unlink($path);
}
return true;
}
/** Returns true if the file does not exist after calling this method.
* @param string $path
* @return boolean
*/
static function safeRmdir($path) {
if (file_exists($path)) {
return rmdir($path);
}
return true;
}
/** Recursively delete a directory.
* @param string $dir
* @throws Exception
* @return boolean
*/
static function deleteDirectoryRecursively($dir) {
// if the directory isn't a part of our plugin then don't do it.
if (strpos($dir, ABJ404_PATH) === false) {
throw new Exception("Can't delete " . esc_html($dir));
}
// if it's already gone then we're done.
if (!file_exists($dir)) {
return true;
}
// if it's not a directory then delete the file.
if (!is_dir($dir)) {
return unlink($dir);
}
// get a list of all files (and directories) in the directory.
$items = scandir($dir);
foreach ($items as $item) {
if ($item == '.' || $item == '..') {
continue;
}
// call self to delete the file/directory.
if (!self::deleteDirectoryRecursively($dir . DIRECTORY_SEPARATOR . $item)) {
return false;
}
}
// remove the original directory.
return rmdir($dir);
}
/** Reads an entire file at once into a string and return it.
* @param string $path
* @param boolean $appendExtraData
* @throws Exception
* @return string
*/
static function readFileContents($path, $appendExtraData = true) {
// modify what's returned to make debugging easier.
$dataSupplement = self::getDataSupplement($path, $appendExtraData);
if (!file_exists($path)) {
throw new Exception("Error: Can't find file: " . esc_html($path));
}
$fileContents = file_get_contents($path);
if ($fileContents !== false) {
return $dataSupplement['prefix'] . $fileContents . $dataSupplement['suffix'];
}
// if we can't read the file that way then try curl.
if (!function_exists('curl_init')) {
throw new Exception("Error: Can't read file: " . esc_html($path) .
"\n file_get_contents didn't work and curl is not installed.");
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'file://' . $path);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec($ch);
curl_close($ch);
if ($output == null) {
throw new Exception("Error: Can't read file, even with cURL: " . esc_html($path));
}
return $dataSupplement['prefix'] . $output . $dataSupplement['suffix'];
}
private static function getDataSupplement($filePath, $appendExtraData = true) {
$f = ABJ_404_Solution_Functions::getInstance();
$path = strtolower($filePath);
// remove the first part of the path because some people don't want to see
// it in the log file.
$homepath = dirname(ABSPATH);
$beginningOfPath = substr($path, 0, strlen($homepath));
if (strtolower($beginningOfPath) == strtolower($homepath)) {
$path = substr($path, strlen($homepath));
}
$supplement = array();
if (!$appendExtraData) {
$supplement['prefix'] = '';
$supplement['suffix'] = '';
} else if ($f->endsWithCaseInsensitive($path, '.sql')) {
$supplement['prefix'] = "\n/* ------------------ " . $filePath . " BEGIN ----- */ \n";
$supplement['suffix'] = "\n/* ------------------ " . $filePath . " END ----- */ \n";
} else if ($f->endsWithCaseInsensitive($path, '.html')) {
$supplement['prefix'] = "\n<!-- ------------------ " . $filePath . " BEGIN ----- --> \n";
$supplement['suffix'] = "\n<!-- ------------------ " . $filePath . " END ----- --> \n";
} else {
$supplement['prefix'] = "\n/* ------------------ " . $filePath . " BEGIN unknown file type in "
. __CLASS__ . '::' . __FUNCTION__ . "() ----- */ \n";
$supplement['suffix'] = "\n/* ------------------ " . $filePath . " END unknown file type in "
. __CLASS__ . '::' . __FUNCTION__ . "() ----- */ \n";
}
return $supplement;
}
/** Deletes the existing file at $filePath and puts the URL contents in it's place.
* @param string $url
* @param string $filePath
*/
function readURLtoFile($url, $filePath) {
$abj404logging = ABJ_404_Solution_Logging::getInstance();
ABJ_404_Solution_Functions::safeUnlink($filePath);
// if we can't read the file that way then try curl.
if (function_exists('curl_init')) {
try {
//This is the file where we save the information
$destinationFileWriteHandle = fopen($filePath, 'w+');
//Here is the file we are downloading, replace spaces with %20
$ch = curl_init($this->str_replace(" ", "%20", $url));
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 '
. '(KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36 (404 Solution WordPress Plugin)');
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
// write curl response to file
curl_setopt($ch, CURLOPT_FILE, $destinationFileWriteHandle);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
// get curl response
curl_exec($ch);
curl_close($ch);
fclose($destinationFileWriteHandle);
if (file_exists($filePath) && filesize($filePath) > 0) {
return;
}
} catch (Exception $e) {
$abj404logging->debugMessage("curl didn't work for downloading a URL. " . $e->getMessage());
}
}
ABJ_404_Solution_Functions::safeUnlink($filePath);
file_put_contents($filePath, fopen($url, 'r'));
}
/**
* @param string $haystack
* @param string $needle
* @return string
*/
function endsWithCaseInsensitive($haystack, $needle) {
$f = ABJ_404_Solution_Functions::getInstance();
$length = $f->strlen($needle);
if ($f->strlen($haystack) < $length) {
return false;
}
$lowerNeedle = $this->strtolower($needle);
$lowerHay = $this->strtolower($haystack);
return ($f->substr($lowerHay, -$length) == $lowerNeedle);
}
/**
* @param string $haystack
* @param string $needle
* @return string
*/
function endsWithCaseSensitive($haystack, $needle) {
$f = ABJ_404_Solution_Functions::getInstance();
$length = $f->strlen($needle);
if ($f->strlen($haystack) < $length) {
return false;
}
return ($f->substr($haystack, -$length) == $needle);
}
/** Sort the QUERY parts of the requested URL.
* This is in place because these are stored as part of the URL in the database and used for forwarding to another page.
* This is done because sometimes different query parts result in a completely different page. Therefore we have to
* take into account the query part of the URL (?query=part) when looking for a page to redirect to.
*
* Here we sort the query parts so that the same request will always look the same.
* @param array $urlParts
* @return string
*/
function sortQueryString($urlParts) {
if (!array_key_exists('query', $urlParts) || $urlParts['query'] == '') {
return '';
}
// parse it into an array
$queryParts = array();
parse_str($urlParts['query'], $queryParts);
// sort the parts
ksort($queryParts);
return urldecode(http_build_query($queryParts));
}
/** We have to remove any 'p=##' because it will cause a 404 otherwise.
* @param string $queryString
* @return string
*/
function removePageIDFromQueryString($queryString) {
// parse the string
$queryParts = array();
parse_str($queryString, $queryParts);
// remove the page id
if (array_key_exists('p', $queryParts)) {
unset($queryParts['p']);
}
// rebuild the string.
return urldecode(http_build_query($queryParts));
}
}
🌑 DarkStealth — WP Plugin Edition
Directory: /home/httpd/html/matrixmodels.com/public_html/wp-content/plugins/404-solution/includes