permissions('maintenance', CC_PERM_EDIT, true);
function imagesToFolders() {
$image_path = 'images/source/';
$image_path_dest = $image_path.'a-z/';
mkdir($image_path_dest);
if(file_exists($image_path_dest)) {
foreach (glob($image_path.'*') as $filename) {
if(is_file($filename)) {
$base_name = basename($filename);
$folder_name = strtoupper(substr($base_name,0,1));
$folder_path = $image_path_dest.$folder_name;
if(!file_exists($folder_path)) {
mkdir($folder_path);
}
echo $filename.' to '.$folder_path.'/'.$base_name.'
';
rename($filename,$folder_path.'/'.$base_name);
}
}
$files = $GLOBALS['db']->select('CubeCart_filemanager', false,array('filepath' => null));
foreach($files as $file) {
$folder = strtoupper(substr($file['filename'], 0, 1));
$GLOBALS['db']->update('CubeCart_filemanager', array('filepath' => 'a-z/'.$folder.'/'), array('file_id' => $file['file_id'], 'filepath' => null));
}
}
}
function crc_integrity_check($files, $mode = 'upgrade')
{
$errors = array();
$log_path = CC_BACKUP_DIR.$mode.'_error_log';
if (file_exists($log_path)) {
unlink($log_path);
}
foreach ($files as $file => $value) {
if (!file_exists($file)) {
$errors[] = "$file - Missing but expected after extract";
} elseif (is_file($file)) {
## Open the source file
if (($v_file = fopen($file, "rb")) == 0) {
$errors[] = "$file - Unable to read in order to validate integrity";
}
## Read the file content
$filesize = filesize($file);
$v_content = ((int)$filesize > 0) ? fread($v_file, $filesize) : '';
fclose($v_file);
if (crc32($v_content) !== $value) {
$errors[] = "$file - Content after extract doesn't match source";
}
}
}
if (count($errors)>0) {
$errors[] = '--';
$errors[] = 'Errors were found which may indicate that the source archive has not been extracted successfully.';
$errors[] = 'It is recommended that a manual '.$mode.' is performed.';
$error_data = "### START ".strtoupper($mode)." LOG - (".date("d M Y - H:i:s").") ###\r\n";
$error_data .= implode("\r\n", $errors);
$error_data .= "\r\n### END RESTORE LOG ###";
$fp = fopen($log_path, 'w');
fwrite($fp, $error_data);
fclose($fp);
} else {
return false;
}
}
$versions = $GLOBALS['db']->select('CubeCart_history');
$version_history = array();
if ($versions) {
foreach ($versions as $version) {
$release_notes_path = CC_ROOT_DIR.'/'.$GLOBALS['config']->get('config', 'adminFolder').'/sources/release_notes/'.$version['version'].'.inc.php';
$version_history[$version['version']] = array(
'time' => formatTime($version['time']),
'version' => file_exists($release_notes_path) ? ''.$version['version'].'' : $version['version']
);
}
}
krsort($version_history, SORT_NATURAL);
$GLOBALS['smarty']->assign('VERSIONS', $version_history);
if (isset($_GET['compress']) && !empty($_GET['compress'])) {
chdir(CC_BACKUP_DIR);
$file_path = './'.basename($_GET['compress']);
$zip = new ZipArchive;
if (file_exists($file_path) && $zip->open($file_path.'.zip', ZipArchive::CREATE)==true) {
$zip->addFile($file_path);
$zip->close();
$GLOBALS['main']->successMessage(sprintf($lang['maintain']['file_compressed'], basename($file_path)));
httpredir('?_g=maintenance&node=index', 'backup');
} else {
$GLOBALS['main']->errorMessage("Error reading file ".basename($file_path));
}
}
if (isset($_GET['restore']) && !empty($_GET['restore'])) {
// Prevent user stopping process
ignore_user_abort(true);
// Set max execution time to three minutes
set_time_limit(180);
$file_name = basename($_GET['restore']);
$file_path = CC_BACKUP_DIR.$file_name;
if (preg_match('/^database_full/', $file_name)) { // Restore database
$delete_source = false;
if (preg_match('/\.sql.zip$/', $file_name)) { // unzip first
$zip = new ZipArchive;
if ($zip->open($file_path) === true) {
$file_path = rtrim($file_path, '.zip');
// Only delete if it diesn't exist before
$delete_source = file_exists($file_path) ? false : true;
$zip->extractTo(CC_BACKUP_DIR);
$zip->close();
} else {
$GLOBALS['main']->errorMessage("Error reading file ".$file_name);
httpredir('?_g=maintenance&node=index', 'backup');
}
}
$handle = fopen($file_path, "r");
$import = false;
$GLOBALS['debug']->status(false); // This prevents memory errors
if ($handle) {
$sql = '';
while (($buffer = fgets($handle)) !== false) {
$sql .= $buffer;
if (substr(trim($buffer), -4) === '#EOQ') {
if ($GLOBALS['db']->parseSchema($sql)) {
$import = true;
}
$sql = '';
}
}
fclose($handle);
}
if ($delete_source) {
unlink($file_path);
}
if ($import) {
$GLOBALS['main']->successMessage($lang['maintain']['db_restored']);
$GLOBALS['cache']->clear();
httpredir('?_g=maintenance&node=index', 'backup');
}
} elseif (preg_match('/^files/', $file_name)) { // restore archive
$file_path = CC_BACKUP_DIR.$file_name;
$zip = new ZipArchive;
if ($zip->open($file_path) === true) {
$crc_check_list = array();
for ($i = 0; $i < $zip->numFiles; $i++) {
$stat = $zip->statIndex($i);
$crc_check_list[$stat['name']] = $stat['crc'];
}
$zip->extractTo(CC_ROOT_DIR);
$zip->close();
$errors = crc_integrity_check($crc_check_list, 'restore');
if ($errors!==false) {
$GLOBALS['main']->errorMessage($lang['maintain']['files_restore_fail']);
httpredir('?_g=maintenance&node=index', 'backup');
} else {
$GLOBALS['main']->successMessage($lang['maintain']['files_restore_success']);
httpredir('?_g=maintenance&node=index', 'backup');
}
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['files_restore_not_possible']);
}
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['files_restore_not_possible']);
httpredir('?_g=maintenance&node=index', 'backup');
}
}
if (isset($_GET['upgrade']) && !empty($_GET['upgrade'])) {
$contents = false;
$upgrade_version = $_GET['upgrade'];
## Download from GitHub
$download_url = '/cubecart/v6/legacy.zip/refs/tags/'.$upgrade_version;
$request = new Request('codeload.github.com', $download_url, 80, false, true, 120);
$request->setMethod('get');
$request->setSSL();
$request->setUserAgent('CubeCart/'.CC_VERSION);
$request->skiplog(true);
$request->customOption(CURLOPT_FOLLOWLOCATION, true);
if (!$contents = $request->send()) {
$contents = file_get_contents('https://codeload.github.com'.$download_url);
}
if (empty($contents)) {
$GLOBALS['main']->errorMessage($lang['maintain']['files_upgrade_download_fail']);
httpredir('?_g=maintenance&node=index', 'upgrade');
} else {
if (stristr($contents, 'DOCTYPE') || stristr($contents, 'Not Found')) {
$GLOBALS['main']->errorMessage("Sorry. CubeCart ".$upgrade_version." was not found on GitHub. Please try again later.");
httpredir('?_g=maintenance&node=index', 'upgrade');
}
$destination_path = CC_BACKUP_DIR.'CubeCart-'.$upgrade_version.'.zip';
$fp = fopen($destination_path, 'w');
fwrite($fp, $contents);
fclose($fp);
if (file_exists($destination_path)) {
$zip = new ZipArchive;
if ($zip->open($destination_path) === true) {
$crc_check_list = array();
## Detect GitHub zip prefix directory (e.g. "cubecart-v6-6503165/")
$first_entry = $zip->statIndex(0);
$zip_prefix = (substr($first_entry['name'], -1) === '/') ? $first_entry['name'] : '';
## Extract files individually, stripping prefix
for ($i = 0; $i < $zip->numFiles; $i++) {
$stat = $zip->statIndex($i);
$name = $stat['name'];
## Strip prefix directory
if (!empty($zip_prefix) && strpos($name, $zip_prefix) === 0) {
$name = substr($name, strlen($zip_prefix));
}
if (empty($name)) continue;
## Build CRC check list with admin folder rename
if (preg_match("#^admin/#", $name)) {
$custom_file_name = preg_replace("#^admin#", $glob['adminFolder'], $name);
} elseif ($name == 'admin.php') {
$custom_file_name = $glob['adminFile'];
} else {
$custom_file_name = $name;
}
$crc_check_list[$custom_file_name] = $stat['crc'];
## Write file to target location
$target = CC_ROOT_DIR.'/'.$name;
if (substr($name, -1) === '/') {
if (!is_dir($target)) mkdir($target, 0755, true);
} else {
$dir = dirname($target);
if (!is_dir($dir)) mkdir($dir, 0755, true);
file_put_contents($target, $zip->getFromIndex($i));
}
}
$zip->close();
$suffix = '-'.(string)time();
rename(CC_ROOT_DIR.'/'.$glob['adminFolder'], CC_ROOT_DIR.'/'.$glob['adminFolder'].$suffix);
rename(CC_ROOT_DIR.'/'.$glob['adminFile'], CC_ROOT_DIR.'/'.$glob['adminFile'].$suffix);
rename(CC_ROOT_DIR.'/admin', CC_ROOT_DIR.'/'.$glob['adminFolder']);
rename(CC_ROOT_DIR.'/admin.php', CC_ROOT_DIR.'/'.$glob['adminFile']);
unlink(CC_ROOT_DIR.'/'.$glob['adminFile'].$suffix);
recursiveDelete(CC_ROOT_DIR.'/'.$glob['adminFolder'].$suffix);
$errors = crc_integrity_check($crc_check_list, 'upgrade');
if ($errors!==false) {
$GLOBALS['main']->errorMessage($lang['maintain']['files_upgrade_fail']);
httpredir('?_g=maintenance&node=index', 'upgrade');
} elseif ($_POST['force']) {
## Try to delete setup folder
recursiveDelete(CC_ROOT_DIR.'/setup');
## If that fails we try an obscure rename
if (file_exists(CC_ROOT_DIR.'/setup')) {
rename(CC_ROOT_DIR.'/setup', CC_ROOT_DIR.'/setup'.$suffix);
}
$GLOBALS['main']->successMessage($lang['maintain']['current_version_restored']);
httpredir('?_g=maintenance&node=index', 'upgrade');
} else {
httpredir(CC_ROOT_REL.'setup/index.php?autoupdate=1');
}
} else {
$GLOBALS['main']->errorMessage("Unable to read archive.");
httpredir('?_g=maintenance&node=index', 'upgrade');
}
}
}
}
if (isset($_GET['delete'])) {
$file = 'backup/'.basename($_GET['delete']);
if (in_array($_GET['delete'], array('restore_error_log','upgrade_error_log'))) {
unlink($file);
switch ($_GET['delete']) {
case 'upgrade_error_log':
$anchor = 'upgrade';
break;
case 'restore_error_log':
$anchor = 'backup';
break;
}
httpredir('?_g=maintenance&node=index', $anchor);
} elseif (file_exists($file) && preg_match('/^.*\.(sql|zip)$/i', $file)) {
## Generic error message for logs delete specific for backup
$message = preg_match('/\_error_log$/', $file) ? $lang['filemanager']['notify_file_delete'] : sprintf($lang['maintain']['backup_deleted'], basename($file));
$GLOBALS['main']->successMessage($message);
unlink($file);
httpredir('?_g=maintenance&node=index', 'backup');
}
}
if (isset($_GET['download'])) {
$file = 'backup/'.basename($_GET['download']);
if (file_exists($file)) {
deliverFile($file);
httpredir('?_g=maintenance&node=index', 'backup');
}
}
########## Rebuild ##########
$clear_post = false;
if (isset($_POST['clear_sessions'])) {
if ($GLOBALS['db']->truncate('CubeCart_sessions')) {
$GLOBALS['main']->successMessage($lang['maintain']['sessions_cleared']);
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['sessions_not_cleared']);
}
$clear_post = true;
}
if (isset($_POST['clearCookieConsent'])) {
if ($GLOBALS['db']->truncate('CubeCart_cookie_consent')) {
$GLOBALS['db']->truncate('CubeCart_cookie_consent_text');
$GLOBALS['main']->successMessage($lang['maintain']['cookie_consent_cleared']);
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['cookie_consent_not_cleared']);
}
$clear_post = true;
}
if (isset($_POST['truncate_seo_custom'])) {
if ($GLOBALS['db']->delete('CubeCart_seo_urls', array('custom' => 1))) {
$GLOBALS['main']->successMessage($lang['maintain']['seo_urls_emptied']);
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['seo_urls_not_emptied']);
}
$clear_post = true;
}
if (isset($_POST['truncate_seo_auto'])) {
if ($GLOBALS['db']->delete('CubeCart_seo_urls', array('custom' => 0))) {
$GLOBALS['main']->successMessage($lang['maintain']['seo_urls_emptied']);
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['seo_urls_not_emptied']);
}
$clear_post = true;
}
if (isset($_POST['sitemap'])) {
if ($GLOBALS['seo']->sitemap()) {
$GLOBALS['main']->successMessage($lang['maintain']['notify_sitemap']);
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['notify_sitemap_fail']);
}
$clear_post = true;
}
if (isset($_REQUEST['emptyTransLogs']) && Admin::getInstance()->permissions('maintenance', CC_PERM_DELETE)) {
if ($GLOBALS['db']->truncate('CubeCart_transactions')) {
$GLOBALS['main']->successMessage($lang['maintain']['notify_logs_transaction']);
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['error_logs_transaction']);
}
$clear_post = true;
if(isset($_GET['redir']) && $_GET['redir']=='transactions') {
httpredir('?_g=orders&node=transactions');
exit;
}
}
if (isset($_REQUEST['emptyEmailLogs']) && Admin::getInstance()->permissions('maintenance', CC_PERM_DELETE)) {
if ($GLOBALS['db']->truncate(array('CubeCart_email_log'))) {
$GLOBALS['main']->successMessage($lang['maintain']['notify_logs_email']);
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['error_logs_email']);
}
$clear_post = true;
if(isset($_GET['redir']) && $_GET['redir']=='emaillog') {
httpredir('?_g=statistics&node=emaillog');
exit;
}
}
if (isset($_REQUEST['emptyErrorLogs']) && Admin::getInstance()->permissions('maintenance', CC_PERM_DELETE)) {
if ($GLOBALS['db']->truncate(array('CubeCart_system_error_log', 'CubeCart_admin_error_log'))) {
$GLOBALS['main']->successMessage($lang['maintain']['notify_logs_error']);
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['error_logs_error']);
}
$clear_post = true;
if(isset($_GET['redir']) && $_GET['redir']=='viewlog') {
httpredir('?_g=settings&node=errorlog','system_error_log');
exit;
}
}
if (isset($_REQUEST['emptyRequestLogs']) && Admin::getInstance()->permissions('maintenance', CC_PERM_DELETE)) {
if ($GLOBALS['db']->truncate('CubeCart_request_log')) {
$GLOBALS['main']->successMessage($lang['maintain']['notify_logs_request']);
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['error_logs_request']);
}
$clear_post = true;
if(isset($_GET['redir']) && $_GET['redir']=='viewlog') {
httpredir('?_g=settings&node=requestlog');
exit;
}
}
if (isset($_REQUEST['clearSearch']) && Admin::getInstance()->permissions('maintenance', CC_PERM_DELETE)) {
if ($GLOBALS['db']->truncate('CubeCart_search')) {
$GLOBALS['main']->successMessage($lang['maintain']['notify_search_clear']);
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['error_search_clear']);
}
$clear_post = true;
if(isset($_GET['redir']) && $_GET['redir']=='searchlog') {
httpredir('?_g=statistics');
exit;
}
}
if (isset($_POST['clearCache']) && Admin::getInstance()->permissions('maintenance', CC_PERM_DELETE)) {
$GLOBALS['cache']->clear();
$GLOBALS['cache']->tidy();
$GLOBALS['main']->successMessage($lang['maintain']['notify_cache_cleared']);
$clear_post = true;
}
if (isset($_POST['clearImageCache']) && Admin::getInstance()->permissions('maintenance', CC_PERM_DELETE)) {
function cleanImageCache($path = null, $failed = array())
{
$path = (isset($path) && is_dir($path)) ? $path : CC_ROOT_DIR.'/images/cache/';
$scan = glob($path.'*', GLOB_MARK);
if (is_array($scan) && !empty($scan)) {
foreach ($scan as $result) {
if (is_dir($result)) {
cleanImageCache($result);
if(!rmdir($result)) {
$failed[] = str_replace(CC_ROOT_DIR.'/images/cache/','',$result);
}
} else {
if(!unlink($result)) {
$failed[] = str_replace(CC_ROOT_DIR.'/images/cache/','',$result);
}
}
}
}
return count(glob(CC_ROOT_DIR.'/images/cache/'.'*', GLOB_MARK)) > 0 ? $failed : true;
}
## recursively delete the contents of the images/cache folder
$clearImageCache = cleanImagecache();
if($clearImageCache===true) {
$GLOBALS['main']->successMessage($lang['maintain']['notify_cache_image']);
} else if(is_array($clearImageCache)) {
foreach($clearImageCache as $file) {
$GLOBALS['main']->errorMessage(sprintf($lang['maintain']['notify_failed_to_delete'], $file));
}
}
$clear_post = true;
}
if (isset($_POST['prodViews'])) {
if ($GLOBALS['db']->update('CubeCart_inventory', array('popularity' => 0), '', true)) {
$GLOBALS['main']->successMessage($lang['maintain']['notify_reset_product']);
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['error_reset_product']);
}
$clear_post = true;
}
if (isset($_REQUEST['clearLogs'])) {
if ($GLOBALS['db']->truncate(array('CubeCart_admin_log', 'CubeCart_access_log'))) {
$GLOBALS['main']->successMessage($lang['maintain']['notify_logs_admin']);
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['error_logs_admin']);
}
$clear_post = true;
if(isset($_GET['redir']) && $_GET['redir']=='viewlog') {
httpredir('?_g=settings&node=errorlog');
exit;
}
if(isset($_GET['redir']) && $_GET['redir']=='adminlogs') {
httpredir('?_g=settings&node=logs');
exit;
}
}
########## Database ##########
if (!empty($_POST['database'])) {
if (is_array($_POST['tablename'])) {
foreach ($_POST['tablename'] as $value) {
$tableList[] = sprintf('`%s`', $value);
}
if(in_array($_POST['action'], array('REBUILD','CHECK','ANALYZE'))) {
if ($_POST['action'] === 'REBUILD') {
foreach ($tableList as $table) {
$GLOBALS['db']->query(sprintf("ALTER TABLE %s ENGINE=InnoDB;", $table));
}
} else {
$GLOBALS['db']->query(sprintf("%s TABLE %s;", $_POST['action'], implode(',', $tableList)));
}
$GLOBALS['main']->successMessage(sprintf($lang['maintain']['notify_db_action'], $_POST['action']));
} else {
die('Action not allowed.');
}
} else {
$GLOBALS['main']->errorMessage($lang['maintain']['db_none_selected']);
}
}
########## Backup ##########
if (isset($_GET['files_backup'])) {
$is_ajax = !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
// Prevent user stopping process
ignore_user_abort(true);
// Allow up to 1 hour — large stores with many images can take several minutes.
// Note: web-server level timeouts (nginx fastcgi_read_timeout, PHP-FPM
// request_terminate_timeout) are not affected and may also need raising.
set_time_limit(3600);
chdir(CC_ROOT_DIR);
$destination = CC_BACKUP_DIR.'files_'.CC_VERSION.'_'.date("dMy-His").'.zip';
// Detect a PHP execution timeout (E_ERROR fatal) in the shutdown handler.
// Web-server-level kills are not catchable, but the PHP-level timeout is.
register_shutdown_function(function () use ($destination) {
$error = error_get_last();
if ($error && $error['type'] === E_ERROR && strpos($error['message'], 'Maximum execution time') !== false) {
if (file_exists($destination)) {
unlink($destination); // remove the incomplete zip
}
}
});
// For AJAX requests, release the session and send response immediately
// so the user can navigate away while the backup continues.
if ($is_ajax) {
session_write_close();
$response = json_encode(array('status' => 'started'));
header('Content-Type: application/json');
header('Content-Length: ' . strlen($response));
header('Connection: close');
while (ob_get_level()) ob_end_flush();
echo $response;
flush();
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
}
}
$zip = new ZipArchive();
$zip = new ZipArchive();
if ($zip->open($destination, ZipArchive::CREATE)!==true) {
if ($is_ajax) {
$mailer = new Mailer();
$mailer->sendEmail($GLOBALS['config']->get('config', 'email_address'), array(
'subject' => sprintf($lang['maintain']['backup_failed_email_subject'], ucwords($lang['maintain']['title_files_backup'])),
'content_html' => '
'.sprintf($lang['maintain']['backup_failed_email_body'], strtolower($lang['maintain']['title_files_backup'])).'
', )); } else { $GLOBALS['main']->errorMessage("Error: Backup failed."); } } else { $cache_folder = basename(CC_CACHE_DIR); $backup_folder = basename(CC_BACKUP_DIR); $files_folder = basename(CC_FILES_DIR); $skip_folders = $backup_folder.'|'.$cache_folder.'|images/cache|includes/extra/sess_'; if (isset($_POST['skip_images']) && $_POST['skip_images']=='1') { $zip->addEmptyDir('./images/source'); $skip_folders .= '|images/source'; } if (isset($_POST['skip_downloads']) && $_POST['skip_downloads']=='1') { $zip->addEmptyDir('./'.$files_folder); if (file_exists('./'.$files_folder.'/.htaccess')) { $zip->addFile('./'.$files_folder.'/.htaccess'); } $skip_folders .= '|'.$files_folder; } $zip->addEmptyDir('./'.$backup_folder); if (file_exists('./'.$backup_folder.'/.htaccess')) { $zip->addFile('./'.$backup_folder.'/.htaccess'); } $zip->addEmptyDir('./'.$cache_folder); if (file_exists('./'.$cache_folder.'/.htaccess')) { $zip->addFile('./'.$cache_folder.'/.htaccess'); } $zip->addEmptyDir('./images/cache'); // Use a lazy RecursiveDirectoryIterator instead of glob_recursive() to // avoid loading the entire file-tree into a PHP array before we start. // The RecursiveCallbackFilterIterator prunes skip-listed directories so // we never even descend into them. $skip_pattern = '#^('.$skip_folders.')#'; $dir_iter = new RecursiveDirectoryIterator('.', RecursiveDirectoryIterator::SKIP_DOTS); $filter_iter = new RecursiveCallbackFilterIterator($dir_iter, function ($current) use ($skip_pattern) { if ($current->isDir()) { $path = ltrim(str_replace('\\', '/', $current->getPathname()), './'); return !preg_match($skip_pattern, $path); } return true; }); $iterator = new RecursiveIteratorIterator($filter_iter, RecursiveIteratorIterator::SELF_FIRST); foreach ($iterator as $fileInfo) { $path = ltrim(str_replace('\\', '/', $fileInfo->getPathname()), './'); if ($path === 'images' || preg_match($skip_pattern, $path)) { continue; } if ($fileInfo->isDir()) { $zip->addEmptyDir('./'.$path); } else { $zip->addFile($fileInfo->getPathname(), './'.$path); } } $zip->close(); if ($is_ajax) { $backup_file = basename($destination); $backup_size = formatBytes(filesize($destination), true); $backup_url = $GLOBALS['storeURL'].'/'.$GLOBALS['config']->get('config', 'adminFile').'?_g=maintenance&node=index#backup'; $type_label = strtolower($lang['maintain']['title_files_backup']); $type_label_uc = ucwords($lang['maintain']['title_files_backup']); $mailer = new Mailer(); $mailer->sendEmail($GLOBALS['config']->get('config', 'email_address'), array( 'subject' => sprintf($lang['maintain']['backup_complete_email_subject'], $type_label_uc), 'content_html' => ''.sprintf($lang['maintain']['backup_complete_email_body'], $type_label).'
' .''.sprintf($lang['maintain']['backup_complete_email_filename'], $backup_file).'
'
.sprintf($lang['maintain']['backup_complete_email_size'], $backup_size).'
'.sprintf($lang['maintain']['backup_complete_email_download'], ''.$backup_url.'').'
', )); } else { $GLOBALS['main']->successMessage($lang['maintain']['files_backup_complete']); } } if ($is_ajax) { exit; } httpredir('?_g=maintenance&node=index', 'backup'); } if (isset($_POST['backup'])) { $is_ajax = !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; // Prevent user stopping process ignore_user_abort(true); // Allow up to 1 hour — large databases can take several minutes. // Note: web-server level timeouts (nginx fastcgi_read_timeout, PHP-FPM // request_terminate_timeout) are not affected and may also need raising. set_time_limit(3600); if (!$_POST['drop'] && !$_POST['structure'] && !$_POST['data']) { if ($is_ajax) { header('Content-Type: application/json'); echo json_encode(array('status' => 'error', 'message' => $lang['maintain']['error_db_backup_option'])); exit; } $GLOBALS['main']->errorMessage($lang['maintain']['error_db_backup_option']); } else { if ($_POST['drop'] && !$_POST['structure']) { if ($is_ajax) { header('Content-Type: application/json'); echo json_encode(array('status' => 'error', 'message' => $lang['maintain']['error_db_backup_conflict'])); exit; } $GLOBALS['main']->errorMessage($lang['maintain']['error_db_backup_conflict']); } else { $full = ($_POST['drop'] && $_POST['structure'] && $_POST['data']) ? '_full' : ''; chdir(CC_BACKUP_DIR); $fileName = 'database'.$full.'_'.CC_VERSION.'_'.$glob['dbdatabase']."_".date("dMy-His").'.sql'; if (file_exists($fileName)) { // Keep file pointer at the start unlink($fileName); } // Detect a PHP execution timeout (E_ERROR fatal) in the shutdown handler. register_shutdown_function(function () use ($fileName) { $error = error_get_last(); if ($error && $error['type'] === E_ERROR && strpos($error['message'], 'Maximum execution time') !== false) { if (file_exists($fileName)) { unlink($fileName); } if (file_exists($fileName.'.zip')) { unlink($fileName.'.zip'); } } }); // For AJAX requests, release the session and send response immediately // so the user can navigate away while the backup continues. if ($is_ajax) { session_write_close(); $response = json_encode(array('status' => 'started')); header('Content-Type: application/json'); header('Content-Length: ' . strlen($response)); header('Connection: close'); while (ob_get_level()) ob_end_flush(); echo $response; flush(); if (function_exists('fastcgi_finish_request')) { fastcgi_finish_request(); } } $all_tables = (isset($_POST['db_3rdparty']) && $_POST['db_3rdparty'] == '1') ? true : false; $write = $GLOBALS['db']->doSQLBackup($_POST['drop'], $_POST['structure'], $_POST['data'], $fileName, $_POST['compress'], $all_tables); if ($is_ajax) { $mailer = new Mailer(); $admin_email = $GLOBALS['config']->get('config', 'email_address'); if ($write) { $actual_file = $_POST['compress'] ? $fileName.'.zip' : $fileName; $backup_size = file_exists($actual_file) ? formatBytes(filesize($actual_file), true) : ''; $backup_url = $GLOBALS['storeURL'].'/'.$GLOBALS['config']->get('config', 'adminFile').'?_g=maintenance&node=index#backup'; $type_label = strtolower($lang['maintain']['title_db_backup']); $type_label_uc = ucwords($lang['maintain']['title_db_backup']); $mailer->sendEmail($admin_email, array( 'subject' => sprintf($lang['maintain']['backup_complete_email_subject'], $type_label_uc), 'content_html' => ''.sprintf($lang['maintain']['backup_complete_email_body'], $type_label).'
' .''.sprintf($lang['maintain']['backup_complete_email_filename'], basename($actual_file)).'
'
.sprintf($lang['maintain']['backup_complete_email_size'], $backup_size).'
'.sprintf($lang['maintain']['backup_complete_email_download'], ''.$backup_url.'').'
', )); } else { $mailer->sendEmail($admin_email, array( 'subject' => sprintf($lang['maintain']['backup_failed_email_subject'], ucwords($lang['maintain']['title_db_backup'])), 'content_html' => ''.sprintf($lang['maintain']['backup_failed_email_body'], strtolower($lang['maintain']['title_db_backup'])).'
', )); } } else { if ($write) { $GLOBALS['main']->successMessage($lang['maintain']['db_backup_complete']); } else { $GLOBALS['main']->errorMessage($lang['maintain']['db_backup_failed']); } } } if ($is_ajax) { exit; } $clear_post = true; } } if ($clear_post) { httpredir(currentPage(array('clearLogs', 'emptyErrorLogs'))); exit; } ########## Tabs ########## $GLOBALS['main']->addTabControl($lang['maintain']['tab_rebuild'], 'rebuild'); $GLOBALS['main']->addTabControl($lang['maintain']['tab_backup'], 'backup'); $GLOBALS['main']->addTabControl($lang['common']['upgrade'], 'upgrade'); $GLOBALS['main']->addTabControl($lang['maintain']['tab_db'], 'database'); $GLOBALS['main']->addTabControl($lang['maintain']['tab_elasticsearch'], 'elasticsearch'); if($GLOBALS['config']->get('config', 'elasticsearch')=='1') { $es = new ElasticsearchHandler; $GLOBALS['smarty']->assign('ES_STATS', $es->getStats()); } $GLOBALS['main']->addTabControl($lang['maintain']['tab_query_sql'], 'general', '?_g=maintenance&node=sql'); ########## ## Database if (isset($database_result) && $database_result) { $GLOBALS['smarty']->assign('TABLES_AFTER', $database_result); } elseif (($tables = $GLOBALS['db']->getRows()) !== false) { ## Parse structure.sql to build index map and column map (single source of truth) $index_map = array(); $full_indexes = array(); $column_map = array(); $structure_file = CC_ROOT_DIR.'/classes/db/schema/structure.sql'; if (file_exists($structure_file)) { $sql = file_get_contents($structure_file); $statements = preg_split('/;\s*#EOQ/i', $sql); ## Track all index types per column (a column can be in multiple indexes) foreach ($statements as $stmt) { ## Parse CREATE TABLE statements if (preg_match('/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?`?(\w+)`?/i', $stmt, $tbl_match)) { $table_name = strtolower($tbl_match[1]); if (!isset($index_map[$table_name])) { $index_map[$table_name] = array(); } ## Match index definitions including bare UNIQUE(...) and FULLTEXT(...) without KEY if (preg_match_all('/^\s*(PRIMARY\s+KEY|UNIQUE\s+KEY|FULLTEXT\s+KEY|UNIQUE|FULLTEXT|KEY)\s*(?:`?(\w+)`?\s*)?\(((?:[^()]+|\([^)]*\))+)\)/im', $stmt, $idx_matches, PREG_SET_ORDER)) { foreach ($idx_matches as $idx) { $raw_type = strtoupper(trim($idx[1])); if (strpos($raw_type, 'PRIMARY') !== false) { $key_type = 'PRIMARY'; } elseif (strpos($raw_type, 'UNIQUE') !== false) { $key_type = 'UNIQUE KEY'; } elseif (strpos($raw_type, 'FULLTEXT') !== false) { $key_type = 'FULLTEXT'; } else { $key_type = 'KEY'; } ## Extract column names, strip prefix lengths e.g. (150) and backticks $columns = explode(',', $idx[3]); foreach ($columns as $col) { $col = trim(str_replace('`', '', $col)); $col = trim(preg_replace('/\(\d+\)/', '', $col)); if (!empty($col)) { if (!isset($index_map[$table_name][$col])) { $index_map[$table_name][$col] = array(); } if (!in_array($key_type, $index_map[$table_name][$col])) { $index_map[$table_name][$col][] = $key_type; } } } ## Store full index definition for fix SQL generation $idx_key_name = ($key_type === 'PRIMARY') ? 'PRIMARY' : (!empty($idx[2]) ? $idx[2] : null); if ($idx_key_name !== null) { $full_indexes[$table_name][$idx_key_name] = array('type' => $key_type, 'columns' => $idx[3]); } } } ## Parse column definitions $column_map[$table_name] = array(); $stmt_lines = preg_split('/\r?\n/', $stmt); foreach ($stmt_lines as $line) { $line = trim($line); if (preg_match('/^`(\w+)`\s+(.+)$/', $line, $col_match)) { $col_name = $col_match[1]; $col_def = rtrim(rtrim($col_match[2]), ','); $column_map[$table_name][$col_name] = $col_def; } } } ## Parse ALTER TABLE ... ADD INDEX/KEY/PRIMARY KEY/UNIQUE statements if (preg_match('/ALTER\s+TABLE\s+`?(\w+)`?\s+ADD\s+(PRIMARY\s+KEY|UNIQUE\s+KEY|UNIQUE|FULLTEXT|INDEX|KEY)\s*(?:`?(\w+)`?\s*)?\(((?:[^()]+|\([^)]*\))+)\)/i', $stmt, $alt_match)) { $table_name = strtolower($alt_match[1]); if (!isset($index_map[$table_name])) { $index_map[$table_name] = array(); } $raw_type = strtoupper(trim($alt_match[2])); if (strpos($raw_type, 'PRIMARY') !== false) { $key_type = 'PRIMARY'; } elseif (strpos($raw_type, 'UNIQUE') !== false) { $key_type = 'UNIQUE KEY'; } elseif (strpos($raw_type, 'FULLTEXT') !== false) { $key_type = 'FULLTEXT'; } else { $key_type = 'KEY'; } $columns = explode(',', $alt_match[4]); foreach ($columns as $col) { $col = trim(str_replace('`', '', $col)); $col = trim(preg_replace('/\(\d+\)/', '', $col)); if (!empty($col)) { if (!isset($index_map[$table_name][$col])) { $index_map[$table_name][$col] = array(); } if (!in_array($key_type, $index_map[$table_name][$col])) { $index_map[$table_name][$col][] = $key_type; } } } ## Store full index definition for fix SQL generation $alt_key_name = ($key_type === 'PRIMARY') ? 'PRIMARY' : (!empty($alt_match[3]) ? $alt_match[3] : null); if ($alt_key_name !== null) { $full_indexes[$table_name][$alt_key_name] = array('type' => $key_type, 'columns' => $alt_match[4]); } } } } else { $GLOBALS['main']->errorMessage('Unable to read classes/db/schema/structure.sql.'); } $actual_map = array(); $all_fix_sql = array(); $found_tables = array(); foreach ($tables as $table) { if (!preg_match('/^'.$GLOBALS['config']->get('config', 'dbprefix').'CubeCart_/i', $table['Name'])) { continue; } $found_tables[] = strtolower(str_replace($GLOBALS['config']->get('config', 'dbprefix'), '', $table['Name'])); // Get index and map them $indexes = $GLOBALS['db']->misc("SHOW INDEX FROM `".$table['Name']."`"); $index_errors = array(); $actual_full_indexes = array(); foreach ($indexes as $index) { if ($index['Key_name']=='PRIMARY') { $key_type = 'PRIMARY'; } elseif ($index['Index_type'] == 'FULLTEXT') { $key_type = 'FULLTEXT'; } elseif ($index['Non_unique'] == '0') { $key_type = 'UNIQUE KEY'; } else { $key_type = 'KEY'; } $table_name = $GLOBALS['config']->get('config', 'dbprefix').str_replace('cubecart', 'CubeCart', $index['Table']); $duplicate = false; if (isset($actual_map[$index['Table']][$index['Column_name']]) && in_array($key_type, $actual_map[$index['Table']][$index['Column_name']])) { $duplicate = sprintf($lang['maintain']['duplicate_index'], $table_name.'.'.$index['Column_name'], $key_type); } if (!isset($actual_map[$index['Table']][$index['Column_name']])) { $actual_map[$index['Table']][$index['Column_name']] = array(); } if (!in_array($key_type, $actual_map[$index['Table']][$index['Column_name']])) { $actual_map[$index['Table']][$index['Column_name']][] = $key_type; } // Build full index definitions grouped by key name if (!isset($actual_full_indexes[$index['Key_name']])) { $actual_full_indexes[$index['Key_name']] = array('type' => $key_type, 'columns' => array()); } $actual_full_indexes[$index['Key_name']]['columns'][(int)$index['Seq_in_index']] = '`'.$index['Column_name'].'`'; } if (!empty($index_map) && isset($index_map[strtolower($index['Table'])])) { foreach ($index_map[strtolower($index['Table'])] as $column => $expected_types) { $table_name = $GLOBALS['config']->get('config', 'dbprefix').str_replace('cubecart', 'CubeCart', $index['Table']); if (!isset($actual_map[$index['Table']][$column])) { $index_errors[] = sprintf($lang['maintain']['missing_index'], $table_name.'.'.$column, implode(', ', $expected_types)); } else { $missing_types = array_diff($expected_types, $actual_map[$index['Table']][$column]); foreach ($missing_types as $missing_type) { $index_errors[] = sprintf($lang['maintain']['missing_index'], $table_name.'.'.$column, $missing_type); } } } } if ($duplicate !== false) { $index_errors[] = $duplicate; } // Check for missing columns $table_key = strtolower(str_replace($GLOBALS['config']->get('config', 'dbprefix'), '', $table['Name'])); if (isset($column_map[$table_key])) { $actual_columns = $GLOBALS['db']->misc("SHOW COLUMNS FROM `".$table['Name']."`"); $actual_col_names = array(); if ($actual_columns) { foreach ($actual_columns as $col) { $actual_col_names[] = strtolower($col['Field']); } } foreach ($column_map[$table_key] as $col_name => $col_def) { if (!in_array(strtolower($col_name), $actual_col_names)) { $index_errors[] = sprintf($lang['maintain']['missing_column'], $table['Name'], $col_name); $all_fix_sql[] = 'ALTER TABLE `'.$table['Name'].'` ADD `'.$col_name.'` '.$col_def.'; #EOQ'; } } } // Generate fix SQL by comparing full index definitions $table_lower = strtolower($index['Table']); if (!empty($full_indexes[$table_lower])) { $renamed_indexes = array(); foreach ($full_indexes[$table_lower] as $key_name => $expected_idx) { $fix_table = '`'.$table['Name'].'`'; if (!isset($actual_full_indexes[$key_name])) { // Index name missing - check if same type+columns exist under a different name $expected_cols_norm = array_map(function($c) { return strtolower(trim(preg_replace('/\(\d+\)/', '', str_replace('`', '', trim($c))))); }, explode(',', $expected_idx['columns'])); $old_name = null; foreach ($actual_full_indexes as $act_name => $act_idx) { if ($act_idx['type'] === $expected_idx['type']) { ksort($act_idx['columns']); $act_cols_norm = array_map(function($c) { return strtolower(trim(str_replace('`', '', $c))); }, array_values($act_idx['columns'])); if ($expected_cols_norm === $act_cols_norm) { $old_name = $act_name; break; } } } if ($key_name === 'PRIMARY') { $all_fix_sql[] = 'ALTER TABLE '.$fix_table.' ADD PRIMARY KEY ('.$expected_idx['columns']; #EOQ'); } elseif ($old_name !== null) { // Same index exists under wrong name - drop old, add with correct name $renamed_indexes[] = $old_name; $table_name = $GLOBALS['config']->get('config', 'dbprefix').str_replace('cubecart', 'CubeCart', $index['Table']); $index_errors[] = sprintf($lang['maintain']['wrong_index_name'], $table_name, $old_name, $key_name); $type_kw = ($expected_idx['type'] === 'FULLTEXT') ? 'FULLTEXT KEY' : $expected_idx['type']; $all_fix_sql[] = 'ALTER TABLE '.$fix_table.' DROP INDEX `'.$old_name.'`, ADD '.$type_kw.' `'.$key_name.'` ('.$expected_idx['columns'].'); #EOQ'; } else { $type_kw = ($expected_idx['type'] === 'FULLTEXT') ? 'FULLTEXT KEY' : $expected_idx['type']; $all_fix_sql[] = 'ALTER TABLE '.$fix_table.' ADD '.$type_kw.' `'.$key_name.'` ('.$expected_idx['columns'].'); #EOQ'; } } elseif ($actual_full_indexes[$key_name]['type'] !== $expected_idx['type']) { // Index exists but wrong type if ($key_name === 'PRIMARY') { $all_fix_sql[] = 'ALTER TABLE '.$fix_table.' DROP PRIMARY KEY, ADD PRIMARY KEY ('.$expected_idx['columns'].'); #EOQ'; } else { $type_kw = ($expected_idx['type'] === 'FULLTEXT') ? 'FULLTEXT KEY' : $expected_idx['type']; $all_fix_sql[] = 'ALTER TABLE '.$fix_table.' DROP INDEX `'.$key_name.'`, ADD '.$type_kw.' `'.$key_name.'` ('.$expected_idx['columns'].'); #EOQ'; } } else { // Index exists with correct type - check columns match $expected_cols = array_map(function($c) { return strtolower(trim(preg_replace('/\(\d+\)/', '', str_replace('`', '', trim($c))))); }, explode(',', $expected_idx['columns'])); ksort($actual_full_indexes[$key_name]['columns']); $actual_cols = array_map(function($c) { return strtolower(trim(str_replace('`', '', $c))); }, array_values($actual_full_indexes[$key_name]['columns'])); if ($expected_cols !== $actual_cols) { // Index has wrong columns - drop and recreate if ($key_name === 'PRIMARY') { $all_fix_sql[] = 'ALTER TABLE '.$fix_table.' DROP PRIMARY KEY, ADD PRIMARY KEY ('.$expected_idx['columns'].'); #EOQ'; } else { $type_kw = ($expected_idx['type'] === 'FULLTEXT') ? 'FULLTEXT KEY' : $expected_idx['type']; $all_fix_sql[] = 'ALTER TABLE '.$fix_table.' DROP INDEX `'.$key_name.'`, ADD '.$type_kw.' `'.$key_name.'` ('.$expected_idx['columns'].'); #EOQ'; } } } } // Check for duplicate actual indexes that should be dropped foreach ($actual_full_indexes as $act_name => $act_idx) { if ($act_name === 'PRIMARY' || isset($full_indexes[$table_lower][$act_name]) || in_array($act_name, $renamed_indexes)) { continue; // Skip expected indexes, PRIMARY, and already-renamed indexes } ksort($act_idx['columns']); $act_cols_norm = array_map(function($c) { return strtolower(trim(str_replace('`', '', $c))); }, array_values($act_idx['columns'])); foreach ($full_indexes[$table_lower] as $exp_name => $exp_idx) { if ($act_idx['type'] !== $exp_idx['type']) { continue; } $exp_cols_norm = array_map(function($c) { return strtolower(trim(preg_replace('/\(\d+\)/', '', str_replace('`', '', trim($c))))); }, explode(',', $exp_idx['columns'])); if ($act_cols_norm === $exp_cols_norm) { $all_fix_sql[] = 'ALTER TABLE '.$fix_table.' DROP INDEX `'.$act_name.'`; #EOQ'; break; } } } } $table['Data_free'] = ($table['Data_free'] > 0) ? formatBytes($table['Data_free'], true) : '-'; $table_size = $table['Data_length']+$table['Index_length']; $data_length = formatBytes($table_size); $table['Data_length'] = ($table_size>0) ? $data_length['size'].' '.$data_length['suffix'] : '-'; $table['Name_Display'] = $GLOBALS['config']->get('config', 'dbdatabase').'.'.$table['Name']; $table['errors'] = count($index_errors)>0 ? implode('