Move site content between installations as a package
System now exports a content package and imports one, by upload or from var/pakete. Pages with their revisions, areas, menus, media, forms, redirects, terms, translations, design and settings are replaced; submissions, statistics, tasks, plugin state and service keys stay, and accounts only come along when that is chosen explicitly. A backup comes first, the site is in maintenance meanwhile, and every file and document in the package is checked before anything is written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a57b829a15
commit
32e42f973b
13 files changed
+973
-2
No files matched your search
@@ -11,6 +11,7 @@ use App\Ops\Backup;
|
||||
use App\Ops\Environment;
|
||||
use App\Ops\Installation;
|
||||
use App\Ops\OriginCheck;
|
||||
use App\Ops\SitePackage;
|
||||
use App\Ops\Updater;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
@@ -28,7 +29,7 @@ use Symfony\Component\Routing\Attribute\Route;
|
||||
*/
|
||||
final class SystemController extends AbstractController
|
||||
{
|
||||
public function __construct(private Updater $updater, private Backup $backup, private ArchiveUpdate $archive, private Installation $installation, private string $adminPath, private string $databaseUrl, private string $mediaDirectory, private string $projectDir, private string $siteOrigin, private OriginCheck $originCheck, private HttpKernelInterface $kernel, private PageRepository $pages, private SiteSettings $settings)
|
||||
public function __construct(private Updater $updater, private Backup $backup, private ArchiveUpdate $archive, private Installation $installation, private string $adminPath, private string $databaseUrl, private string $mediaDirectory, private string $projectDir, private string $siteOrigin, private OriginCheck $originCheck, private HttpKernelInterface $kernel, private PageRepository $pages, private SiteSettings $settings, private SitePackage $packages)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -47,6 +48,9 @@ final class SystemController extends AbstractController
|
||||
'asked' => $request->query->getBoolean('pruefen'),
|
||||
'steps' => $request->getSession()->remove('update_steps') ?: [],
|
||||
'backups' => $this->backup->all(),
|
||||
// Inhaltspakete (ADR 118): die per FTP abgelegten und die Grenze für Uploads.
|
||||
'packages' => $this->packages->waiting(),
|
||||
'uploadLimit' => $this->uploadLimit(),
|
||||
'adminPath' => $this->adminPath,
|
||||
// Der Weg ohne Git: das Releasearchiv. Auf geteiltem Hosting ist das der einzige.
|
||||
'archiveVersion' => $request->query->getBoolean('pruefen') ? $this->archive->updateAvailable() : null,
|
||||
@@ -497,6 +501,23 @@ final class SystemController extends AbstractController
|
||||
return new Response($this->originCheck->token(), 200, ['Content-Type' => 'text/plain; charset=UTF-8', 'Cache-Control' => 'no-store', 'X-Robots-Tag' => 'noindex']);
|
||||
}
|
||||
|
||||
/** Die kleinere der beiden PHP-Grenzen, die einen Upload begrenzen - so, wie sie in der php.ini steht. */
|
||||
private function uploadLimit(): string
|
||||
{
|
||||
$bytes = static function (string $value): int {
|
||||
$value = trim($value);
|
||||
$number = (int) $value;
|
||||
|
||||
return match (strtolower(substr($value, -1))) {
|
||||
'g' => $number * 1024 ** 3, 'm' => $number * 1024 ** 2, 'k' => $number * 1024, default => $number,
|
||||
};
|
||||
};
|
||||
$limits = array_filter([$bytes((string) ini_get('upload_max_filesize')), $bytes((string) ini_get('post_max_size'))]);
|
||||
$min = [] === $limits ? 0 : min($limits);
|
||||
|
||||
return 0 === $min ? 'unbegrenzt' : number_format($min / 1048576, 0, ',', '.').' MB';
|
||||
}
|
||||
|
||||
private function back(string $message, string $type = 'error'): Response
|
||||
{
|
||||
$this->addFlash($type, $message);
|
||||
|
||||
Reference in new issue
Block a user