File: /home/h340499/public_html/wp-content/plugins/duplicator-pro/src/Package/PackageUtils.php
<?php
namespace Duplicator\Package;
use DateTime;
use Duplicator\Models\GlobalEntity;
use Duplicator\Utils\Logging\DupLog;
use Duplicator\Package\DupPackage;
use Duplicator\Models\TemplateEntity;
use Duplicator\Core\Constants;
use Exception;
use Duplicator\Installer\Models\MigrateData;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Package\Archive\PackageArchive;
use Duplicator\Package\Recovery\RecoveryPackage;
class PackageUtils
{
const DEFAULT_BACKUP_TYPE = 'Standard';
const BULK_DELETE_LIMIT_CHUNK = 100;
/**
* Restise excecure packages registration
*
* @return void
*/
public static function registerStandardPackageType(): void
{
DupPackage::registerType();
}
/**
* Update CREATED AFTER INSTALL FLAGS
*
* @param MigrateData $migrationData migration data
*
* @return void
*/
public static function updateCreatedAfterInstallFlags(MigrateData $migrationData): void
{
if ($migrationData->restoreBackupMode == false) {
return;
}
// Refresh recovery Backup set beforw backup
$ids = DupPackage::dbSelect('FIND_IN_SET(\'' . DupPackage::FLAG_DISASTER_SET . '\', `flags`)', 0, 0, '', 'ids');
if (count($ids)) {
RecoveryPackage::setRecoveablePackage($ids[0]);
}
// Update all backups with created after restore flag or created after install time
DupPackage::dbSelectCallback(
function (DupPackage $package): void {
$package->updateMigrateAfterInstallFlag();
$package->save();
},
'FIND_IN_SET(\'' . DupPackage::FLAG_CREATED_AFTER_RESTORE . '\', `flags`) OR
(
`id` > ' . $migrationData->packageId . ' AND
`created` < \'' . esc_sql($migrationData->installTime) . '\'
)'
);
}
/**
* Get the number of Backups
*
* @param string[] $backupTypes backup types to include, is empty all types are included
*
* @return int
*/
public static function getNumPackages(array $backupTypes = []): int
{
$ids = DupPackage::getIdsByStatus(
[],
0,
0,
'',
$backupTypes
);
return count($ids);
}
/**
* Get the number of complete Backups
*
* @param string[] $backupTypes backup types to include, is empty all types are included
*
* @return int
*/
public static function getNumCompletePackages(array $backupTypes = []): int
{
$ids = DupPackage::getIdsByStatus(
[
[
'op' => '>=',
'status' => AbstractPackage::STATUS_COMPLETE,
],
],
0,
0,
'',
$backupTypes
);
return count($ids);
}
/**
* Get packages without storages
*
* @param int $limit Limit the number of packages to return, if 0 no limit is applied
*
* @return int[]
*/
public static function getPackageWithoutStorages(int $limit = 0): array
{
$where = '(`status` = ' . AbstractPackage::STATUS_COMPLETE . ' OR `status` < ' . AbstractPackage::STATUS_PRE_PROCESS . ')' .
' AND FIND_IN_SET(\'' . DupPackage::FLAG_HAVE_LOCAL . '\', `flags`) = 0' .
' AND FIND_IN_SET(\'' . DupPackage::FLAG_HAVE_REMOTE . '\', `flags`) = 0';
return DupPackage::dbSelect($where, $limit, 0, '', 'ids', [DupPackage::getBackupType()]);
}
/**
* Massive delete packages without storages using direct SQL query
*
* @param int $limit Limit the number of packages to return, if 0 no limit is applied
*
* @return int Number of packages deleted
*/
public static function bulkDeletePackageWithoutStorages(int $limit = 0): int
{
// In that case we can use direct SQL query because the backup don't have storages,so we don't need remove local files
global $wpdb;
$table = DupPackage::getTableName();
$ids = self::getPackageWithoutStorages($limit);
$count = count($ids);
if ($count == 0) {
return 0;
}
$idList = implode(',', $ids);
$query = "DELETE FROM `{$table}` WHERE id IN ({$idList})";
$result = $wpdb->query($query);
if ($result === false) {
throw new Exception("Error deleting packages without storages: " . $wpdb->last_error);
}
return (int) $result;
}
/**
* Delete packages without storages in chunks
*
* @return int Number of packages deleted in this chunk, -1 if error
*/
public static function bulkDeletePackageWithoutStoragesChunk(): int
{
try {
return self::bulkDeletePackageWithoutStorages(self::BULK_DELETE_LIMIT_CHUNK);
} catch (Exception $e) {
DupLog::trace("Error in bulkDeletePackageWithoutStoragesChunk: " . $e->getMessage());
return -1;
}
}
/**
* Creates a default name
*
* @param bool $preDate if true prepend date to name
*
* @return string Default Backup name
*/
public static function getDefaultPackageName(bool $preDate = true): string
{
//Remove specail_chars from final result
$special_chars = [
".",
"-",
];
$name = ($preDate) ?
date('Ymd') . '_' . sanitize_title(get_bloginfo('name', 'display')) :
sanitize_title(get_bloginfo('name', 'display')) . '_' . date('Ymd');
$name = substr(sanitize_file_name($name), 0, 40);
return str_replace($special_chars, '', $name);
}
/**
* Provides various date formats
*
* @param string $utcDate created date in the GMT timezone
* @param int $format Various date formats to apply
*
* @return string formatted date
*/
public static function formatLocalDateTime(string $utcDate, int $format = 1): string
{
$date = get_date_from_gmt($utcDate);
$date = new DateTime($date);
switch ($format) {
//YEAR
case 1:
return $date->format('Y-m-d H:i');
case 2:
return $date->format('Y-m-d H:i:s');
case 3:
return $date->format('y-m-d H:i');
case 4:
return $date->format('y-m-d H:i:s');
//MONTH
case 5:
return $date->format('m-d-Y H:i');
case 6:
return $date->format('m-d-Y H:i:s');
case 7:
return $date->format('m-d-y H:i');
case 8:
return $date->format('m-d-y H:i:s');
//DAY
case 9:
return $date->format('d-m-Y H:i');
case 10:
return $date->format('d-m-Y H:i:s');
case 11:
return $date->format('d-m-y H:i');
case 12:
return $date->format('d-m-y H:i:s');
default:
return $date->format('Y-m-d H:i');
}
}
/**
* Cleanup all tmp files
*
* @param bool $all empty all contents
*
* @return bool true on success fail on failure
*/
public static function tmpCleanup($all = false): bool
{
//Delete all files now
if ($all) {
$dir = DUPLICATOR_PRO_SSDIR_PATH_TMP . "/*";
foreach (glob($dir) as $file) {
if (basename($file) === 'index.php') {
continue;
}
SnapIO::rrmdir($file);
}
} else {
// Remove scan files that are 24 hours old
$dir = DUPLICATOR_PRO_SSDIR_PATH_TMP . "/*_scan.json";
foreach (glob($dir) as $file) {
if (filemtime($file) <= time() - Constants::TEMP_CLEANUP_SECONDS) {
SnapIO::rrmdir($file);
}
}
}
// Clean up extras directory if it is still hanging around
$extras_directory = SnapIO::safePath(DUPLICATOR_PRO_SSDIR_PATH_TMP) . '/extras';
if (file_exists($extras_directory)) {
try {
if (!SnapIO::rrmdir($extras_directory)) {
throw new Exception('Failed to delete: ' . $extras_directory);
}
} catch (Exception $ex) {
DupLog::trace("Couldn't recursively delete {$extras_directory}");
}
}
return true;
}
/**
* Safe tmp cleanup
*
* @param bool $purgeTempArchives if true purge temp archives
*
* @return void
*/
public static function safeTmpCleanup(bool $purgeTempArchives = false): void
{
if ($purgeTempArchives) {
$dir = DUPLICATOR_PRO_SSDIR_PATH_TMP . "/*_archive.zip.*";
foreach (glob($dir) as $file_path) {
unlink($file_path);
}
$dir = DUPLICATOR_PRO_SSDIR_PATH_TMP . "/*_archive.daf.*";
foreach (glob($dir) as $file_path) {
unlink($file_path);
}
} else {
$dir = DUPLICATOR_PRO_SSDIR_PATH_TMP . "/*";
$files = glob($dir);
if ($files !== false) {
foreach ($files as $file_path) {
if (basename($file_path) === 'index.php') {
continue;
}
if (filemtime($file_path) <= time() - Constants::TEMP_CLEANUP_SECONDS) {
SnapIO::rrmdir($file_path);
}
}
}
}
}
/**
* Get type string
*
* @param int $executionType execution type
* @param int $templateId template id
*
* @return string
*/
public static function getExecTypeString(int $executionType, int $templateId = -1): string
{
switch ($executionType) {
case AbstractPackage::EXEC_TYPE_MANUAL:
if ($templateId != -1) {
$template = TemplateEntity::getById($templateId);
if (isset($template->is_manual) && !$template->is_manual) {
return __('Template', 'duplicator-pro') . ' ' . $template->name;
}
}
return __('Manual', 'duplicator-pro');
case AbstractPackage::EXEC_TYPE_SCHEDULED:
return __('Schedule', 'duplicator-pro');
case AbstractPackage::EXEC_TYPE_RUN_NOW:
return __('Schedule (Run Now)', 'duplicator-pro');
default:
return __('Unknown', 'duplicator-pro');
}
}
/**
* Returns the Backup engine type string
*
* @param int $engineType Backup engine type
* @param int $zipArchiveMode Zip archive mode
*
* @return string
*/
public static function getEngineTypeString(int $engineType, int $zipArchiveMode): string
{
switch ($engineType) {
case PackageArchive::BUILD_MODE_SHELL_EXEC:
return __('Shell Exec', 'duplicator-pro');
case PackageArchive::BUILD_MODE_ZIP_ARCHIVE:
if ($zipArchiveMode == PackageArchive::ZIP_MODE_SINGLE_THREAD) {
return __('Zip Archive ST', 'duplicator-pro');
}
return __('Zip Archive', 'duplicator-pro');
case PackageArchive::BUILD_MODE_DUP_ARCHIVE:
return __('Dup Archive', 'duplicator-pro');
default:
return __('Unknown', 'duplicator-pro');
}
}
/**
* Returns an array with stats about the orphaned files
*
* @return string[] The full path of the orphaned file
*/
public static function getOrphanedPackageFiles(): array
{
$global = GlobalEntity::getInstance();
$orphans = [];
$numPackages = DupPackage::countByStatus([], [DupPackage::getBackupType()]);
$numPerPage = 100;
$pages = floor($numPackages / $numPerPage) + 1;
$skipStart = ['dup_pro'];
for ($page = 0; $page < $pages; $page++) {
$offset = $page * $numPerPage;
$pagePackages = DupPackage::getRowByStatus(
[],
$numPerPage,
$offset,
'`id` ASC',
[DupPackage::getBackupType()]
);
foreach ($pagePackages as $cPack) {
$skipStart[] = $cPack->name . '_' . $cPack->hash;
}
}
$pagePackages = null;
$fileTimeSkipInSec = (
max(
Constants::DEFAULT_MAX_PACKAGE_RUNTIME_IN_MIN,
$global->max_package_runtime_in_min
) + Constants::ORPAHN_CLEANUP_DELAY_MAX_PACKAGE_RUNTIME
) * 60;
foreach (
[
DUPLICATOR_PRO_SSDIR_PATH,
DUPLICATOR_PRO_PATH_LOGS,
] as $rootPathToCheck
) {
if (file_exists($rootPathToCheck) && ($handle = opendir($rootPathToCheck)) !== false) {
while (false !== ($fileName = readdir($handle))) {
if ($fileName == '.' || $fileName == '..') {
continue;
}
$fileFullPath = $rootPathToCheck . '/' . $fileName;
if (is_dir($fileFullPath)) {
continue;
}
if (time() - filemtime($fileFullPath) < $fileTimeSkipInSec) {
// file younger than 2 hours skip for security
continue;
}
if (!preg_match(DUPLICATOR_PRO_FULL_GEN_BACKUP_FILE_REGEX_PATTERN, $fileName)) {
continue;
}
foreach ($skipStart as $skip) {
if (strpos($fileName, $skip) === 0) {
continue 2;
}
}
$orphans[] = $fileFullPath;
}
closedir($handle);
}
}
return $orphans;
}
/**
* Returns an array with stats about the orphaned files
*
* @return array{size:int,count:int} The total count and file size of orphaned files
*/
public static function getOrphanedPackageInfo(): array
{
$files = self::getOrphanedPackageFiles();
$info = [];
$info['size'] = 0;
$info['count'] = 0;
if (count($files)) {
foreach ($files as $path) {
$get_size = @filesize($path);
if ($get_size > 0) {
$info['size'] += $get_size;
$info['count']++;
}
}
}
return $info;
}
}