From 6d30aa28719db4722d25c25bb49e3360f624a29b Mon Sep 17 00:00:00 2001 From: Jared Hancock <jared@osticket.com> Date: Thu, 9 Jul 2015 22:43:57 -0500 Subject: [PATCH] upgrade: Defer migrating attachments from v1.6 This patch suggests that old attachments in the `upload_dir` can be located and left alone when upgrading away from osTicket v1.6. Later, via the `file` command line applet, the attachments can be migrated into the database, or to the new filesystem plugin after it is installed with: php manage.php file migrate --backend=6 --to=D This allows attachment migration to be retried in the event it failed for any reason. --- README.md | 7 ++ include/class.file.php | 62 ++++++++++++- include/cli/modules/file.php | 2 +- .../streams/core/15b30765-dd0022fb.patch.sql | 6 +- .../streams/core/15b30765-dd0022fb.task.php | 91 +++++++++---------- .../streams/core/934954de-f1ccd3bb.patch.sql | 19 +++- 6 files changed, 132 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 72c42c75f..872b2fa82 100644 --- a/README.md +++ b/README.md @@ -72,11 +72,18 @@ osTicket-1.7, visit the /scp page of you ticketing system. The upgrader will be presented and will walk you through the rest of the process. (The couple clicks needed to go through the process are pretty boring to describe). +### Upgrading from v1.6 **WARNING**: If you are upgrading from osTicket 1.6, please ensure that all your files in your upload folder are both readable and writable to your http server software. Unreadable files will not be migrated to the database during the upgrade and will be effectively lost. +After upgrading, we recommend migrating your attachments to the database or +to the new filesystem plugin. Use the `file` command-line applet to perform +the migration. + + php manage.php file migrate --backend=6 --to=D + View the UPGRADING.txt file for other todo items to complete your upgrade. Help diff --git a/include/class.file.php b/include/class.file.php index e0953876d..ef5657313 100644 --- a/include/class.file.php +++ b/include/class.file.php @@ -623,6 +623,7 @@ class FileStorageBackend { static $desc = false; static $registry; static $blocksize = 131072; + static $private = false; /** * All storage backends should call this function during the request @@ -632,8 +633,15 @@ class FileStorageBackend { self::$registry[$typechar] = $class; } - static function allRegistered() { - return self::$registry; + static function allRegistered($private=false) { + $R = self::$registry; + if (!$private) { + foreach ($R as $i=>$bk) { + if ($bk::$private) + unset($R[$i]); + } + } + return $R; } /** @@ -848,4 +856,54 @@ class AttachmentChunkedData extends FileStorageBackend { } FileStorageBackend::register('D', 'AttachmentChunkedData'); +/** + * This class provides an interface for files attached on the filesystem in + * versions previous to v1.7. The upgrader will keep the attachments on the + * disk where they were and write the path into the `attrs` field of the + * %file table. This module will continue to serve those files until they + * are migrated with the `file` cli app + */ +class OneSixAttachments extends FileStorageBackend { + static $desc = "upload_dir folder (from osTicket v1.6)"; + static $private = true; + + function read($bytes=32768, $offset=false) { + $filename = $this->meta->attrs; + if (!$this->fp) + $this->fp = @fopen($filename, 'rb'); + if (!$this->fp) + throw new IOException($filename.': Unable to open for reading'); + if ($offset) + fseek($this->fp, $offset); + if (($status = @fread($this->fp, $bytes)) === false) + throw new IOException($filename.': Unable to read from file'); + return $status; + } + + function passthru() { + $filename = $this->meta->attrs; + if (($status = @readfile($filename)) === false) + throw new IOException($filename.': Unable to read from file'); + return $status; + } + + function write($data) { + throw new IOException('This backend does not support new files'); + } + + function upload($filepath) { + throw new IOException('This backend does not support new files'); + } + + function unlink() { + $filename = $this->meta->attrs; + if (!@unlink($filename)) + throw new IOException($filename.': Unable to delete file'); + // Drop usage of the `attrs` field + $this->meta->attrs = null; + $this->meta->save(); + return true; + } +} +FileStorageBackend::register('6', 'OneSixAttachments'); ?> diff --git a/include/cli/modules/file.php b/include/cli/modules/file.php index f3f7991d3..416346939 100644 --- a/include/cli/modules/file.php +++ b/include/cli/modules/file.php @@ -59,7 +59,7 @@ class FileManager extends Module { switch ($args['action']) { case 'backends': // List configured backends - foreach (FileStorageBackend::allRegistered() as $char=>$bk) { + foreach (FileStorageBackend::allRegistered(true) as $char=>$bk) { print "$char -- {$bk::$desc} ($bk)\n"; } break; diff --git a/include/upgrader/streams/core/15b30765-dd0022fb.patch.sql b/include/upgrader/streams/core/15b30765-dd0022fb.patch.sql index 796fbf296..9fbb35360 100644 --- a/include/upgrader/streams/core/15b30765-dd0022fb.patch.sql +++ b/include/upgrader/streams/core/15b30765-dd0022fb.patch.sql @@ -18,7 +18,11 @@ INSERT INTO `%TABLE_PREFIX%file_chunk` (`file_id`, `chunk_id`, `filedata`) SELECT `id`, 0, `filedata` FROM `%TABLE_PREFIX%file`; -ALTER TABLE `%TABLE_PREFIX%file` DROP COLUMN `filedata`; +ALTER TABLE `%TABLE_PREFIX%file` + DROP COLUMN `filedata`, + ADD `bk` CHAR(1) NOT NULL DEFAULT 'D' AFTER `id`, + ADD `attrs` VARCHAR(255) AFTER `name`; + OPTIMIZE TABLE `%TABLE_PREFIX%file`; -- Finished with patch diff --git a/include/upgrader/streams/core/15b30765-dd0022fb.task.php b/include/upgrader/streams/core/15b30765-dd0022fb.task.php index f633c36c6..755c57fe7 100644 --- a/include/upgrader/streams/core/15b30765-dd0022fb.task.php +++ b/include/upgrader/streams/core/15b30765-dd0022fb.task.php @@ -95,37 +95,33 @@ class AttachmentMigrater extends MigrationTask { # need to be recalculated for every shift() operation. $info = array_pop($this->queue); # Attach file to the ticket - if (!($info['data'] = @file_get_contents($info['path']))) { + if (!@is_readable($info['path'])) { # Continue with next file return $this->skip($info['attachId'], sprintf('%s: Cannot read file contents', $info['path'])); } # Get the mime/type of each file # XXX: Use finfo_buffer for PHP 5.3+ - if(function_exists('mime_content_type')) { - //XXX: function depreciated in newer versions of PHP!!!!! - $info['type'] = mime_content_type($info['path']); - } elseif (function_exists('finfo_file')) { // PHP 5.3.0+ + if (function_exists('finfo_file')) { // PHP 5.3.0+ $finfo = finfo_open(FILEINFO_MIME_TYPE); $info['type'] = finfo_file($finfo, $info['path']); } + elseif (function_exists('mime_content_type')) { + //XXX: function depreciated in newer versions of PHP!!!!! + $info['type'] = mime_content_type($info['path']); + } # TODO: Add extension-based mime-type lookup - if (!($fileId = $this->saveAttachment($info))) { + $file = $this->saveAttachment($info); + if (!$file) return $this->skip($info['attachId'], sprintf('%s: Unable to migrate attachment', $info['path'])); - } + # Update the ATTACHMENT_TABLE record to set file_id db_query('update '.TICKET_ATTACHMENT_TABLE - .' set file_id='.db_input($fileId) + .' set file_id='.db_input($file->id) .' where attach_id='.db_input($info['attachId'])); - # Remove disk image of the file. If this fails, the migration for - # this file would not be retried, because the file_id in the - # TICKET_ATTACHMENT_TABLE has a nonzero value now - if (!@unlink($info['path'])) //XXX: what should we do on failure? - $this->error( - sprintf('%s: Unable to remove file from disk', - $info['path'])); + # TODO: Log an internal note to the ticket? return true; } @@ -231,47 +227,44 @@ class AttachmentMigrater extends MigrationTask { return $this->errorList; } - // This is the AttachmentFile::create() method from osTicket 1.7.6. It's - // been ported here so that further changes to the %file table and the - // AttachmentFile::create() method do not affect upgrades from osTicket - // 1.6 to osTicket 1.8 and beyond. + // This is (similar to) the AttachmentFile::create() method from + // osTicket 1.7.6. It's been ported here so that further changes to the + // %file table and the AttachmentFile::create() method do not affect + // upgrades from osTicket 1.6 to osTicket 1.8 and beyond. function saveAttachment($file) { - if(!$file['hash']) + if (!$file['hash']) $file['hash']=MD5(md5_file($file['path']).time()); - $file['data'] = file_get_contents($file['path']); - if(!$file['size']) - $file['size']=strlen($file['data']); - - $sql='INSERT INTO '.FILE_TABLE.' SET created=NOW() ' - .',type='.db_input($file['type']) - .',size='.db_input($file['size']) - .',name='.db_input($file['name']) - .',hash='.db_input($file['hash']); - - if (!(db_query($sql) && ($id=db_insert_id()))) - return false; - - $f = new CompatAttachmentFile($id); - $bk = new AttachmentChunkedData($f); - if (!$bk->write($file['data'])) - return false; - - return $id; + if (!$file['size']) + $file['size'] = filesize($file['path']); + + return OldOneSixFile::create(array( + 'name' => $file['name'], + 'size' => $file['size'], + 'type' => $file['type'], + 'hash' => $file['hash'], + 'bk' => '6', + 'attrs' => $file['path'], + )); } } -class CompatAttachmentFile { - var $id; - - function __construct($id) { - $this->id = $id; - } - - function getId() { - return $this->id; +class OldOneSixFile extends VerySimpleModel { + static $meta = array( + 'table' => FILE_TABLE, + 'pk' => array('id'), + 'joins' => array( + 'attachments' => array( + 'reverse' => 'Attachment.file' + ), + ), + ); + + static function create($info) { + $I = parent::create($info); + $I->save(); + return $I; } } return 'AttachmentMigrater'; -?> diff --git a/include/upgrader/streams/core/934954de-f1ccd3bb.patch.sql b/include/upgrader/streams/core/934954de-f1ccd3bb.patch.sql index c6bb713e7..9cc75cb6b 100644 --- a/include/upgrader/streams/core/934954de-f1ccd3bb.patch.sql +++ b/include/upgrader/streams/core/934954de-f1ccd3bb.patch.sql @@ -8,15 +8,30 @@ */ ALTER TABLE `%TABLE_PREFIX%file` - ADD `bk` CHAR(1) NOT NULL DEFAULT 'D' AFTER `ft`, -- RFC 4288, Section 4.2 declares max MIMEType at 255 ascii chars CHANGE `type` `type` varchar(255) collate ascii_general_ci NOT NULL default '', CHANGE `size` `size` BIGINT(20) NOT NULL DEFAULT 0, CHANGE `hash` `key` VARCHAR(86) COLLATE ascii_general_ci, ADD `signature` VARCHAR(86) COLLATE ascii_bin AFTER `key`, - ADD `attrs` VARCHAR(255) AFTER `name`, ADD INDEX (`signature`); +-- dd0022fb14892c0bb6a9700392df2de7 added `bk` and `attrs` to facilitate +-- upgrading from osTicket 1.6 without loading files into the database +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = '%TABLE_PREFIX%file' + AND table_schema = DATABASE() + AND column_name = 'bk' + ) > 0, + "SELECT 1", + "ALTER TABLE `%TABLE_PREFIX%file` + ADD `bk` CHAR(1) NOT NULL DEFAULT 'D' AFTER `ft`, + ADD `attrs` VARCHAR(255) AFTER `name`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + -- Finished with patch UPDATE `%TABLE_PREFIX%config` SET `value` = 'f1ccd3bb620e314b0ae1dbd0a1a99177' -- GitLab