晋太元中,武陵人捕鱼为业。缘溪行,忘路之远近。忽逢桃花林,夹岸数百步,中无杂树,芳草鲜美,落英缤纷。渔人甚异之,复前行,欲穷其林。 林尽水源,便得一山,山有小口,仿佛若有光。便舍船,从口入。初极狭,才通人。复行数十步,豁然开朗。土地平旷,屋舍俨然,有良田、美池、桑竹之属。阡陌交通,鸡犬相闻。其中往来种作,男女衣着,悉如外人。黄发垂髫,并怡然自乐。 见渔人,乃大惊,问所从来。具答之。便要还家,设酒杀鸡作食。村中闻有此人,咸来问讯。自云先世避秦时乱,率妻子邑人来此绝境,不复出焉,遂与外人间隔。问今是何世,乃不知有汉,无论魏晋。此人一一为具言所闻,皆叹惋。余人各复延至其家,皆出酒食。停数日,辞去。此中人语云:“不足为外人道也。”(间隔 一作:隔绝) 既出,得其船,便扶向路,处处志之。及郡下,诣太守,说如此。太守即遣人随其往,寻向所志,遂迷,不复得路。 南阳刘子骥,高尚士也,闻之,欣然规往。未果,寻病终。后遂无问津者。
|
Server : Apache System : Linux srv.rainic.com 4.18.0-553.47.1.el8_10.x86_64 #1 SMP Wed Apr 2 05:45:37 EDT 2025 x86_64 User : rainic ( 1014) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/stando/www/wp-content/plugins/duplicator/installer/dup-installer/classes/ |
Upload File : |
<?php
/**
* Walks every table in db that then walks every row and column replacing searches with replaces
* large tables are split into 50k row blocks to save on memory.
*
* Standard: PSR-2
* @link http://www.php-fig.org/psr/psr-2 Full Documentation
*
* @package SC\DUPX\UpdateEngine
*
*/
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
class DUPX_UpdateEngine
{
const SERIALIZE_OPEN_STR_REGEX = '/^(s:\d+:")/';
const SERIALIZE_OPEN_SUBSTR_LEN = 25;
const SERIALIZE_CLOSE_STR_REGEX = '/^";}*(?:"|a:|s:|S:|b:|d:|i:|o:|O:|C:|r:|R:|N;|$)/';
const SERIALIZE_CLOSE_SUBSTR_LEN = 50;
const SERIALIZE_CLOSE_STR = '";';
const SERIALIZE_CLOSE_STR_LEN = 2;
private static $report = null;
/**
* Used to report on all log errors into the installer-txt.log
*
* @return string Writes the results of the update engine tables to the log
*/
public static function logErrors()
{
$s3Funcs = DUPX_S3_Funcs::getInstance();
if (!empty($s3Funcs->report['errsql'])) {
DUPX_Log::info("--------------------------------------");
DUPX_Log::info("DATA-REPLACE ERRORS (MySQL)");
foreach ($s3Funcs->report['errsql'] as $error) {
DUPX_Log::info($error);
}
DUPX_Log::info("");
}
if (!empty($s3Funcs->report['errser'])) {
DUPX_Log::info("--------------------------------------");
DUPX_Log::info("DATA-REPLACE ERRORS (Serialization):");
foreach ($s3Funcs->report['errser'] as $error) {
DUPX_Log::info($error);
}
DUPX_Log::info("");
}
if (!empty($s3Funcs->report['errkey'])) {
DUPX_Log::info("--------------------------------------");
DUPX_Log::info("DATA-REPLACE ERRORS (Key):");
DUPX_Log::info('Use SQL: SELECT @row := @row + 1 as row, t.* FROM some_table t, (SELECT @row := 0) r');
foreach ($s3Funcs->report['errkey'] as $error) {
DUPX_Log::info($error);
}
}
}
/**
* Used to report on all log stats into the installer-txt.log
*
* @return string Writes the results of the update engine tables to the log
*/
public static function logStats()
{
$s3Funcs = DUPX_S3_Funcs::getInstance();
DUPX_Log::resetIndent();
if (!empty($s3Funcs->report) && is_array($s3Funcs->report)) {
$stats = "--------------------------------------\n";
$stats .= sprintf("SCANNED:\tTables:%d \t|\t Rows:%d \t|\t Cells:%d \n", $s3Funcs->report['scan_tables'], $s3Funcs->report['scan_rows'], $s3Funcs->report['scan_cells']);
$stats .= sprintf("UPDATED:\tTables:%d \t|\t Rows:%d \t|\t Cells:%d \n", $s3Funcs->report['updt_tables'], $s3Funcs->report['updt_rows'], $s3Funcs->report['updt_cells']);
$stats .= sprintf("ERRORS:\t\t%d \nRUNTIME:\t%f sec", $s3Funcs->report['err_all'], $s3Funcs->report['time']);
DUPX_Log::info($stats);
}
}
/**
* Returns only the text type columns of a table ignoring all numeric types
*
* @param obj $conn A valid database link handle
* @param string $table A valid table name
*
* @return array All the column names of a table
*/
private static function getTextColumns($table)
{
$dbh = DUPX_S3_Funcs::getInstance()->getDbConnection();
$type_where = '';
$type_where .= "type LIKE '%char%' OR ";
$type_where .= "type LIKE '%text' OR ";
$type_where .= "type LIKE '%blob' ";
$sql = "SHOW COLUMNS FROM `".mysqli_real_escape_string($dbh, $table)."` WHERE {$type_where}";
$result = DUPX_DB::mysqli_query($dbh, $sql, __FILE__, __LINE__);
if (!$result) {
return null;
}
$fields = array();
if (mysqli_num_rows($result) > 0) {
while ($row = mysqli_fetch_assoc($result)) {
$fields[] = $row['Field'];
}
}
//Return Primary which is needed for index lookup. LIKE '%PRIMARY%' is less accurate with lookup
//$result = mysqli_query($dbh, "SHOW INDEX FROM `{$table}` WHERE KEY_NAME LIKE '%PRIMARY%'");
$result = mysqli_query($dbh, "SHOW INDEX FROM `".mysqli_real_escape_string($dbh, $table)."`");
if (mysqli_num_rows($result) > 0) {
while ($row = mysqli_fetch_assoc($result)) {
$fields[] = $row['Column_name'];
}
}
return (count($fields) > 0) ? array_unique($fields) : null;
}
public static function set_sql_column_safe(&$str)
{
$str = "`$str`";
}
public static function loadInit()
{
DUPX_Log::info('ENGINE LOAD INIT', DUPX_Log::LV_DEBUG);
$s3Funcs = DUPX_S3_Funcs::getInstance();
$s3Funcs->report['profile_start'] = DUPX_U::getMicrotime();
$dbh = $s3Funcs->getDbConnection();
@mysqli_autocommit($dbh, false);
}
/**
* Begins the processing for replace logic
*
* @param array $tables The tables we want to look at
*
* @return array Collection of information gathered during the run.
*/
public static function load($tables = array())
{
self::loadInit();
if (is_array($tables)) {
foreach ($tables as $table) {
self::evaluateTalbe($table);
}
}
self::commitAndSave();
return self::loadEnd();
}
public static function commitAndSave()
{
DUPX_Log::info('ENGINE COMMIT AND SAVE', DUPX_Log::LV_DEBUG);
$dbh = DUPX_S3_Funcs::getInstance()->getDbConnection();
@mysqli_commit($dbh);
@mysqli_autocommit($dbh, true);
DUPX_NOTICE_MANAGER::getInstance()->saveNotices();
}
public static function loadEnd()
{
$s3Funcs = DUPX_S3_Funcs::getInstance();
DUPX_Log::info('ENGINE LOAD END', DUPX_Log::LV_DEBUG);
$s3Funcs->report['profile_end'] = DUPX_U::getMicrotime();
$s3Funcs->report['time'] = DUPX_U::elapsedTime($s3Funcs->report['profile_end'], $s3Funcs->report['profile_start']);
$s3Funcs->report['errsql_sum'] = empty($s3Funcs->report['errsql']) ? 0 : count($s3Funcs->report['errsql']);
$s3Funcs->report['errser_sum'] = empty($s3Funcs->report['errser']) ? 0 : count($s3Funcs->report['errser']);
$s3Funcs->report['errkey_sum'] = empty($s3Funcs->report['errkey']) ? 0 : count($s3Funcs->report['errkey']);
$s3Funcs->report['err_all'] = $s3Funcs->report['errsql_sum'] + $s3Funcs->report['errser_sum'] + $s3Funcs->report['errkey_sum'];
return $s3Funcs->report;
}
public static function getTableRowParamsDefault($table = '')
{
return array(
'table' => $table,
'updated' => false,
'row_count' => 0,
'columns' => array(),
'colList' => '*',
'colMsg' => 'every column',
'columnsSRList' => array(),
'pages' => 0,
'page_size' => 0,
'page' => 0,
'current_row' => 0
);
}
private static function getTableRowsParams($table)
{
$s3Funcs = DUPX_S3_Funcs::getInstance();
$dbh = $s3Funcs->getDbConnection();
$rowsParams = self::getTableRowParamsDefault($table);
// Count the number of rows we have in the table if large we'll split into blocks
$rowsParams['row_count'] = mysqli_query($dbh, "SELECT COUNT(*) FROM `".mysqli_real_escape_string($dbh, $rowsParams['table'])."`");
if (!$rowsParams['row_count']) {
return null;
}
$rows_result = mysqli_fetch_array($rowsParams['row_count']);
@mysqli_free_result($rowsParams['row_count']);
$rowsParams['row_count'] = $rows_result[0];
if ($rowsParams['row_count'] == 0) {
$rowsParams['colMsg'] = 'no columns ';
self::logEvaluateTable($rowsParams);
return null;
}
// Get a list of columns in this table
$sql = 'DESCRIBE '.mysqli_real_escape_string($dbh, $rowsParams['table']);
$fields = mysqli_query($dbh, $sql);
if (!$fields) {
return null;
}
while ($column = mysqli_fetch_array($fields)) {
$rowsParams['columns'][$column['Field']] = $column['Key'] == 'PRI' ? true : false;
}
$rowsParams['page_size'] = $GLOBALS['DATABASE_PAGE_SIZE'];
$rowsParams['pages'] = ceil($rowsParams['row_count'] / $rowsParams['page_size']);
// Grab the columns of the table. Only grab text based columns because
// they are the only data types that should allow any type of search/replace logic
if (!$s3Funcs->getPost('fullsearch')) {
$rowsParams['colList'] = self::getTextColumns($rowsParams['table']);
if ($rowsParams['colList'] != null && is_array($rowsParams['colList'])) {
array_walk($rowsParams['colList'], array(__CLASS__, 'set_sql_column_safe'));
$rowsParams['colList'] = implode(',', $rowsParams['colList']);
}
$rowsParams['colMsg'] = (empty($rowsParams['colList'])) ? 'every column' : 'text columns';
}
if (empty($rowsParams['colList'])) {
$rowsParams['colMsg'] = 'no columns ';
}
self::logEvaluateTable($rowsParams);
if (empty($rowsParams['colList'])) {
return null;
} else {
// PREPARE SEARCH AN REPLACE LISF FOR TABLES
$rowsParams['columnsSRList'] = self::getColumnsSearchReplaceList($rowsParams['table'], $rowsParams['columns']);
return $rowsParams;
}
}
public static function logEvaluateTable($rowsParams)
{
DUPX_Log::resetIndent();
$log = "\n".'EVALUATE TABLE: '.str_pad(DUPX_Log::varToString($rowsParams['table']), 50, '_', STR_PAD_RIGHT);
$log .= '[ROWS:'.str_pad($rowsParams['row_count'], 6, " ", STR_PAD_LEFT).']';
$log .= '[PG:'.str_pad($rowsParams['pages'], 4, " ", STR_PAD_LEFT).']';
$log .= '[SCAN:'.$rowsParams['colMsg'].']';
if (DUPX_Log::isLevel(DUPX_Log::LV_DETAILED)) {
$log .= '[COLS: '.$rowsParams['colList'].']';
}
DUPX_Log::info($log);
DUPX_Log::incIndent();
}
public static function evaluateTalbe($table)
{
$s3Funcs = DUPX_S3_Funcs::getInstance();
// init table params if isn't initialized
if (!self::initTableParams($table)) {
return false;
}
//Paged Records
$pages = $s3Funcs->cTableParams['pages'];
for ($page = 0; $page < $pages; $page++) {
self::evaluateTableRows($table, $page);
}
if ($s3Funcs->cTableParams['updated']) {
$s3Funcs->report['updt_tables']++;
}
}
public static function evaluateTableRows($table, $page)
{
$s3Funcs = DUPX_S3_Funcs::getInstance();
// init table params if isn't initialized
if (!self::initTableParams($table)) {
return false;
}
$s3Funcs->cTableParams['page'] = $page;
if ($s3Funcs->cTableParams['page'] >= $s3Funcs->cTableParams['pages']) {
DUPX_Log::info('ENGINE EXIT PAGE:'.DUPX_Log::varToString($table).' PAGES:'.$s3Funcs->cTableParams['pages'], DUPX_Log::LV_DEBUG);
return false;
}
self::evaluatePagedRows($s3Funcs->cTableParams);
}
public static function initTableParams($table)
{
$s3Funcs = DUPX_S3_Funcs::getInstance();
if (is_null($s3Funcs->cTableParams) || $s3Funcs->cTableParams['table'] !== $table) {
DUPX_Log::info('ENGINE INIT TABLE PARAMS '.DUPX_Log::varToString($table), DUPX_Log::LV_DETAILED);
$s3Funcs->report['scan_tables']++;
if (($s3Funcs->cTableParams = self::getTableRowsParams($table)) === null) {
DUPX_Log::info('ENGINE TABLE PARAMS EMPTY', DUPX_Log::LV_DEBUG);
return false;
}
}
return true;
}
/**
* evaluate rows with pagination
*
* @param array $rowsParams
*
* @return boolean // if true table is modified and updated
*/
private static function evaluatePagedRows(&$rowsParams)
{
$nManager = DUPX_NOTICE_MANAGER::getInstance();
$s3Funcs = DUPX_S3_Funcs::getInstance();
$dbh = $s3Funcs->getDbConnection();
$start = $rowsParams['page'] * $rowsParams['page_size'];
$end = $start + $rowsParams['page_size'] - 1;
$sql = sprintf("SELECT {$rowsParams['colList']} FROM `%s` LIMIT %d, %d", $rowsParams['table'], $start, $rowsParams['page_size']);
$data = DUPX_DB::mysqli_query($dbh, $sql, __FILE__, __LINE__);
$scan_count = min($rowsParams['row_count'], $end);
if (DUPX_Log::isLevel(DUPX_Log::LV_DETAILED)) {
DUPX_Log::info('ENGINE EV TABLE '.str_pad(DUPX_Log::varToString($rowsParams['table']), 50, '_', STR_PAD_RIGHT).
'[PAGE:'.str_pad($rowsParams['page'], 4, " ", STR_PAD_LEFT).']'.
'[START:'.str_pad($start, 6, " ", STR_PAD_LEFT).']'.
'[OF:'.str_pad($scan_count, 6, " ", STR_PAD_LEFT).']', DUPX_Log::LV_DETAILED);
}
if (!$data) {
$errMsg = mysqli_error($dbh);
$s3Funcs->report['errsql'][] = $errMsg;
$nManager->addFinalReportNotice(array(
'shortMsg' => 'DATA-REPLACE ERRORS: MySQL',
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => $errMsg,
'sections' => 'search_replace'
));
}
//Loops every row
while ($row = mysqli_fetch_assoc($data)) {
self::evaluateRow($rowsParams, $row);
}
//DUPX_U::fcgiFlush();
@mysqli_free_result($data);
return $rowsParams['updated'];
}
/**
* evaluate single row columns
*
* @param array $rowsParams
* @param array $row
*
* @return boolean true if row is modified and updated
*/
private static function evaluateRow(&$rowsParams, $row)
{
$nManager = DUPX_NOTICE_MANAGER::getInstance();
$s3Funcs = DUPX_S3_Funcs::getInstance();
$dbh = $s3Funcs->getDbConnection();
$maxSerializeLenCheck = $s3Funcs->getPost('maxSerializeStrlen');
$s3Funcs->report['scan_rows']++;
$rowsParams['current_row']++;
$upd_col = array();
$upd_sql = array();
$where_sql = array();
$upd = false;
$serial_err = false;
$is_unkeyed = !in_array(true, $rowsParams['columns']);
$rowErrors = array();
//Loops every cell
foreach ($rowsParams['columns'] as $column => $primary_key) {
$s3Funcs->report['scan_cells']++;
if (!isset($row[$column])) {
continue;
}
$safe_column = '`'.mysqli_real_escape_string($dbh, $column).'`';
$edited_data = $originalData = $row[$column];
$base64converted = false;
$txt_found = false;
//Unkeyed table code
//Added this here to add all columns to $where_sql
//The if statement with $txt_found would skip additional columns -TG
if ($is_unkeyed && !empty($originalData)) {
$where_sql[] = $safe_column.' = "'.mysqli_real_escape_string($dbh, $originalData).'"';
}
//Only replacing string values
if (!empty($row[$column]) && !is_numeric($row[$column]) && $primary_key != 1) {
// get search and reaplace list for column
$tColList = &$rowsParams['columnsSRList'][$column]['list'];
$tColSearchList = &$rowsParams['columnsSRList'][$column]['sList'];
$tColreplaceList = &$rowsParams['columnsSRList'][$column]['rList'];
$tColExactMatch = $rowsParams['columnsSRList'][$column]['exactMatch'];
// skip empty search col
if (empty($tColSearchList)) {
continue;
}
// Search strings in data
foreach ($tColList as $item) {
if (strpos($edited_data, $item['search']) !== false) {
$txt_found = true;
break;
}
}
if (!$txt_found) {
//if not found decetc Base 64
if (($decoded = DUPX_U::is_base64($row[$column])) !== false) {
$edited_data = $decoded;
$base64converted = true;
// Search strings in data decoded
foreach ($tColList as $item) {
if (strpos($edited_data, $item['search']) !== false) {
$txt_found = true;
break;
}
}
}
//Skip table cell if match not found
if (!$txt_found) {
continue;
}
}
// 0 no limit
if ($maxSerializeLenCheck > 0 && self::is_serialized_string($edited_data) && strlen($edited_data) > $maxSerializeLenCheck) {
$serial_err = true;
$trimLen = DUPX_Log::isLevel(DUPX_Log::LV_HARD_DEBUG) ? 10000 : 200;
$rowErrors[$column] = 'ENGINE: serialize data too big to convert; data len:'.strlen($edited_data).' Max size:'.$maxSerializeLenCheck;
$rowErrors[$column] .= "\n\tDATA: ".mb_strimwidth($edited_data, 0, $trimLen, ' [...]');
} else {
//Replace logic - level 1: simple check on any string or serlized strings
if ($tColExactMatch) {
// if is exact match search and replace the itentical string
if (($rIndex = array_search($edited_data, $tColSearchList)) !== false) {
DUPX_Log::info("ColExactMatch ".$column.' search:'.$edited_data.' replace:'.$tColreplaceList[$rIndex].' index:'.$rIndex, DUPX_Log::LV_DEBUG);
$edited_data = $tColreplaceList[$rIndex];
}
} else {
// search if column contain search list
$edited_data = self::searchAndReplaceItems($tColSearchList, $tColreplaceList, $edited_data);
//Replace logic - level 2: repair serialized strings that have become broken
// check value without unserialize it
if (self::is_serialized_string($edited_data)) {
$serial_check = self::fixSerialString($edited_data);
if ($serial_check['fixed']) {
$edited_data = $serial_check['data'];
} else {
$trimLen = DUPX_Log::isLevel(DUPX_Log::LV_HARD_DEBUG) ? 10000 : 200;
$message = 'ENGINE: serialize data serial check error'.
"\n\tDATA: ".mb_strimwidth($edited_data, 0, $trimLen, ' [...]');
DUPX_Log::info($message);
$serial_err = true;
$rowErrors[$column] = $message;
}
}
}
}
}
//Base 64 encode
if ($base64converted) {
$edited_data = base64_encode($edited_data);
}
//Change was made
if ($serial_err == false && $edited_data != $originalData) {
$s3Funcs->report['updt_cells']++;
$upd_col[] = $safe_column;
$upd_sql[] = $safe_column.' = "'.mysqli_real_escape_string($dbh, $edited_data).'"';
$upd = true;
}
if ($primary_key) {
$where_sql[] = $safe_column.' = "'.mysqli_real_escape_string($dbh, $originalData).'"';
}
}
foreach ($rowErrors as $errCol => $msgCol) {
$longMsg = $msgCol."\n\tTABLE:".$rowsParams['table'].' COLUMN: '.$errCol.' WHERE: '.implode(' AND ', array_filter($where_sql));
$s3Funcs->report['errser'][] = $longMsg;
$nManager->addFinalReportNotice(array(
'shortMsg' => 'DATA-REPLACE ERROR: Serialization',
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => $longMsg,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_PRE,
'sections' => 'search_replace'
));
}
//PERFORM ROW UPDATE
if ($upd && !empty($where_sql)) {
$sql = "UPDATE `{$rowsParams['table']}` SET ".implode(', ', $upd_sql).' WHERE '.implode(' AND ', array_filter($where_sql));
$result = DUPX_DB::mysqli_query($dbh, $sql, __FILE__, __LINE__);
if ($result) {
$s3Funcs->report['updt_rows']++;
$rowsParams['updated'] = true;
} else {
$errMsg = mysqli_error($dbh)."\n\tTABLE:".$rowsParams['table'].' COLUMN: '.$errCol.' WHERE: '.implode(' AND ', array_filter($where_sql));
$s3Funcs->report['errsql'][] = ($GLOBALS['LOGGING'] == 1) ? 'DB ERROR: '.$errMsg : 'DB ERROR: '.$errMsg."\nSQL: [{$sql}]\n";
$nManager->addFinalReportNotice(array(
'shortMsg' => 'DATA-REPLACE ERRORS: MySQL',
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => $errMsg,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_PRE,
'sections' => 'search_replace'
));
}
} elseif ($upd) {
$errMsg = sprintf("Row [%s] on Table [%s] requires a manual update.", $rowsParams['current_row'], $rowsParams['table']);
$s3Funcs->report['errkey'][] = $errMsg;
$nManager->addFinalReportNotice(array(
'shortMsg' => 'DATA-REPLACE ERROR: Key',
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => $errMsg,
'sections' => 'search_replace'
));
}
return $rowsParams['updated'];
}
private static function getColumnsSearchReplaceList($table, $columns)
{
// PREPARE SEARCH AN REPLACE LISF FOR TABLES
$srManager = DUPX_S_R_MANAGER::getInstance();
$searchList = array();
$replaceList = array();
$list = $srManager->getSearchReplaceList($table);
foreach ($list as $item) {
$searchList[] = $item['search'];
$replaceList[] = $item['replace'];
}
$columnsSRList = array();
foreach ($columns as $column => $primary_key) {
if (($cScope = self::getSearchReplaceCustomScope($table, $column)) === false) {
// if don't have custom scope get normal search and reaplce table list
$columnsSRList[$column] = array(
'list' => &$list,
'sList' => &$searchList,
'rList' => &$replaceList,
'exactMatch' => false
);
} else {
// if column have custom scope overvrite default table search/replace list
$columnsSRList[$column] = array(
'list' => $srManager->getSearchReplaceList($cScope, true, false),
'sList' => array(),
'rList' => array(),
'exactMatch' => self::isExactMatch($table, $column)
);
foreach ($columnsSRList[$column]['list'] as $item) {
$columnsSRList[$column]['sList'][] = $item['search'];
$columnsSRList[$column]['rList'][] = $item['replace'];
}
}
}
return $columnsSRList;
}
/**
* searches and replaces strings without deserializing
* recursion for arrays
*
* @param array $search
* @param array $replace
* @param mixed $data
*
* @return mixed
*/
public static function searchAndReplaceItems($search, $replace, $data)
{
if (empty($data) || is_numeric($data) || is_bool($data) || is_callable($data)) {
/* do nothing */
} else if (is_string($data)) {
// Multiple replace string. If the string is serialized will fixed with fixSerialString
$data = str_replace($search, $replace, $data);
} else if (is_array($data)) {
$_tmp = array();
foreach ($data as $key => $value) {
// prevent recursion overhead
if (empty($value) || is_numeric($value) || is_bool($value) || is_callable($value) || is_object($data)) {
$_tmp[$key] = $value;
} else {
$_tmp[$key] = self::searchAndReplaceItems($search, $replace, $value, false);
}
}
$data = $_tmp;
unset($_tmp);
} elseif (is_object($data)) {
// it can never be an object type
DUPX_Log::info("OBJECT DATA IMPOSSIBLE\n");
}
return $data;
}
/**
* FROM WORDPRESS
* Check value to find if it was serialized.
*
* If $data is not an string, then returned value will always be false.
* Serialized data is always a string.
*
* @since 2.0.5
*
* @param string $data Value to check to see if was serialized.
* @param bool $strict Optional. Whether to be strict about the end of the string. Default true.
* @return bool False if not serialized and true if it was.
*/
public static function is_serialized_string($data, $strict = true)
{
// if it isn't a string, it isn't serialized.
if (!is_string($data)) {
return false;
}
$data = trim($data);
if ('N;' == $data) {
return true;
}
if (strlen($data) < 4) {
return false;
}
if (':' !== $data[1]) {
return false;
}
if ($strict) {
$lastc = substr($data, -1);
if (';' !== $lastc && '}' !== $lastc) {
return false;
}
} else {
$semicolon = strpos($data, ';');
$brace = strpos($data, '}');
// Either ; or } must exist.
if (false === $semicolon && false === $brace) {
return false;
}
// But neither must be in the first X characters.
if (false !== $semicolon && $semicolon < 3) {
return false;
}
if (false !== $brace && $brace < 4) {
return false;
}
}
$token = $data[0];
switch ($token) {
case 's' :
if ($strict) {
if ('"' !== substr($data, -2, 1)) {
return false;
}
} elseif (false === strpos($data, '"')) {
return false;
}
// or else fall through
case 'a' :
case 'O' :
return (bool) preg_match("/^{$token}:[0-9]+:/s", $data);
case 'b' :
case 'i' :
case 'd' :
$end = $strict ? '$' : '';
return (bool) preg_match("/^{$token}:[0-9.E-]+;$end/", $data);
}
return false;
}
/**
* Test if a string in properly serialized
*
* @param string $data Any string type
*
* @return bool Is the string a serialized string
*/
public static function unserializeTest($data)
{
if (!is_string($data)) {
return false;
} else if ($data === 'b:0;') {
return true;
} else {
try {
DUPX_Handler::setMode(DUPX_Handler::MODE_OFF);
$unserialize_ret = @unserialize($data);
DUPX_Handler::setMode();
return ($unserialize_ret !== false);
}
catch (Exception $e) {
DUPX_Log::info("Unserialize exception: ".$e->getMessage());
//DEBUG ONLY:
DUPX_Log::info("Serialized data\n".$data, DUPX_Log::LV_DEBUG);
return false;
}
}
}
/**
* custom columns list
* if the table / column pair exists in this array then the search scope will be overwritten with that contained in the array
*
* @var array
*/
private static $customScopes = array(
'signups' => array(
'domain' => array(
'scope' => 'domain_host',
'exact' => true
),
'path' => array(
'scope' => 'domain_path',
'exact' => true
)
),
'site' => array(
'domain' => array(
'scope' => 'domain_host',
'exact' => true
),
'path' => array(
'scope' => 'domain_path',
'exact' => true
)
)
);
/**
*
* @param string $table
* @param string $column
* @return boolean|string false if custom scope not found or return custom scoper for table/column
*/
private static function getSearchReplaceCustomScope($table, $column)
{
if (strpos($table, $GLOBALS['DUPX_AC']->wp_tableprefix) !== 0) {
return false;
}
$table_key = substr($table, strlen($GLOBALS['DUPX_AC']->wp_tableprefix));
if (!array_key_exists($table_key, self::$customScopes)) {
return false;
}
if (!array_key_exists($column, self::$customScopes[$table_key])) {
return false;
}
return self::$customScopes[$table_key][$column]['scope'];
}
/**
*
* @param string $table
* @param string $column
* @return boolean if true search a exact match in column if false search as LIKE
*/
private static function isExactMatch($table, $column)
{
if (strpos($table, $GLOBALS['DUPX_AC']->wp_tableprefix) !== 0) {
return false;
}
$table_key = substr($table, strlen($GLOBALS['DUPX_AC']->wp_tableprefix));
if (!array_key_exists($table_key, self::$customScopes)) {
return false;
}
if (!array_key_exists($column, self::$customScopes[$table_key])) {
return false;
}
return self::$customScopes[$table_key][$column]['exact'];
}
/**
* Fixes the string length of a string object that has been serialized but the length is broken
*
* @param string $data The string object to recalculate the size on.
*
* @return string A serialized string that fixes and string length types
*/
public static function fixSerialString($data)
{
$result = array(
'data' => null,
'fixed' => false,
'tried' => false
);
// check if serialized string must be fixed
if (self::unserializeTest($data)) {
$result['data'] = $data;
$result['fixed'] = true;
} else {
$result['tried'] = true;
$serialized_fixed = self::recursiveFixSerialString($data);
if (self::unserializeTest($serialized_fixed)) {
$result['data'] = $serialized_fixed;
$result['fixed'] = true;
} else {
$result['fixed'] = false;
}
}
return $result;
}
/**
* Fixes the string length of a string object that has been serialized but the length is broken
* Work on nested serialized string recursively.
*
* @param string $data The string ojbect to recalculate the size on.
*
* @return string A serialized string that fixes and string length types
*/
public static function recursiveFixSerialString($data)
{
if (!self::is_serialized_string($data)) {
return $data;
}
$result = '';
$matches = null;
$openLevel = 0;
$openContent = '';
$openContentL2 = '';
// parse every char
for ($i = 0; $i < strlen($data); $i++) {
$cChar = $data[$i];
$addChar = true;
if ($cChar == 's') {
// test if is a open string
if (preg_match(self::SERIALIZE_OPEN_STR_REGEX, substr($data, $i, self::SERIALIZE_OPEN_SUBSTR_LEN), $matches)) {
if ($openLevel > 1) {
$openContentL2 .= $matches[0];
}
$addChar = false;
$openLevel++;
$i += strlen($matches[0]) - 1;
}
} else if ($openLevel > 0 && $cChar == '"') {
// test if is a close string
if (preg_match(self::SERIALIZE_CLOSE_STR_REGEX, substr($data, $i, self::SERIALIZE_CLOSE_SUBSTR_LEN))) {
$addChar = false;
switch ($openLevel) {
case 1:
// level 1
// flush string content
$result .= 's:'.strlen($openContent).':"'.$openContent.'";';
$openContent = '';
break;
case 2;
// level 2
// fix serial string level2
$sublevelstr = self::recursiveFixSerialString($openContentL2);
// flush content on level 1
$openContent .= 's:'.strlen($sublevelstr).':"'.$sublevelstr.'";';
$openContentL2 = '';
break;
default:
// level > 2
// keep writing at level 2; it will be corrected with recursion
$openContentL2 .= self::SERIALIZE_CLOSE_STR;
break;
}
$openLevel--;
$i += self::SERIALIZE_CLOSE_STR_LEN - 1;
}
}
if ($addChar) {
switch ($openLevel) {
case 0:
// level 0
// add char on result
$result .= $cChar;
break;
case 1:
// level 1
// add char on content level1
$openContent .= $cChar;
break;
default:
// level > 1
// add char on content level2
$openContentL2 .= $cChar;
break;
}
}
}
return $result;
}
}