@@ -7,6 +7,7 @@ use App\Entity\User;
|
|||||||
use App\Entity\Speaker;
|
use App\Entity\Speaker;
|
||||||
use App\Entity\Series;
|
use App\Entity\Series;
|
||||||
use App\Entity\SharedNote;
|
use App\Entity\SharedNote;
|
||||||
|
use App\Service\DatabaseTransferService;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@@ -40,6 +41,7 @@ class DefaultController extends AbstractController
|
|||||||
'last4Notes' => $last4Notes,
|
'last4Notes' => $last4Notes,
|
||||||
'reverseNoteSort' => $openNotes,
|
'reverseNoteSort' => $openNotes,
|
||||||
'isAdmin' => $this->isGranted('ROLE_ADMIN'),
|
'isAdmin' => $this->isGranted('ROLE_ADMIN'),
|
||||||
|
'xferDB' => DatabaseTransferService::isTransferEnabled(),
|
||||||
'meta' => $meta,
|
'meta' => $meta,
|
||||||
'speakers' => $speakers,
|
'speakers' => $speakers,
|
||||||
'series' => $series,
|
'series' => $series,
|
||||||
@@ -89,7 +91,7 @@ class DefaultController extends AbstractController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/reference-editor', name: 'app_reference_editor')]
|
#[Route('/reference-editor', 'app_reference_editor')]
|
||||||
public function referenceEditor(EntityManagerInterface $emi): Response
|
public function referenceEditor(EntityManagerInterface $emi): Response
|
||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||||||
@@ -97,10 +99,63 @@ class DefaultController extends AbstractController
|
|||||||
return $this->render('editors/reference-editor.html.twig');
|
return $this->render('editors/reference-editor.html.twig');
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/template-editor', name: 'app_template_editor')]
|
#[Route('/template-editor', 'app_template_editor')]
|
||||||
public function templateEditor(): Response
|
public function templateEditor(): Response
|
||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
||||||
return $this->render('editors/template-editor.html.twig');
|
return $this->render('editors/template-editor.html.twig');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/xfer-database', name: 'app_admin_transfer_db')]
|
||||||
|
public function transfer(DatabaseTransferService $service, Request $request): Response
|
||||||
|
{
|
||||||
|
$step = $request->query->get('step', 'init');
|
||||||
|
$session = $request->getSession();
|
||||||
|
|
||||||
|
if ($step === 'init') {
|
||||||
|
$service->createSchema();
|
||||||
|
$session->remove('transfer_logs');
|
||||||
|
return $this->redirectToRoute('app_admin_transfer_db', ['step' => 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($step === 'summary') {
|
||||||
|
// Finalize
|
||||||
|
$service->finalizeEnvSwap();
|
||||||
|
|
||||||
|
return $this->render('default/transfer_summary.html.twig', [
|
||||||
|
'logs' => $session->get('transfer_logs', [])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$classes = $service->getEntityClasses();
|
||||||
|
$totalClasses = count($classes);
|
||||||
|
|
||||||
|
if (isset($classes[$step])) {
|
||||||
|
$class = explode("\\", $classes[$step]);
|
||||||
|
$className = end($class);
|
||||||
|
$func = "transfer{$className}Table";
|
||||||
|
|
||||||
|
if (method_exists($service, $func)) {
|
||||||
|
$skippedCount = $service->{$func}();
|
||||||
|
|
||||||
|
if ($skippedCount > 0) {
|
||||||
|
$logs = $session->get('transfer_logs', []);
|
||||||
|
$logs[] = "Skipped $skippedCount orphaned records in the $className table.";
|
||||||
|
$session->set('transfer_logs', $logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
$progress = round((($step+1) / $totalClasses) * 100);
|
||||||
|
$nextStep = ($step + 1 < $totalClasses) ? ($step + 1) : 'summary';
|
||||||
|
|
||||||
|
return $this->render('default/transfer_progress.html.twig', [
|
||||||
|
'current' => $className,
|
||||||
|
'progress' => $progress,
|
||||||
|
'next_step' => $nextStep,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 7: Logout and redirect
|
||||||
|
return $this->redirectToRoute('app_logout');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
501
src/Service/DatabaseTransferService.php
Normal file
501
src/Service/DatabaseTransferService.php
Normal file
@@ -0,0 +1,501 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use App\Entity\Bible;
|
||||||
|
use App\Entity\Note;
|
||||||
|
use App\Entity\Reference;
|
||||||
|
use App\Entity\Series;
|
||||||
|
use App\Entity\Speaker;
|
||||||
|
use App\Entity\Template;
|
||||||
|
use App\Entity\User;
|
||||||
|
use Doctrine\ORM\Tools\SchemaTool;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
use PDO;
|
||||||
|
use PDOException;
|
||||||
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
|
||||||
|
class DatabaseTransferService
|
||||||
|
{
|
||||||
|
private PDO $srcDB;
|
||||||
|
private PDO $destDB;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private string $projectDir,
|
||||||
|
private ManagerRegistry $doctrine
|
||||||
|
) {
|
||||||
|
// connect the source database
|
||||||
|
switch (explode(":", $_ENV['DATABASE_URL'])[0]) {
|
||||||
|
case 'pdo-sqlite';
|
||||||
|
list($driverName, $dbFile) = explode(":/", $_ENV['DATABASE_URL']);
|
||||||
|
$this->srcDB = new PDO('sqlite:'.$dbFile);
|
||||||
|
break;
|
||||||
|
case 'pdo-mysql';
|
||||||
|
case 'pdo-pgsql';
|
||||||
|
$dsn = $this->convertDBURLString($_ENV['DATABASE_URL']);
|
||||||
|
$this->srcDB = new PDO($dsn['dsn'], $dsn['username'], $dsn['password']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect the destination database
|
||||||
|
switch(explode(':', $_ENV['XFER_DATABASE_URL'])[0]) {
|
||||||
|
case 'pdo-sqlite':
|
||||||
|
list($driverName, $dbFile) = explode(":/", $_ENV['XFER_DATABASE']);
|
||||||
|
$this->destDB = new PDO('sqlite:'.$dbFile);
|
||||||
|
break;
|
||||||
|
case 'pdo-mysql':
|
||||||
|
case 'pdo-pgsql':
|
||||||
|
$dsn = $this->convertDBURLString($_ENV['XFER_DATABASE_URL']);
|
||||||
|
$this->destDB = new PDO($dsn['dsn'], $dsn['username'], $dsn['password']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createSchema(): void
|
||||||
|
{
|
||||||
|
$destEm = $this->doctrine->getManager('transfer');
|
||||||
|
$metadata = $destEm->getMetadataFactory()->getAllMetadata();
|
||||||
|
$tool = new SchemaTool($destEm);
|
||||||
|
|
||||||
|
$tool->dropSchema($metadata);
|
||||||
|
$tool->createSchema($metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityClasses(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
User::class,
|
||||||
|
Bible::class,
|
||||||
|
Reference::class,
|
||||||
|
Template::class,
|
||||||
|
Series::class,
|
||||||
|
Speaker::class,
|
||||||
|
Note::class
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferUserTable(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM user";
|
||||||
|
$stmt = $this->srcDB->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$insQuery = "INSERT INTO user (id, email, roles, password, name, meta_data, home_church_rss) ".
|
||||||
|
"VALUES ".
|
||||||
|
"(:id, :email, :roles, :password, :name, :meta_data, :home_church)";
|
||||||
|
$destStmt = $this->destDB->prepare($insQuery);
|
||||||
|
$skippedCount = 0;
|
||||||
|
|
||||||
|
$this->destDB->beginTransaction();
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$params = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'email' => $row['email'],
|
||||||
|
'roles' => $row['roles'],
|
||||||
|
'password' => $row['password'],
|
||||||
|
'name' => $row['name'],
|
||||||
|
'meta_data' => $row['meta_data'],
|
||||||
|
'home_church' => $row['home_church_rss'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->destDB->exec("SAVEPOINT row_insert");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$destStmt->execute($params);
|
||||||
|
|
||||||
|
$this->destDB->exec("RELEASE SAVEPOINT row_insert");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$skipCodes = [
|
||||||
|
'23000',
|
||||||
|
'23503',
|
||||||
|
'23505',
|
||||||
|
];
|
||||||
|
if (in_array($e->getCode(), $skipCodes, true)) {
|
||||||
|
$this->destDB->exec("ROLLBACK TO SAVEPOINT row_insert");
|
||||||
|
$skippedCount++; // 4. Increment the counter
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->rollBack();
|
||||||
|
print_r($params);
|
||||||
|
die("Critical Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->commit();
|
||||||
|
|
||||||
|
return $skippedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferBibleTable(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM bible";
|
||||||
|
$stmt = $this->srcDB->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$insQuery = "INSERT INTO bible (id, book, chapter, verse, content, book_index, label) ".
|
||||||
|
"VALUES ".
|
||||||
|
"(:id, :book, :chapter, :verse, :content, :book_index, :label)";
|
||||||
|
$destStmt = $this->destDB->prepare($insQuery);
|
||||||
|
$skippedCount = 0;
|
||||||
|
|
||||||
|
$this->destDB->beginTransaction();
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$uuid = Uuid::fromString($row['id']);
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'id' => $uuid->toBinary(),
|
||||||
|
'book' => $row['book'],
|
||||||
|
'chapter' => $row['chapter'],
|
||||||
|
'verse' => $row['verse'],
|
||||||
|
'content' => $row['content'],
|
||||||
|
'book_index' => $row['book_index'],
|
||||||
|
'label' => $row['label'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->destDB->exec("SAVEPOINT row_insert");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$destStmt->execute($params);
|
||||||
|
|
||||||
|
$this->destDB->exec("RELEASE SAVEPOINT row_insert");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$skipCodes = [
|
||||||
|
'23000',
|
||||||
|
'23503',
|
||||||
|
'23505',
|
||||||
|
];
|
||||||
|
if (in_array($e->getCode(), $skipCodes, true)) {
|
||||||
|
$this->destDB->exec("ROLLBACK TO SAVEPOINT row_insert");
|
||||||
|
$skippedCount++; // 4. Increment the counter
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->rollBack();
|
||||||
|
print_r($params);
|
||||||
|
die("Critical Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->destDB->commit();
|
||||||
|
|
||||||
|
return $skippedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferReferenceTable(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM reference";
|
||||||
|
$stmt = $this->srcDB->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$insQuery = "INSERT INTO reference (id, type, name, label, ndx, content) ".
|
||||||
|
"VALUES ".
|
||||||
|
"(:id, :type, :name, :label, :ndx, :content)";
|
||||||
|
$destStmt = $this->destDB->prepare($insQuery);
|
||||||
|
$skippedCount = 0;
|
||||||
|
|
||||||
|
$this->destDB->beginTransaction();
|
||||||
|
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$params = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'type' => $row['type'],
|
||||||
|
'name' => $row['name'],
|
||||||
|
'label' => $row['label'],
|
||||||
|
'ndx' => $row['ndx'],
|
||||||
|
'content' => $row['content'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->destDB->exec("SAVEPOINT row_insert");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$destStmt->execute($params);
|
||||||
|
|
||||||
|
$this->destDB->exec("RELEASE SAVEPOINT row_insert");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$skipCodes = [
|
||||||
|
'23000',
|
||||||
|
'23503',
|
||||||
|
'23505',
|
||||||
|
];
|
||||||
|
if (in_array($e->getCode(), $skipCodes, true)) {
|
||||||
|
$this->destDB->exec("ROLLBACK TO SAVEPOINT row_insert");
|
||||||
|
$skippedCount++; // 4. Increment the counter
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->rollBack();
|
||||||
|
print_r($params);
|
||||||
|
die("Critical Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->commit();
|
||||||
|
|
||||||
|
return $skippedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferTemplateTable(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM template";
|
||||||
|
$stmt = $this->srcDB->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$insQuery = "INSERT INTO template (id, user_id, name, content) ".
|
||||||
|
"VALUES ".
|
||||||
|
"(:id, :user_id, :name, :content)";
|
||||||
|
$destStmt = $this->destDB->prepare($insQuery);
|
||||||
|
$skippedCount = 0;
|
||||||
|
|
||||||
|
$this->destDB->beginTransaction();
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$params = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'user_id' => $row['user_id'],
|
||||||
|
'name' => $row['name'],
|
||||||
|
'content' => $row['content'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->destDB->exec("SAVEPOINT row_insert");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$destStmt->execute($params);
|
||||||
|
|
||||||
|
$this->destDB->exec("RELEASE SAVEPOINT row_insert");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$skipCodes = [
|
||||||
|
'23000',
|
||||||
|
'23503',
|
||||||
|
'23505',
|
||||||
|
];
|
||||||
|
if (in_array($e->getCode(), $skipCodes, true)) {
|
||||||
|
$this->destDB->exec("ROLLBACK TO SAVEPOINT row_insert");
|
||||||
|
$skippedCount++; // 4. Increment the counter
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->rollBack();
|
||||||
|
print_r($params);
|
||||||
|
die("Critical Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->commit();
|
||||||
|
|
||||||
|
return $skippedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferSpeakerTable(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM speaker";
|
||||||
|
$stmt = $this->srcDB->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$insQuery = "INSERT INTO speaker (id, name, user_id) ".
|
||||||
|
"VALUES ".
|
||||||
|
"(:id, :name, :user_id)";
|
||||||
|
$destStmt = $this->destDB->prepare($insQuery);
|
||||||
|
$skippedCount = 0;
|
||||||
|
|
||||||
|
$this->destDB->beginTransaction();
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$params = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'name' => $row['name'],
|
||||||
|
'user_id' => $row['user_id'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->destDB->exec("SAVEPOINT row_insert");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$destStmt->execute($params);
|
||||||
|
|
||||||
|
$this->destDB->exec("RELEASE SAVEPOINT row_insert");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$skipCodes = [
|
||||||
|
'23000',
|
||||||
|
'23503',
|
||||||
|
'23505',
|
||||||
|
];
|
||||||
|
if (in_array($e->getCode(), $skipCodes, true)) {
|
||||||
|
$this->destDB->exec("ROLLBACK TO SAVEPOINT row_insert");
|
||||||
|
$skippedCount++; // 4. Increment the counter
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->rollBack();
|
||||||
|
print_r($params);
|
||||||
|
die("Critical Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->commit();
|
||||||
|
|
||||||
|
return $skippedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferSeriesTable(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM series";
|
||||||
|
$stmt = $this->srcDB->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$insQuery = "INSERT INTO series (id, name, user_id, template_id) ".
|
||||||
|
"VALUES ".
|
||||||
|
"(:id, :name, :user_id, :template_id)";
|
||||||
|
$destStmt = $this->destDB->prepare($insQuery);
|
||||||
|
$skippedCount = 0;
|
||||||
|
|
||||||
|
$this->destDB->beginTransaction();
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$params = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'name' => $row['name'],
|
||||||
|
'user_id' => $row['user_id'],
|
||||||
|
'template_id' => $row['template_id'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->destDB->exec("SAVEPOINT row_insert");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$destStmt->execute($params);
|
||||||
|
|
||||||
|
$this->destDB->exec("RELEASE SAVEPOINT row_insert");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$skipCodes = [
|
||||||
|
'23000',
|
||||||
|
'23503',
|
||||||
|
'23505',
|
||||||
|
];
|
||||||
|
if (in_array($e->getCode(), $skipCodes, true)) {
|
||||||
|
$this->destDB->exec("ROLLBACK TO SAVEPOINT row_insert");
|
||||||
|
$skippedCount++; // 4. Increment the counter
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->rollBack();
|
||||||
|
print_r($params);
|
||||||
|
die("Critical Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->commit();
|
||||||
|
|
||||||
|
return $skippedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferNoteTable(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM note";
|
||||||
|
$stmt = $this->srcDB->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$insQuery = "INSERT INTO note (id, title, date, passage, refs, text, speaker_id, series_id, user_id, recording) ".
|
||||||
|
"VALUES ".
|
||||||
|
"(:id, :title, :date, :passage, :refs, :text, :speaker_id, :series_id, :user_id, :recording)";
|
||||||
|
$destStmt = $this->destDB->prepare($insQuery);
|
||||||
|
$skippedCount = 0;
|
||||||
|
|
||||||
|
$this->destDB->beginTransaction();
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$params = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'speaker_id' => $row['speaker_id'],
|
||||||
|
'series_id' => $row['series_id'],
|
||||||
|
'user_id' => $row['user_id'],
|
||||||
|
'title' => $row['title'],
|
||||||
|
'date' => $row['date'],
|
||||||
|
'passage' => $row['passage'],
|
||||||
|
'refs' => $row['refs'],
|
||||||
|
'text' => $row['text'],
|
||||||
|
'recording' => $row['recording'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->destDB->exec("SAVEPOINT row_insert");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$destStmt->execute($params);
|
||||||
|
|
||||||
|
$this->destDB->exec("RELEASE SAVEPOINT row_insert");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$skipCodes = [
|
||||||
|
'23000',
|
||||||
|
'23503',
|
||||||
|
'23505',
|
||||||
|
];
|
||||||
|
if (in_array($e->getCode(), $skipCodes, true)) {
|
||||||
|
$this->destDB->exec("ROLLBACK TO SAVEPOINT row_insert");
|
||||||
|
$skippedCount++; // 4. Increment the counter
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->rollBack();
|
||||||
|
print_r($params);
|
||||||
|
die("Critical Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->commit();
|
||||||
|
|
||||||
|
return $skippedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function finalizeEnvSwap(): void
|
||||||
|
{
|
||||||
|
$envPath = $this->projectDir . '/.env';
|
||||||
|
|
||||||
|
if (!file_exists($envPath)) {
|
||||||
|
throw new \Exception(".env file not found at $envPath");
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = file($envPath);
|
||||||
|
$newLines = [];
|
||||||
|
$xferUrlValue = null;
|
||||||
|
|
||||||
|
// 1. First pass: Find the value of XFER_DATABASE_URL
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
if (preg_match('/^XFER_DATABASE_URL=(.*)/', trim($line), $matches)) {
|
||||||
|
$xferUrlValue = $matches[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$xferUrlValue) {
|
||||||
|
throw new \Exception("XFER_DATABASE_URL not found in .env file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Second pass: Perform the swap
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$trimmed = trim($line);
|
||||||
|
|
||||||
|
// Prepend #OLD_ to the existing DATABASE_URL
|
||||||
|
if (preg_match('/^DATABASE_URL=/', $trimmed)) {
|
||||||
|
$newLines[] = "# OLD_" . $line;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename XFER_DATABASE_URL to DATABASE_URL
|
||||||
|
if (preg_match('/^XFER_DATABASE_URL=/', $trimmed)) {
|
||||||
|
$newLines[] = "DATABASE_URL=" . $xferUrlValue . PHP_EOL;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newLines[] = $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($envPath, implode('', $newLines));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isTransferEnabled(): bool
|
||||||
|
{
|
||||||
|
return (isset($_ENV['XFER_DATABASE_URL']) && !empty($_ENV['XFER_DATABASE_URL']));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function convertDBURLString(string $DBURL): array
|
||||||
|
{
|
||||||
|
$dsn = preg_split("/[:|\/|\@]+/", $DBURL);
|
||||||
|
if (count($dsn) > 6) {
|
||||||
|
die('You cannot have special characters in your password for the database entry during this process. Change the database user password to just use alpha-numeric characters, then run again. You can change it back to more secure after this transfer is complete');
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = [
|
||||||
|
'dsn' => substr($dsn[0], 4).':'.
|
||||||
|
'host='.$dsn[3].';'.
|
||||||
|
'port='.$dsn[4].';'.
|
||||||
|
'dbname='.$dsn[5],
|
||||||
|
'username' => $dsn[1],
|
||||||
|
'password' => $dsn[2]
|
||||||
|
];
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,9 @@
|
|||||||
<li><a href="#" onclick="saveNote()">Save Note</a></li>
|
<li><a href="#" onclick="saveNote()">Save Note</a></li>
|
||||||
{% if isAdmin is defined and isAdmin %}
|
{% if isAdmin is defined and isAdmin %}
|
||||||
<li><a href='/reference-editor'>Reference Editor</a></li>
|
<li><a href='/reference-editor'>Reference Editor</a></li>
|
||||||
|
{% if xferDB is defined and xferDB %}
|
||||||
|
<li><a href='/xfer-database'>Transfer Database</a></li>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li><a href='#' onclick="openRef()">Open Reference</a></li>
|
<li><a href='#' onclick="openRef()">Open Reference</a></li>
|
||||||
<li><a href='/template-editor'>Template Editor</a></li>
|
<li><a href='/template-editor'>Template Editor</a></li>
|
||||||
|
|||||||
24
templates/default/transfer_progress.html.twig
Normal file
24
templates/default/transfer_progress.html.twig
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="container mt-5 text-center">
|
||||||
|
<h2>Transferring Database...</h2>
|
||||||
|
<p>Processing: <strong>{{ current }}</strong></p>
|
||||||
|
|
||||||
|
<div class="progress mb-3" style="height: 30px;">
|
||||||
|
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
||||||
|
role="progressbar" style="width: {{ progress }}%;">
|
||||||
|
{{ progress }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Please do not close this window.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Automatically move to the next step after a short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = "{{ path('app_admin_transfer_db', {step: next_step}) }}";
|
||||||
|
}, 500);
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
29
templates/default/transfer_summary.html.twig
Normal file
29
templates/default/transfer_summary.html.twig
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="container mt-5 text-center">
|
||||||
|
<h2 class="text-success">Database Transfer Complete!</h2>
|
||||||
|
<p>Your data has been successfully moved to the new database, and the environment variables have been updated.</p>
|
||||||
|
|
||||||
|
{% if logs|length > 0 %}
|
||||||
|
<div class="alert alert-warning text-start mx-auto" style="max-width: 600px;">
|
||||||
|
<h5 class="alert-heading">Data Clean-up Notice</h5>
|
||||||
|
<p>During the transfer, the following orphaned records were safely ignored to maintain database integrity:</p>
|
||||||
|
<ul>
|
||||||
|
{% for log in logs %}
|
||||||
|
<li>{{ log }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-success mx-auto" style="max-width: 600px;">
|
||||||
|
Perfect transfer! No orphaned records were found.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<p class="text-muted">You must log in again to establish a connection with the new database.</p>
|
||||||
|
<a href="{{ path('app_logout') }}" class="btn btn-primary btn-lg">Acknowledge & Relogin</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user