[TikiWiki-commits] [Git][tikiwiki/tiki][29.x] [BP][ENH] AttachmentsMigrateCommand: handle attachments migration errors and improve reporting
"Bruno Kambere \(@kambereBr\) via TikiWiki-cvs" <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <698f12e1e77ae_3b18586c8995f@gitlab-sidekiq-low-urgency-cpu-bound-v2-645689c776-mfghk.mail> |
Bruno Kambere pushed to branch 29.x at Tiki Wiki CMS Groupware / Tiki
Commits:
9a7c7f3f by Bruno Kambere at 2026-02-13T13:53:24+02:00
[BP][ENH] AttachmentsMigrateCommand: handle attachments migration errors and improve reporting
---
* [ENH] AttachmentsMigrateCommand: handle attachments migration errors and improve reporting
---
* [ENH] AttachmentsMigrateCommand: add confirmation prompts for migration direction
* [FIX] FileGalLib: Fix array offset errors in file gallery operations
* [ENH] AttachmentsMigrateCommand: enhance reverse migration from file galleries to attachments
* [ENH] AttachmentsMigrateCommand: handle attachments migration errors and improve reporting
See merge request tikiwiki/tiki!9538
See merge request tikiwiki/tiki!9567
- - - - -
3 changed files:
- lib/Filegals/FileGalLib.php
- lib/core/Tiki/Command/AttachmentsMigrateCommand.php
- lib/core/Tiki/FileGallery/DirectMapping/EventHandler.php
Changes:
=====================================
lib/Filegals/FileGalLib.php
=====================================
@@ -507,8 +507,10 @@ class FileGalLib extends TikiLib
return false;
}
if (empty($galleryId)) {
- $info = $this->get_file_info($id);
- $galleryId = $info['galleryId'];
+ $info = $this->get_file_gallery_info($id);
+ if ($info) {
+ $galleryId = $info['parentId'] ?? 0;
+ }
} else {
$info = null;
}
=====================================
lib/core/Tiki/Command/AttachmentsMigrateCommand.php
=====================================
@@ -59,25 +59,73 @@ class AttachmentsMigrateCommand extends Command
return Command::SUCCESS;
}
+ // Display migration direction confirmation
+ if ($prefs['feature_use_fgal_for_wiki_attachments'] === 'y') {
+ $output->writeln('');
+ $output->writeln('<comment>' . tr('WARNING: This migration will convert %0 attachments. From legacy attachments to File galleries.', $count) . '</comment>');
+ $output->writeln('<comment>' . tr('Do you want to proceed? (yes/no)') . '</comment>');
+ $output->writeln('');
+ $handle = fopen("php://stdin", "r");
+ $line = fgets($handle);
+ } else {
+ $output->writeln('');
+ $output->writeln('<comment>' . tr('WARNING: This migration will convert %0 attachments. From File galleries to legacy attachments.', $count) . '</comment>');
+ $output->writeln('<comment>' . tr('Target storage: %0', $prefs['w_use_db'] === 'y' ? 'Database' : 'Filesystem (' . $prefs['w_use_dir'] . ')') . '</comment>');
+ $output->writeln('<comment>' . tr('Do you want to proceed? (yes/no)') . '</comment>');
+ $output->writeln('');
+ $handle = fopen("php://stdin", "r");
+ $line = fgets($handle);
+ }
+
+ if (trim(strtolower($line)) != 'y' && trim(strtolower($line)) != 'yes') {
+ $output->writeln('<comment>' . tr('Migration cancelled.') . '</comment>');
+ return Command::SUCCESS;
+ }
+
$remove_orphans = $input->getOption('remove-orphans');
+ $missingFiles = [];
+ $migratedCount = 0;
+ $skippedCount = 0;
+ $orphanCount = 0;
+ $errorCount = 0;
if ($prefs['feature_use_fgal_for_wiki_attachments'] === 'y') {
- $count = 0;
$result = $wikilib->list_all_attachments();
foreach ($result['data'] as $att) {
$output->writeln(tr('Processing page %0, attachment %1 %2...', $att['page'], $att['attId'], $att['filename']));
- // find or create attachments gallery for the corresponding wiki apge
+
+ // find or create attachments gallery for the corresponding wiki page
$galleryId = $filegallib->get_attachment_gallery($att['page'], 'wiki page', true);
if (! $galleryId) {
if ($remove_orphans) {
- $output->writeln(tr('Wiki page no found, removing attachment...'));
+ $output->writeln(tr('Wiki page not found, removing attachment...'));
$wikilib->remove_wiki_attachment($att['attId']);
+ $orphanCount++;
} else {
$output->writeln('<error>' . tr('File gallery for page %0 could not be found or created. Does the page exist?', $att['page']) . '</error>');
$output->writeln(tr('Hint: run this command with --remove-orphans to delete these attachments.'));
+ $skippedCount++;
}
continue;
}
+
+ // Check if file exists (when not stored in database)
+ if ($att['path']) {
+ $filePath = $prefs['w_use_dir'] . $att['path'];
+ if (! file_exists($filePath)) {
+ $output->writeln('<comment>' . tr('Skipping: File not found at %0', $filePath) . '</comment>');
+ $missingFiles[] = [
+ 'page' => $att['page'],
+ 'attId' => $att['attId'],
+ 'filename' => $att['filename'],
+ 'path' => $filePath,
+ 'hash' => $att['path']
+ ];
+ $skippedCount++;
+ continue;
+ }
+ }
+
// create file and replace its contents
$file = new TikiFile([
'galleryId' => $galleryId,
@@ -87,7 +135,23 @@ class AttachmentsMigrateCommand extends Command
'hits' => $att['hits'],
]);
$file->setParam('created', $att['created']);
- $data = $wikilib->get_item_attachement_data($att);
+
+ try {
+ $data = $wikilib->get_item_attachement_data($att);
+ } catch (\Throwable $e) {
+ $output->writeln('<error>' . tr('Failed to read attachment data: %0', $e->getMessage()) . '</error>');
+ $missingFiles[] = [
+ 'page' => $att['page'],
+ 'attId' => $att['attId'],
+ 'filename' => $att['filename'],
+ 'path' => $att['path'] ? ($prefs['w_use_dir'] . $att['path']) : 'database',
+ 'hash' => $att['path'] ?? '',
+ 'error' => $e->getMessage()
+ ];
+ $errorCount++;
+ continue;
+ }
+
$name = $att['filename'];
if (strlen($name) > 40) {
$name = substr($name, 0, 18) . '...' . substr($name, -18);
@@ -96,6 +160,7 @@ class AttachmentsMigrateCommand extends Command
$fileId = $file->replace($data, $att['filetype'], $name, $att['filename']);
} catch (\Throwable $e) {
$output->writeln('<error>' . tr('Failed converting attachment to a file: %0 in %1:%2', $e->getMessage(), $e->getFile(), $e->getLine()) . '</error>');
+ $errorCount++;
continue;
}
// remove wiki attachment row
@@ -151,41 +216,189 @@ class AttachmentsMigrateCommand extends Command
$tikilib->update_page($pageInfo['pageName'], $matches->getText(), tra('attachment conversion'), 'admin', '127.0.0.1', null, 0, '', null, null, null, '', '', true);
}
}
- $count++;
+ $migratedCount++;
+ }
+
+ // Generate migration report
+ $output->writeln('');
+ $output->writeln('<info>MIGRATION REPORT' . '</info>');
+ $output->writeln('<info>----------------' . '</info>');
+ $output->writeln('<info>' . tr('Successfully migrated: %0', $migratedCount) . '</info>');
+ $output->writeln('<comment>' . tr('Skipped (missing files): %0', $skippedCount) . '</comment>');
+ if ($orphanCount > 0) {
+ $output->writeln('<comment>' . tr('Removed (orphans): %0', $orphanCount) . '</comment>');
+ }
+ if ($errorCount > 0) {
+ $output->writeln('<error>' . tr('Failed with errors: %0', $errorCount) . '</error>');
+ }
+ $output->writeln('<info>' . tr('Total processed: %0', $migratedCount + $skippedCount + $orphanCount + $errorCount) . '</info>');
+
+ // Report missing files in detail
+ if (! empty($missingFiles)) {
+ $output->writeln('');
+ $output->writeln('<error>MISSING FILES DETAILS' . '</error>');
+ $output->writeln('<comment>' . tr('The following attachments could not be migrated because their files are missing:') . '</comment>');
+ $output->writeln('');
+
+ $table = new Table($output);
+ $table->setHeaders(['Page', 'AttId', 'Filename', 'Hash', 'Path']);
+ foreach ($missingFiles as $missing) {
+ $table->addRow([
+ $missing['page'],
+ $missing['attId'],
+ $missing['filename'],
+ $missing['hash'],
+ $missing['path']
+ ]);
+ }
+ $table->render();
+
+ $output->writeln('');
+ $output->writeln('<error>RECOMMENDED ACTIONS' . '</error>');
+ $output->writeln('1. ' . tr('Verify your w_use_dir preference value is correct:'));
+ $output->writeln(' ' . tr('Current value: %0', $prefs['w_use_dir']));
+ $output->writeln(' ' . tr('Check if this directory exists and contains the attachment files'));
+ $output->writeln('');
}
- $output->writeln('<comment>' . tr('Finished migrating legacy attachments to file galleries. Total files migrated: %0', $count) . '</comment>');
} else {
+ // Migrating from file galleries to attachments
$mapping = [];
+ $reverseErrors = [];
+ $reverseMigratedCount = 0;
+ $reverseSkippedCount = 0;
+ $reverseErrorCount = 0;
+
$result = $filegallib->list_file_galleries(0, -1, 'galleryId', '', '', $prefs['fgal_root_wiki_attachments_id']);
foreach ($result['data'] as $gal_info) {
$output->writeln(tr('Processing file gallery %0 %1...', $gal_info['id'], $gal_info['name']));
+
+ // Verify gallery info exists before proceeding
+ $fullGalInfo = $filegallib->get_file_gallery_info($gal_info['id']);
+ if (! $fullGalInfo) {
+ $output->writeln('<comment>' . tr('Skipping: Gallery info not found for gallery %0', $gal_info['id']) . '</comment>');
+ $reverseSkippedCount++;
+ continue;
+ }
+
+ // Check if corresponding wiki page exists
+ $pageInfo = $tikilib->get_page_info($gal_info['name']);
+ if (! $pageInfo) {
+ $output->writeln('<comment>' . tr('Skipping: Wiki page "%0" does not exist', $gal_info['name']) . '</comment>');
+ $reverseSkippedCount++;
+ // Don't delete the gallery if page doesn't exist - let admin decide
+ continue;
+ }
+
$files = $filegallib->get_files(0, -1, 'fileId', '', $gal_info['id']);
+ $galleryHasErrors = false;
+
foreach ($files['data'] as $file_info) {
$output->writeln(tr('Processing file %0 %1...', $file_info['id'], $file_info['name']));
- // create wiki attachment and store data or path
- $file = TikiFile::id($file_info['id']);
- $data = $file->getContents();
- if ($prefs['w_use_db'] === 'y') {
- $fhash = '';
- } else {
- $fhash = $tikilib->get_attach_hash_file_name($file->filename);
- $fp = fopen($prefs['w_use_dir'] . $fhash, "wb");
- fwrite($fp, $data);
- fclose($fp);
- $data = '';
+
+ try {
+ // create wiki attachment and store data or path
+ $file = TikiFile::id($file_info['id']);
+ $data = $file->getContents();
+
+ if ($prefs['w_use_db'] === 'y') {
+ $fhash = '';
+ } else {
+ // Verify w_use_dir is writable
+ if (! is_dir($prefs['w_use_dir'])) {
+ throw new \Exception(tr('Directory does not exist: %0', $prefs['w_use_dir']));
+ }
+ if (! is_writable($prefs['w_use_dir'])) {
+ throw new \Exception(tr('Directory is not writable: %0', $prefs['w_use_dir']));
+ }
+
+ $fhash = $tikilib->get_attach_hash_file_name($file->filename);
+ $targetPath = $prefs['w_use_dir'] . $fhash;
+
+ $fp = fopen($targetPath, "wb");
+ if (! $fp) {
+ throw new \Exception(tr('Failed to open file for writing: %0', $targetPath));
+ }
+ fwrite($fp, $data);
+ fclose($fp);
+ $data = '';
+ }
+
+ $attId = $wikilib->wiki_attach_file($gal_info['name'], $file->filename, $file->filetype, $file->filesize, $data, $file->description, $file->user, $fhash);
+
+ // remove from file galleries
+ $file->delete();
+
+ $mapping[] = [$file->fileId, $attId, $file->filename];
+ $reverseMigratedCount++;
+ } catch (\Throwable $e) {
+ $output->writeln('<error>' . tr('Failed to migrate file %0: %1', $file_info['name'], $e->getMessage()) . '</error>');
+ $reverseErrors[] = [
+ 'gallery' => $gal_info['name'],
+ 'fileId' => $file_info['id'],
+ 'filename' => $file_info['name'],
+ 'error' => $e->getMessage()
+ ];
+ $reverseErrorCount++;
+ $galleryHasErrors = true;
}
- $attId = $wikilib->wiki_attach_file($gal_info['name'], $file->filename, $file->filetype, $file->filesize, $data, $file->description, $file->user, $fhash);
- // remove from file galleries
- $file->delete();
- $mapping[] = [$file->fileId, $attId, $file->filename];
}
- $filegallib->remove_file_gallery($gal_info['id']);
+
+ // Only remove gallery if all files were successfully migrated
+ if (! $galleryHasErrors && count($files['data']) > 0) {
+ try {
+ $filegallib->remove_file_gallery($gal_info['id']);
+ } catch (\Throwable $e) {
+ $output->writeln('<comment>' . tr('Note: Gallery %0 could not be removed: %1', $gal_info['name'], $e->getMessage()) . '</comment>');
+ }
+ } elseif ($galleryHasErrors) {
+ $output->writeln('<comment>' . tr('Gallery %0 was not removed due to migration errors', $gal_info['name']) . '</comment>');
+ }
+ }
+
+ // Generate reverse migration report
+ $output->writeln('');
+ $output->writeln('<info>MIGRATION REPORT' . '</info>');
+ $output->writeln('<info>----------------' . '</info>');
+ $output->writeln('<info>' . tr('Successfully migrated: %0', $reverseMigratedCount) . '</info>');
+ $output->writeln('<comment>' . tr('Skipped: %0', $reverseSkippedCount) . '</comment>');
+ if ($reverseErrorCount > 0) {
+ $output->writeln('<error>' . tr('Failed with errors: %0', $reverseErrorCount) . '</error>');
+ }
+ $output->writeln('<info>' . tr('Total processed: %0', $reverseMigratedCount + $reverseSkippedCount + $reverseErrorCount) . '</info>');
+ $output->writeln('');
+
+ // Show error details if any
+ if (! empty($reverseErrors)) {
+ $output->writeln('<error>ERROR DETAILS' . '</error>');
+ $table = new Table($output);
+ $table->setHeaders(['Gallery/Page', 'File ID', 'Filename', 'Error']);
+ foreach ($reverseErrors as $error) {
+ $table->addRow([
+ $error['gallery'],
+ $error['fileId'],
+ $error['filename'],
+ $error['error']
+ ]);
+ }
+ $table->render();
+ $output->writeln('');
+
+ $output->writeln('<error>RECOMMENDED ACTIONS' . '</error>');
+ $output->writeln('1. ' . tr('Verify your w_use_dir preference value is correct:'));
+ $output->writeln(' ' . tr('Current value: %0', $prefs['w_use_dir']));
+ $output->writeln(' ' . tr('Ensure directory exists and is writable'));
+ $output->writeln('');
+ $output->writeln('2. ' . tr('Fix any reported errors and run the migration again'));
+ $output->writeln('');
+ }
+
+ if (! empty($mapping)) {
+ $output->writeln('<comment>' . tr('Replacing file references with attachment references in wiki pages must be done manually. Here\'s a table with ID mapping:') . '</comment>');
+ $table = new Table($output);
+ $table->setHeaders(['File ID', 'Attachment ID', 'File Name']);
+ $table->setRows($mapping);
+ $table->render();
}
- $output->writeln('<comment>' . tr('Replacing file references with attachment references in wiki pages must be done manually. Here\'s a table with ID mapping:') . '</comment>');
- $table = new Table($output);
- $table->setHeaders(['File ID', 'Attachment ID', 'File Name']);
- $table->setRows($mapping);
- $table->render();
}
return Command::SUCCESS;
=====================================
lib/core/Tiki/FileGallery/DirectMapping/EventHandler.php
=====================================
@@ -61,12 +61,12 @@ class EventHandler
protected function isDirectFgal($args)
{
- if (! isset($args['info']) || $args['info']['type'] != 'direct') {
+ if (! isset($args['info']['type']) || $args['info']['type'] !== 'direct') {
return false;
}
if (! empty($args['info']['direct'])) {
$config = json_decode($args['info']['direct'], true);
- if ($config['adapter'] != 'inherit') {
+ if (! isset($config['adapter']) || $config['adapter'] !== 'inherit') {
return false;
}
}
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/9a7c7f3f113acc0896872c5bf74013f11ba76c20
--
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/9a7c7f3f113acc0896872c5bf74013f11ba76c20
You're receiving this email because of your account on gitlab.com.
_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs