mautic / upgrade.php
chrisbryan17's picture
Upload folder using huggingface_hub
d2897cd verified
<?php
ini_set('display_errors', 'Off');
date_default_timezone_set('UTC');
// Running this script standalone is no longer supported
$standalone = 0;
$task = getVar('task');
define('IN_CLI', 'cli' === php_sapi_name());
define('MAUTIC_ROOT', (IN_CLI || empty($task)) ? __DIR__ : dirname(__DIR__));
define('MAUTIC_APP_ROOT', MAUTIC_ROOT.'/app');
if (IN_CLI) {
if (!file_exists(__DIR__.'/upgrade')) {
mkdir(__DIR__.'/upgrade');
}
define('MAUTIC_UPGRADE_ROOT', __DIR__.'/upgrade');
} else {
define('MAUTIC_UPGRADE_ROOT', __DIR__);
}
// Fail-safe PHP version check
if (file_exists(MAUTIC_UPGRADE_ROOT.'/app/release_metadata.json')) {
$metadata = json_decode(file_get_contents(MAUTIC_UPGRADE_ROOT.'/app/release_metadata.json'), true);
// Are we running the minimum version?
if (version_compare(PHP_VERSION, $metadata['minimum_php_version'], 'lt')) {
echo 'Your server does not meet the minimum PHP requirements. Mautic requires PHP version '.$metadata['minimum_php_version'].' while your server has '
.PHP_VERSION.'. Please contact your host to update your PHP installation.'."\n";
exit;
}
// Are we running a version newer than what Mautic supports?
if (version_compare(PHP_VERSION, $metadata['maximum_php_version'], 'gt')) {
echo 'Mautic does not support PHP version '.PHP_VERSION.' at this time. To use Mautic, you will need to downgrade to an earlier version.'
."\n";
exit;
}
}
// Get local parameters
$localParameters = get_local_config();
$cacheDir = (isset($localParameters['cache_path'])) ? str_replace('%kernel.project_dir%', MAUTIC_ROOT, $localParameters['cache_path'].'/prod') : MAUTIC_ROOT.'/var/cache/prod';
$logDir = (isset($localParameters['log_path'])) ? str_replace('%kernel.project_dir%', MAUTIC_ROOT, $localParameters['log_path'].'/prod') : MAUTIC_ROOT.'/var/logs';
define('MAUTIC_CACHE_DIR', $cacheDir);
define('MAUTIC_UPGRADE_ERROR_LOG', $logDir.'/upgrade_errors.php');
/*
* Updating to 2.8.1: Check to see if we have a mautic_session_name
* and use that to populate the actual session name that will be
* generated after a successful update.
*/
if (isset($_COOKIE['mautic_session_name'])) {
$sessionValue = $_COOKIE[$_COOKIE['mautic_session_name']];
include MAUTIC_APP_ROOT.'/config/paths.php';
$localConfigPath = str_replace('%kernel.project_dir%', MAUTIC_ROOT, $paths['local_config']);
$newSessionName = md5(md5($localConfigPath).$localParameters['secret_key']);
setcookie($newSessionName, $sessionValue, 0, '/', '', false, true);
unset($_COOKIE['mautic_session_name']);
setcookie('mautic_session_name', '', -1);
}
// Fetch the update state out of the request if applicable
$state = json_decode(base64_decode(getVar('updateState', 'W10=')), true);
// Prime the state if it's empty
if (empty($state)) {
$state['pluginComplete'] = false;
$state['bundleComplete'] = false;
$state['cacheComplete'] = false;
$state['coreComplete'] = false;
$state['vendorComplete'] = false;
}
$status = ['complete' => false, 'error' => false, 'updateState' => $state, 'stepStatus' => 'In Progress'];
if (IN_CLI) {
echo "Upgrading through upgrade.php using the CLI is no longer supported. Please use 'php bin/console mautic:update:find' instead. \n";
exit(1);
}
// Web request upgrade
$request = explode('?', $_SERVER['REQUEST_URI'])[0];
$url = "//{$_SERVER['HTTP_HOST']}{$request}";
$isSSL = (!empty($_SERVER['HTTPS']) && 'off' != $_SERVER['HTTPS']);
$cookie_path = (isset($localParameters['cookie_path'])) ? $localParameters['cookie_path'] : '/';
$cookie_domain = (isset($localParameters['cookie_domain'])) ? $localParameters['cookie_domain'] : '';
$cookie_secure = (isset($localParameters['cookie_secure'])) ? $localParameters['cookie_secure'] : $isSSL;
$cookie_httponly = (isset($localParameters['cookie_httponly'])) ? $localParameters['cookie_httponly'] : false;
setcookie('mautic_update', $task, time() + 300, $cookie_path, $cookie_domain, $cookie_secure, $cookie_httponly);
$query = '';
$maxCount = 5;
switch ($task) {
case '':
html_body("<div class='well text-center'>This script cannot run standalone. Please log into Mautic to check for updates.</div>");
// no break
case 'moveBundles':
$status = move_mautic_bundles($status, $maxCount);
if (empty($status['complete'])) {
if (!isset($state['refresh_count'])) {
$state['refresh_count'] = 1;
}
$nextTask = 'moveBundles';
$query = 'count='.$state['refresh_count'].'&';
++$state['refresh_count'];
} else {
$nextTask = 'moveCore';
unset($state['refresh_count']);
}
break;
case 'moveCore':
$status = move_mautic_core($status);
$nextTask = 'moveVendors';
break;
case 'moveVendors':
$status = move_mautic_vendors($status, $maxCount);
$nextTask = (!empty($status['complete'])) ? 'clearCache' : 'moveVendors';
if (empty($status['complete'])) {
if (!isset($state['refresh_count'])) {
$state['refresh_count'] = 1;
}
$nextTask = 'moveVendors';
$query = 'count='.$state['refresh_count'].'&';
++$state['refresh_count'];
} else {
$nextTask = 'clearCache';
unset($state['refresh_count']);
}
break;
case 'clearCache':
clear_mautic_cache();
$nextTask = 'finish';
break;
case 'finish':
$status['complete'] = true;
$status['stepStatus'] = 'Success';
$status['nextStep'] = 'Processing Database Updates';
$status['nextStepStatus'] = 'In Progress';
$status['updateState']['cacheComplete'] = true;
break;
default:
$status['error'] = true;
$status['message'] = 'Invalid task';
$status['stepStatus'] = 'Failed';
break;
}
// Request through Mautic's UI
$status['updateState'] = get_state_param($status['updateState']);
send_response($status);
/**
* Get local parameters.
*
* @return mixed
*/
function get_local_config()
{
static $parameters;
if (null === $parameters) {
// Used in paths.php
$root = MAUTIC_APP_ROOT;
/** @var array<string> $paths */
$paths = [];
include MAUTIC_APP_ROOT.'/config/paths.php';
// Include local config to get cache_path
$localConfig = str_replace('%kernel.project_dir%', MAUTIC_ROOT, $paths['local_config']);
/** @var array<string, mixed> $parameters */
$parameters = [];
include $localConfig;
$localParameters = $parameters;
// check for parameter overrides
if (file_exists(MAUTIC_APP_ROOT.'/../config/parameters_local.php')) {
/** @var array<string, mixed> $parameters */
include MAUTIC_APP_ROOT.'/../config/parameters_local.php';
$localParameters = array_merge($localParameters, $parameters);
}
foreach ($localParameters as $k => &$v) {
if (!empty($v) && is_string($v) && preg_match('/getenv\((.*?)\)/', $v, $match)) {
$v = (string) getenv($match[1]);
}
}
$parameters = $localParameters;
}
return $parameters;
}
/**
* Clears the application cache.
*
* Since this script is being executed via web requests and standalone from the Mautic application, we don't have access to Symfony's
* CLI suite. So we'll go with Option B in this instance and just nuke the entire production cache and let Symfony rebuild it on the next
* application cycle.
*
* @return bool
*/
function clear_mautic_cache()
{
if (!recursive_remove_directory(MAUTIC_CACHE_DIR)) {
process_error_log(['Could not remove the application cache. You will need to manually delete '.MAUTIC_CACHE_DIR.'.']);
return false;
}
// Follow the same pattern as the console command and flush opcache/apc as appropriate.
if (function_exists('opcache_reset')) {
opcache_reset();
}
if (function_exists('apcu_clear_cache')) {
apcu_clear_cache();
}
return true;
}
/**
* Copy a folder.
*
* This function is based on \Joomla\Filesystem\Folder:copy()
*
* @param string $src The path to the source folder
* @param string $dest The path to the destination folder
*
* @return array<string>|string|bool True on success, a single error message on a "boot" fail, or an array of errors from the recursive operation
*/
function copy_directory($src, $dest)
{
@set_time_limit((int) ini_get('max_execution_time'));
$errorLog = [];
// Eliminate trailing directory separators, if any
$src = rtrim($src, DIRECTORY_SEPARATOR);
$dest = rtrim($dest, DIRECTORY_SEPARATOR);
// Make sure the destination exists
if (!is_dir($dest)) {
if (!@mkdir($dest, 0777, true)) {
return sprintf(
'Could not move files from %s to production since the folder could not be created.',
str_replace(MAUTIC_UPGRADE_ROOT, '', $src)
);
}
}
if (!($dh = @opendir($src))) {
return sprintf('Could not read directory %s to move files.', str_replace(MAUTIC_UPGRADE_ROOT, '', $src));
}
// Walk through the directory copying files and recursing into folders.
while (false !== ($file = readdir($dh))) {
$sfid = $src.'/'.$file;
$dfid = $dest.'/'.$file;
switch (filetype($sfid)) {
case 'dir':
if ('.' != $file && '..' != $file) {
$ret = copy_directory($sfid, $dfid);
if (true !== $ret) {
if (is_array($ret)) {
$errorLog += $ret;
} else {
$errorLog[] = $ret;
}
}
}
break;
case 'file':
if (!@rename($sfid, $dfid)) {
$errorLog[] = sprintf('Could not move file %s to production.', str_replace(MAUTIC_UPGRADE_ROOT, '', $sfid));
}
break;
}
}
if (!empty($errorLog)) {
return $errorLog;
}
return true;
}
/**
* Fetches a request variable and returns the sanitized version of it.
*
* @param string $name
* @param string $default
* @param int $filter
*
* @return mixed|string
*/
function getVar($name, $default = '', $filter = FILTER_SANITIZE_STRING)
{
if (isset($_REQUEST[$name])) {
return filter_var($_REQUEST[$name], $filter);
}
return $default;
}
/**
* Moves the Mautic bundles from the upgrade directory to production.
*
* A typical update package will only include changed files in the bundles. However, in this script we will assume that all of
* the bundle resources are included here and recursively iterate over the bundles in batches to update the filesystem.
*
* @param array<mixed> $status
* @param int $maxCount
*
* @return array<mixed>
*/
function move_mautic_bundles(array $status, $maxCount = 5)
{
$errorLog = [];
// First, we will move any addon bundles into position
if (is_dir(MAUTIC_UPGRADE_ROOT.'/plugins') && !$status['updateState']['pluginComplete']) {
$iterator = new DirectoryIterator(MAUTIC_UPGRADE_ROOT.'/plugins');
// Sanity check, make sure there are actually directories here to process
$dirs = glob(MAUTIC_UPGRADE_ROOT.'/plugins/*', GLOB_ONLYDIR);
if (count($dirs)) {
/** @var DirectoryIterator $directory */
foreach ($iterator as $directory) {
// Sanity checks
if (!$directory->isDot() && $directory->isDir()) {
$src = $directory->getPath().'/'.$directory->getFilename();
$dest = str_replace(MAUTIC_UPGRADE_ROOT, MAUTIC_ROOT, $src);
$result = copy_directory($src, $dest);
if (true !== $result) {
if (is_array($result)) {
$errorLog += $result;
} else {
$errorLog[] = $result;
}
}
$deleteDir = recursive_remove_directory($src);
if (!$deleteDir) {
$errorLog[] = sprintf('Failed to remove the upgrade directory %s folder', str_replace(MAUTIC_UPGRADE_ROOT, '', $src));
}
}
}
}
// At this point, there shouldn't be any plugins remaining; nuke the folder
$deleteDir = recursive_remove_directory(MAUTIC_UPGRADE_ROOT.'/plugins');
if (!$deleteDir) {
$errorLog[] = sprintf('Failed to remove the upgrade directory %s folder', '/plugins');
}
process_error_log($errorLog);
$status['updateState']['pluginComplete'] = true;
if (-1 != $maxCount) {
// Finished with plugins, get a response back to the app so we can iterate to the next part
return $status;
}
}
// Now we move the main app bundles into production
if (is_dir(MAUTIC_UPGRADE_ROOT.'/app/bundles') && !$status['updateState']['bundleComplete']) {
// Initialize the bundle state if it isn't
if (!isset($status['updateState']['completedBundles'])) {
$status['updateState']['completedBundles'] = [];
}
$completed = true;
$iterator = new DirectoryIterator(MAUTIC_UPGRADE_ROOT.'/app/bundles');
// Sanity check, make sure there are actually directories here to process
$dirs = glob(MAUTIC_UPGRADE_ROOT.'/app/bundles/*', GLOB_ONLYDIR);
if (count($dirs)) {
$count = 0;
/** @var DirectoryIterator $directory */
foreach ($iterator as $directory) {
// Exit the loop if the count has reached 5
if (-1 != $maxCount && $count === $maxCount) {
$completed = false;
break;
}
// Sanity checks
if (!$directory->isDot() && $directory->isDir()) {
// Don't process this bundle if we've already tried it
if (isset($status['updateState']['completedBundles'][$directory->getFilename()])) {
continue;
}
$src = $directory->getPath().'/'.$directory->getFilename();
$dest = str_replace(MAUTIC_UPGRADE_ROOT, MAUTIC_ROOT, $src);
$result = copy_directory($src, $dest);
if (true !== $result) {
if (is_array($result)) {
$errorLog += $result;
} else {
$errorLog[] = $result;
}
}
$deleteDir = recursive_remove_directory($src);
if (!$deleteDir) {
$errorLog[] = sprintf('Failed to remove the upgrade directory %s folder', str_replace(MAUTIC_UPGRADE_ROOT, '', $src));
}
$status['updateState']['completedBundles'][$directory->getFilename()] = true;
++$count;
}
}
}
if ($completed) {
$status['updateState']['bundleComplete'] = true;
// At this point, there shouldn't be any bundles remaining; nuke the folder
$deleteDir = recursive_remove_directory(MAUTIC_UPGRADE_ROOT.'/app/bundles');
if (!$deleteDir) {
$errorLog[] = sprintf('Failed to remove the upgrade directory %s folder', '/app/bundles');
}
}
process_error_log($errorLog);
// If we haven't finished the bundles yet, throw a response back to repeat the step
if (!$status['updateState']['bundleComplete']) {
return $status;
}
}
// To get here, all of the bundle updates must have been processed (or there are literally none). Step complete.
$status['complete'] = true;
return $status;
}
/**
* Moves the Mautic core files that are not part of bundles or vendors into production.
*
* The "core" files are broken into groups for purposes of the update script: bundles, vendor, and everything else. This step
* will take care of the everything else.
*
* @param array<mixed> $status
*
* @return array<mixed>
*/
function move_mautic_core(array $status)
{
$errorLog = [];
// Multilevel directories
$nestedDirectories = [
'/media',
'/themes',
'/translations',
'/app/middlewares',
];
foreach ($nestedDirectories as $dir) {
if (is_dir(MAUTIC_UPGRADE_ROOT.$dir)) {
copy_directories($dir, $errorLog);
// At this point, we can remove the media directory
$deleteDir = recursive_remove_directory(MAUTIC_UPGRADE_ROOT.$dir);
if (!$deleteDir) {
$errorLog[] = sprintf('Failed to remove the upgrade directory %s folder', $dir);
}
}
}
// Single level directories with files only
$fileOnlyDirectories = [
'/app/config',
'/app/migrations',
'/app',
'/bin',
];
foreach ($fileOnlyDirectories as $dir) {
if (copy_files($dir, $errorLog)) {
// At this point, we can remove the config directory
$deleteDir = recursive_remove_directory(MAUTIC_UPGRADE_ROOT.$dir);
if (!$deleteDir) {
$errorLog[] = sprintf('Failed to remove the upgrade directory %s folder', $dir);
}
}
}
// Now move any root level files
$iterator = new FilesystemIterator(MAUTIC_UPGRADE_ROOT);
/** @var FilesystemIterator $file */
foreach ($iterator as $file) {
// Sanity checks
if ($file->isFile() && !in_array($file->getFilename(), ['deleted_files.txt', 'critical_migrations.txt', 'upgrade.php'])) {
$src = $file->getPath().'/'.$file->getFilename();
$dest = str_replace(MAUTIC_UPGRADE_ROOT, MAUTIC_ROOT, $src);
if (!@rename($src, $dest)) {
$errorLog[] = sprintf('Could not move file %s to production.', str_replace(MAUTIC_UPGRADE_ROOT, '', $src));
}
}
}
process_error_log($errorLog);
// In this step, we'll also go ahead and remove deleted files, return the results from that
return remove_mautic_deleted_files($status);
}
/**
* Moves the Mautic dependencies from the upgrade directory to production.
*
* Since the /vendor folder is not stored under version control, we cannot accurately track changes in third party dependencies
* between releases. Therefore, this step will recursively iterate over the vendors in batches to remove each package completely
* and replace it with the new version.
*
* @param array<mixed> $status
* @param int $maxCount
*
* @return array<mixed>
*/
function move_mautic_vendors(array $status, $maxCount = 5)
{
$errorLog = [];
// If there isn't even a vendor directory, just skip this step
if (!is_dir(MAUTIC_UPGRADE_ROOT.'/vendor')) {
$status['complete'] = true;
$status['stepStatus'] = 'Success';
$status['nextStep'] = 'Clearing Application Cache';
$status['nextStepStatus'] = 'In Progress';
$status['updateState']['vendorComplete'] = true;
return $status;
}
// Initialize the vendor state if it isn't
if (!isset($status['updateState']['completedVendors'])) {
$status['updateState']['completedVendors'] = [];
}
// Symfony is the largest of our vendors, we will process it first
if (is_dir(MAUTIC_UPGRADE_ROOT.'/vendor/symfony') && !isset($status['updateState']['completedVendors']['symfony'])) {
// Initialize the Symfony state if it isn't, this step will recurse
if (!isset($status['updateState']['completedSymfony'])) {
$status['updateState']['completedSymfony'] = [];
}
$completed = true;
$iterator = new DirectoryIterator(MAUTIC_UPGRADE_ROOT.'/vendor/symfony');
// Sanity check, make sure there are actually directories here to process
$dirs = glob(MAUTIC_UPGRADE_ROOT.'/vendor/symfony/*', GLOB_ONLYDIR);
if (count($dirs)) {
$count = 0;
/** @var DirectoryIterator $directory */
foreach ($iterator as $directory) {
// Exit the loop if the count has reached 5
if (-1 != $maxCount && $count === $maxCount) {
$completed = false;
break;
}
// Sanity checks
if (!$directory->isDot() && $directory->isDir()) {
// Don't process this directory if we've already tried it
if (isset($status['updateState']['completedSymfony'][$directory->getFilename()])) {
continue;
}
$src = $directory->getPath().'/'.$directory->getFilename();
$dest = str_replace(MAUTIC_UPGRADE_ROOT, MAUTIC_ROOT, $src);
// We'll need to completely remove the existing vendor first
recursive_remove_directory($dest);
$result = copy_directory($src, $dest);
if (true !== $result) {
if (is_array($result)) {
$errorLog += $result;
} else {
$errorLog[] = $result;
}
}
$deleteDir = recursive_remove_directory($src);
if (!$deleteDir) {
$errorLog[] = sprintf('Failed to remove the upgrade directory %s folder', str_replace(MAUTIC_UPGRADE_ROOT, '', $src));
}
$status['updateState']['completedSymfony'][$directory->getFilename()] = true;
++$count;
}
}
}
if ($completed) {
$status['updateState']['completedVendors']['symfony'] = true;
// At this point, there shouldn't be any Symfony code remaining; nuke the folder
$deleteDir = recursive_remove_directory(MAUTIC_UPGRADE_ROOT.'/vendor/symfony');
if (!$deleteDir) {
$errorLog[] = sprintf('Failed to remove the upgrade directory %s folder', '/vendor/symfony');
}
}
process_error_log($errorLog);
// If we haven't finished Symfony yet, throw a response back to repeat the step
if (!isset($status['updateState']['completedVendors']['symfony'])) {
return $status;
}
}
// Once we've gotten here, we can safely iterate through the rest of the vendor directory; the rest of the contents are rather small in size
$completed = true;
$iterator = new DirectoryIterator(MAUTIC_UPGRADE_ROOT.'/vendor');
// Sanity check, make sure there are actually directories here to process
$dirs = glob(MAUTIC_UPGRADE_ROOT.'/vendor/*', GLOB_ONLYDIR);
if (count($dirs)) {
$count = 0;
/** @var DirectoryIterator $directory */
foreach ($iterator as $directory) {
// Exit the loop if the count has reached 5
if (-1 != $maxCount && $count === $maxCount) {
$completed = false;
break;
}
// Sanity checks
if (!$directory->isDot() && $directory->isDir()) {
// Don't process this directory if we've already tried it
if (isset($status['updateState']['completedVendors'][$directory->getFilename()])) {
continue;
}
$src = $directory->getPath().'/'.$directory->getFilename();
$dest = str_replace(MAUTIC_UPGRADE_ROOT, MAUTIC_ROOT, $src);
// We'll need to completely remove the existing vendor first
recursive_remove_directory($dest);
$result = copy_directory($src, $dest);
if (true !== $result) {
if (is_array($result)) {
$errorLog += $result;
} else {
$errorLog[] = $result;
}
}
$deleteDir = recursive_remove_directory($src);
if (!$deleteDir) {
$errorLog[] = sprintf('Failed to remove the upgrade directory %s folder', str_replace(MAUTIC_UPGRADE_ROOT, '', $src));
}
$status['updateState']['completedVendors'][$directory->getFilename()] = true;
++$count;
}
}
}
if ($completed) {
$status['updateState']['vendorComplete'] = true;
// Move the autoload.php file over now
if (!@rename(MAUTIC_UPGRADE_ROOT.'/vendor/autoload.php', MAUTIC_ROOT.'/vendor/autoload.php')) {
$errorLog[] = 'Could not move file /vendor/autoload.php to production.';
}
// At this point, there shouldn't be any vendors remaining; nuke the folder
$deleteDir = recursive_remove_directory(MAUTIC_UPGRADE_ROOT.'/vendor');
if (!$deleteDir) {
$errorLog[] = sprintf('Failed to remove the upgrade directory %s folder', '/vendor');
}
}
process_error_log($errorLog);
// If we haven't finished the vendors yet, throw a response back to repeat the step
if (!$status['updateState']['vendorComplete']) {
return $status;
}
// Once we get here, we have finished the moving files step; notifiy Mautic of this
$status['complete'] = true;
$status['stepStatus'] = 'Success';
$status['nextStep'] = 'Clearing Application Cache';
$status['nextStepStatus'] = 'In Progress';
$status['updateState']['vendorComplete'] = true;
return $status;
}
/**
* Copy files from the directory.
*
* @param string $dir
* @param array<string> &$errorLog
*
* @return bool
*/
function copy_files($dir, &$errorLog)
{
if (is_dir(MAUTIC_UPGRADE_ROOT.$dir)) {
$iterator = new FilesystemIterator(MAUTIC_UPGRADE_ROOT.$dir);
/** @var FilesystemIterator $file */
foreach ($iterator as $file) {
// Sanity checks
if ($file->isFile()) {
$src = $file->getPath().'/'.$file->getFilename();
$dest = str_replace(MAUTIC_UPGRADE_ROOT, MAUTIC_ROOT, $src);
if (!@rename($src, $dest)) {
$errorLog[] = sprintf('Could not move file %s to production.', str_replace(MAUTIC_UPGRADE_ROOT, '', $src));
}
}
}
return true;
}
return false;
}
/**
* Copy directories.
*
* @param string $dir
* @param array<string> &$errorLog
* @param bool $createDest
*/
function copy_directories($dir, &$errorLog, $createDest = true): bool
{
// Ensure the destination directory exists
$exists = file_exists(MAUTIC_ROOT.$dir);
if ($createDest && !$exists) {
mkdir(MAUTIC_ROOT.$dir, 0755, true);
} elseif (!$exists) {
$errorLog[] = sprintf('%s does not exist.', MAUTIC_ROOT.$dir);
return false;
}
// Copy root level files first
copy_files($dir, $errorLog);
$iterator = new DirectoryIterator(MAUTIC_UPGRADE_ROOT.$dir);
/** @var DirectoryIterator $directory */
foreach ($iterator as $directory) {
// Sanity checks
if (!$directory->isDot() && $directory->isDir()) {
$src = $directory->getPath().'/'.$directory->getFilename();
$dest = str_replace(MAUTIC_UPGRADE_ROOT, MAUTIC_ROOT, $src);
$result = copy_directory($src, $dest);
if (true !== $result) {
if (is_array($result)) {
$errorLog += $result;
} else {
$errorLog[] = $result;
}
}
$deleteDir = recursive_remove_directory($src);
if (!$deleteDir) {
$errorLog[] = sprintf('Failed to remove the upgrade directory %s folder', str_replace(MAUTIC_UPGRADE_ROOT, '', $src));
}
}
}
return true;
}
/**
* Processes the error log for each step.
*
* @param array<string> $errorLog
*/
function process_error_log(array $errorLog): void
{
// If there were any errors, add them to the error log
if (count($errorLog)) {
// Check if the error log exists first
if (file_exists(MAUTIC_UPGRADE_ERROR_LOG)) {
$errors = file_get_contents(MAUTIC_UPGRADE_ERROR_LOG);
} else {
$errors = "<?php die('no access'); \n\n";
}
$errors .= implode(PHP_EOL, $errorLog)."\n";
@file_put_contents(MAUTIC_UPGRADE_ERROR_LOG, $errors);
}
}
/**
* Tries to recursively delete a directory.
*
* This code is based on the recursive_remove_directory function used by Akeeba Restore
*
* @param string $directory
*
* @return bool
*/
function recursive_remove_directory($directory)
{
// if the path has a slash at the end we remove it here
if ('/' == substr($directory, -1)) {
$directory = substr($directory, 0, -1);
}
// if the path is not valid or is not a directory ...
if (!file_exists($directory)) {
return true;
} elseif (!is_dir($directory)) {
return false;
// ... if the path is not readable
} elseif (!is_readable($directory)) {
// ... we return false and exit the function
return false;
// ... else if the path is readable
} else {
// we open the directory
$handle = opendir($directory);
// and scan through the items inside
while (false !== ($item = readdir($handle))) {
// if the filepointer is not the current directory
// or the parent directory
if ('.' != $item && '..' != $item) {
// we build the new path to delete
$path = $directory.'/'.$item;
// if the new path is a directory
if (is_dir($path)) {
// we call this function with the new path
recursive_remove_directory($path);
// if the new path is a file
} else {
// we remove the file
@unlink($path);
}
}
}
// close the directory
closedir($handle);
// try to delete the now empty directory
if (!@rmdir($directory)) {
// return false if not possible
return false;
}
// return success
return true;
}
}
/**
* Removes deleted files from the system.
*
* While packaging updates, the script will generate a list of deleted files in comparison to the previous version. In this step,
* we will process that list to remove files which are no longer included in the application.
*
* @param array<mixed> $status
*
* @return array<mixed>
*/
function remove_mautic_deleted_files(array $status)
{
$errorLog = [];
// Make sure we have a deleted_files list otherwise we can't process this step
if (file_exists(MAUTIC_UPGRADE_ROOT.'/deleted_files.txt')) {
$deletedFiles = json_decode(file_get_contents(MAUTIC_UPGRADE_ROOT.'/deleted_files.txt'), true);
foreach ($deletedFiles as $file) {
$path = MAUTIC_ROOT.'/'.$file;
// If it doesn't exist, don't even bother
if (file_exists($path)) {
// Try setting the permissions to 777 just to make sure we can get rid of the file
@chmod($path, 0777);
if (!@unlink($path)) {
// Failed to delete, reset the permissions to 644 for safety
@chmod($path, 0644);
$errorLog[] = sprintf(
'Failed removing the file at %s from the production path. As this is a deleted file, you can manually remove this file.',
$file
);
} else {
// Check to see if directory is now empty and if so, delete it
$dirpath = dirname($path);
if (file_exists($dirpath) && !glob($dirpath.'/*')) {
@chmod($dirpath, 0777);
if (!@unlink($dirpath)) {
// Failed to delete, reset the permissions to 0755 for safety
@chmod($dirpath, 0755);
}
}
}
}
}
} else {
$errorLog[] = 'The file containing the list of deleted files was not found, could not process the deleted file list.';
}
process_error_log($errorLog);
$status['complete'] = true;
$status['updateState']['coreComplete'] = true;
return $status;
}
/**
* @param array<mixed> $state
*
* @return string
*/
function get_state_param(array $state)
{
return base64_encode(json_encode($state));
}
/**
* Send the response back to the main application.
*
* @param array<mixed> $status
*/
function send_response(array $status): void
{
header('Content-Type: application/json; charset=utf-8');
echo json_encode($status);
}
/**
* Wrap content in some HTML.
*/
function html_body(string $content): void
{
$html = <<<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Upgrade Mautic</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
</head>
<body>
<div class="container" style="padding: 25px;">
$content
</div>
</body>
</html>
HTML;
echo $html;
exit;
}