/** * Rolls back a filesystem transaction, it is safe to rollback when no transaction is in progress * * @return void */ public static function rollback() { if (self::$rollback_operations === NULL) { return; } self::$rollback_operations = array_reverse(self::$rollback_operations); foreach (self::$rollback_operations as $operation) { switch ($operation['action']) { case 'append': $current_length = filesize($operation['filename']); $handle = fopen($operation['filename'], 'r+'); ftruncate($handle, $current_length - $operation['length']); fclose($handle); break; case 'delete': self::updateDeletedMap($operation['filename'], debug_backtrace()); unlink($operation['filename']); fFilesystem::updateFilenameMap($operation['filename'], '*DELETED at ' . time() . ' with token ' . uniqid('', TRUE) . '* ' . $operation['filename']); break; case 'write': file_put_contents($operation['filename'], $operation['old_data']); break; case 'rename': fFilesystem::updateFilenameMap($operation['new_name'], $operation['old_name']); rename($operation['new_name'], $operation['old_name']); break; } } // All files to be deleted should have their backtraces erased foreach (self::$commit_operations as $operation) { if (isset($operation['object'])) { self::updateDeletedMap($operation['object']->getPath(), NULL); fFilesystem::updateFilenameMap($operation['object']->getPath(), preg_replace('#*DELETED at \\d+ with token [\\w.]+* #', '', $operation['filename'])); } } self::$commit_operations = NULL; self::$rollback_operations = NULL; }
/** * Rolls back a filesystem transaction, it is safe to rollback when no transaction is in progress * * @return void */ public static function rollback() { self::$rollback_operations = array_reverse(self::$rollback_operations); foreach (self::$rollback_operations as $operation) { switch ($operation['action']) { case 'delete': self::updateExceptionMap($operation['filename'], new fProgrammerException('The action requested can not be performed because the file has been deleted')); unlink($operation['filename']); break; case 'write': file_put_contents($operation['filename'], $operation['old_data']); break; case 'rename': fFilesystem::updateFilenameMap($operation['new_name'], $operation['old_name']); rename($operation['new_name'], $operation['old_name']); break; } } // All files to be deleted should have their exceptions erased foreach (self::$commit_operations as $operation) { if (isset($operation['object'])) { self::updateExceptionMap($operation['object']->getPath(), NULL); } } self::$commit_operations = NULL; self::$rollback_operations = NULL; }
/** * Renames the current file * * If the filename already exists and the overwrite flag is set to false, * a new filename will be created. * * This operation will be reverted if a filesystem transaction is in * progress and is later rolled back. * * @param string $new_filename The new full path to the file or a new filename in the current directory * @param boolean $overwrite If the new filename already exists, `TRUE` will cause the file to be overwritten, `FALSE` will cause the new filename to change * @return fFile The file object, to allow for method chaining */ public function rename($new_filename, $overwrite) { $this->tossIfDeleted(); if (!$this->getParent()->isWritable()) { throw new fEnvironmentException('The file, %s, can not be renamed because the directory containing it is not writable', $this->file); } // If the filename does not contain any folder traversal, rename the file in the current directory if (preg_match('#^[^/\\\\]+$#D', $new_filename)) { $new_filename = $this->getParent()->getPath() . $new_filename; } $info = fFilesystem::getPathInfo($new_filename); if (!file_exists($info['dirname'])) { throw new fProgrammerException('The new filename specified, %s, is inside of a directory that does not exist', $new_filename); } // Make the filename absolute $new_filename = fDirectory::makeCanonical(realpath($info['dirname'])) . $info['basename']; if ($this->file == $new_filename && $overwrite) { return $this; } if (file_exists($new_filename) && !$overwrite) { $new_filename = fFilesystem::makeUniqueName($new_filename); } if (file_exists($new_filename)) { if (!is_writable($new_filename)) { throw new fEnvironmentException('The new filename specified, %s, already exists, but is not writable', $new_filename); } if (fFilesystem::isInsideTransaction()) { fFilesystem::recordWrite(new fFile($new_filename)); } // Windows requires that the existing file be deleted before being replaced unlink($new_filename); } else { $new_dir = new fDirectory($info['dirname']); if (!$new_dir->isWritable()) { throw new fEnvironmentException('The new filename specified, %s, is inside of a directory that is not writable', $new_filename); } } rename($this->file, $new_filename); // Allow filesystem transactions if (fFilesystem::isInsideTransaction()) { fFilesystem::recordRename($this->file, $new_filename); } fFilesystem::updateFilenameMap($this->file, $new_filename); return $this; }
/** * Saves any changes to the image * * If the file type is different than the current one, removes the current * file once the new one is created. * * This operation will be reverted by a filesystem transaction being rolled * back. If a transaction is in progress and the new image type causes a * new file to be created, the old file will not be deleted until the * transaction is committed. * * @param string $new_image_type The new file format for the image: 'NULL` (no change), `'jpg'`, `'gif'`, `'png'` * @param integer $jpeg_quality The quality setting to use for JPEG images - this may be ommitted * @param boolean $overwrite If an existing file with the same name and extension should be overwritten * @param string :$new_image_type * @param boolean :$overwrite * @return fImage The image object, to allow for method chaining */ public function saveChanges($new_image_type = NULL, $jpeg_quality = 90, $overwrite = FALSE) { // This allows ommitting the $jpeg_quality parameter, which is very useful for non-jpegs $args = func_get_args(); if (count($args) == 2 && is_bool($args[1])) { $overwrite = $args[1]; $jpeg_quality = 90; } $this->tossIfDeleted(); self::determineProcessor(); if (self::$processor == 'none') { throw new fEnvironmentException("The changes to the image can't be saved because neither the GD extension or ImageMagick appears to be installed on the server"); } $type = self::getImageType($this->file); if ($type == 'tif' && self::$processor == 'gd') { throw new fEnvironmentException('The image specified, %s, is a TIFF file and the GD extension can not handle TIFF files. Please install ImageMagick if you wish to manipulate TIFF files.', $this->file); } $valid_image_types = array('jpg', 'gif', 'png'); if ($new_image_type !== NULL && !in_array($new_image_type, $valid_image_types)) { throw new fProgrammerException('The new image type specified, %1$s, is invalid. Must be one of: %2$s.', $new_image_type, join(', ', $valid_image_types)); } if (is_numeric($jpeg_quality)) { $jpeg_quality = (int) round($jpeg_quality); } if (!is_integer($jpeg_quality) || $jpeg_quality < 1 || $jpeg_quality > 100) { throw new fProgrammerException('The JPEG quality specified, %1$s, is either not an integer, less than %2$s or greater than %3$s.', $jpeg_quality, 1, 100); } if ($new_image_type && fFilesystem::getPathInfo($this->file, 'extension') != $new_image_type) { if ($overwrite) { $path_info = fFilesystem::getPathInfo($this->file); $output_file = $path_info['dirname'] . $path_info['filename'] . '.' . $new_image_type; } else { $output_file = fFilesystem::makeUniqueName($this->file, $new_image_type); } if (file_exists($output_file)) { if (!is_writable($output_file)) { throw new fEnvironmentException('Changes to the image can not be saved because the file, %s, is not writable', $output_file); } } else { $output_dir = dirname($output_file); if (!is_writable($output_dir)) { throw new fEnvironmentException('Changes to the image can not be saved because the directory to save the new file, %s, is not writable', $output_dir); } } } else { $output_file = $this->file; if (!is_writable($output_file)) { throw new fEnvironmentException('Changes to the image can not be saved because the file, %s, is not writable', $output_file); } } // If we don't have any changes and no name change, just exit if (!$this->pending_modifications && $output_file == $this->file) { return $this; } // Wrap changes to the image into the filesystem transaction if ($output_file == $this->file && fFilesystem::isInsideTransaction()) { fFilesystem::recordWrite($this); } if (self::$processor == 'gd') { $this->processWithGD($output_file, $jpeg_quality); } elseif (self::$processor == 'imagemagick') { $this->processWithImageMagick($output_file, $jpeg_quality); } $old_file = $this->file; fFilesystem::updateFilenameMap($this->file, $output_file); // If we created a new image, delete the old one if ($output_file != $old_file) { $old_image = new fImage($old_file); $old_image->delete(); } $this->pending_modifications = array(); return $this; }