Spaces:
No application file
No application file
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; | |
} | |