[TikiWiki-commits] [Git][tikiwiki/tiki][27.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 <698f199e5d424_3b1857e0549d1@gitlab-sidekiq-low-urgency-cpu-bound-v2-645689c776-kjkqf.mail>

Bruno Kambere pushed to branch 27.x at Tiki Wiki CMS Groupware / Tiki


Commits:
5eb86cdd by Bruno Kambere at 2026-02-13T14:23:00+02:00
[BP][ENH] AttachmentsMigrateCommand: handle attachments migration errors and improve reporting
---
* [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

See merge request tikiwiki/tiki!9568

- - - - -


3 changed files:

- lib/core/Tiki/Command/AttachmentsMigrateCommand.php
- lib/core/Tiki/FileGallery/DirectMapping/EventHandler.php
- lib/filegals/filegallib.php


Changes:

=====================================
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;
             }
         }


=====================================
lib/filegals/filegallib.php
=====================================
@@ -487,8 +487,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;
         }



View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/5eb86cdd9c198dd8d1c09a1834945cd599461f61

-- 
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/5eb86cdd9c198dd8d1c09a1834945cd599461f61
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.