/** * {@inheritdoc} * * @throws \InvalidArgumentException */ public function transform(File $file, $self = false) { $config = $this->getConfig(); $width = $file->width(); $height = $file->height(); $src_x = 0; $src_y = 0; $src_w = $width; $src_h = $height; switch ($config['direction']) { case self::VERTICAL: $src_y = $height; $src_h = -$height; break; case self::HORIZONTAL: $src_x = $width; $src_w = -$width; break; case self::BOTH: $src_x = $width; $src_y = $height; $src_w = -$width; $src_h = -$height; break; default: throw new InvalidArgumentException(sprintf('Invalid flip direction %s', $config['direction'])); break; } return $this->_process($file, array('dest_w' => $width, 'dest_h' => $height, 'source_x' => $src_x, 'source_y' => $src_y, 'source_w' => $src_w, 'source_h' => $src_h, 'quality' => $config['quality'], 'overwrite' => $self, 'target' => sprintf('%s-flip-%s', $file->name(), $config['direction']))); }
/** * {@inheritdoc} * * @throws \InvalidArgumentException */ public function transform(File $file, $self = false) { $config = $this->getConfig(); $baseWidth = $file->width(); $baseHeight = $file->height(); $width = $config['width']; $height = $config['height']; if (is_numeric($width) && !$height) { $height = round($baseHeight / $baseWidth * $width); } else { if (is_numeric($height) && !$width) { $width = round($baseWidth / $baseHeight * $height); } else { if (!is_numeric($height) && !is_numeric($width)) { throw new InvalidArgumentException('Invalid width and height for crop'); } } } $location = $config['location']; $widthScale = $baseWidth / $width; $heightScale = $baseHeight / $height; $src_x = 0; $src_y = 0; $src_w = $baseWidth; $src_h = $baseHeight; // If an array is passed, use those dimensions if (is_array($location)) { list($src_x, $src_y, $src_w, $src_h) = $location; // Source width is larger, use height scale as the base } else { if ($widthScale > $heightScale) { $src_w = $width * $heightScale; // Position horizontally in the middle if ($location === self::CENTER) { $src_x = $baseWidth / 2 - $width / 2 * $heightScale; // Position at the far right } else { if ($location === self::RIGHT || $location === self::BOTTOM) { $src_x = $baseWidth - $src_w; } } // Source height is larger, use width scale as the base } else { $src_h = $height * $widthScale; // Position vertically in the middle if ($location === self::CENTER) { $src_y = $baseHeight / 2 - $height / 2 * $widthScale; // Position at the bottom } else { if ($location === self::RIGHT || $location === self::BOTTOM) { $src_y = $baseHeight - $src_h; } } } } return $this->_process($file, array('dest_w' => $width, 'dest_h' => $height, 'source_x' => $src_x, 'source_y' => $src_y, 'source_w' => $src_w, 'source_h' => $src_h, 'quality' => $config['quality'], 'overwrite' => $self)); }
/** * Calculate the transformation options and process. * * @param \Transit\File $file * @param bool $self * @return \Transit\File * @throws \InvalidArgumentException */ public function transform(File $file, $self = false) { $config = $this->_config; if (empty($config['percent']) || !is_numeric($config['percent'])) { throw new InvalidArgumentException('Invalid percent for scaling'); } $width = round($file->width() * $config['percent']); $height = round($file->height() * $config['percent']); return $this->_process($file, array('dest_w' => $width, 'dest_h' => $height, 'quality' => $config['quality'], 'overwrite' => $self)); }
/** * {@inheritdoc} * * @throws \InvalidArgumentException */ public function transform(File $file, $self = false) { $config = $this->getConfig(); $baseWidth = $file->width(); $baseHeight = $file->height(); $width = $config['width']; $height = $config['height']; $newWidth = null; $newHeight = null; if (is_numeric($width) && !$height) { $height = round($baseHeight / $baseWidth * $width); } else { if (is_numeric($height) && !$width) { $width = round($baseWidth / $baseHeight * $height); } else { if (!is_numeric($height) && !is_numeric($width)) { throw new InvalidArgumentException('Invalid width and height for resize'); } } } // Maintains the aspect ratio of the image if ($config['aspect']) { $widthScale = $width / $baseWidth; $heightScale = $height / $baseHeight; if ($config['mode'] === self::WIDTH && $widthScale < $heightScale || $config['mode'] === self::HEIGHT && $widthScale > $heightScale) { $newWidth = $width; $newHeight = $baseHeight * $newWidth / $baseWidth; } else { if ($config['mode'] === self::WIDTH && $widthScale > $heightScale || $config['mode'] === self::HEIGHT && $widthScale < $heightScale) { $newHeight = $height; $newWidth = $newHeight * $baseWidth / $baseHeight; } else { $newWidth = $width; $newHeight = $height; } } } else { $newWidth = $width; $newHeight = $height; } // Don't expand if we don't want it too if (!$config['expand']) { if ($newWidth > $baseWidth) { $newWidth = $baseWidth; } if ($newHeight > $baseHeight) { $newHeight = $baseHeight; } } return $this->_process($file, array('dest_w' => $newWidth, 'dest_h' => $newHeight, 'quality' => $config['quality'], 'overwrite' => $self)); }
/** * Delete the temporary file. * * @param Model $model * @return bool */ public function afterValidate(Model $model) { if ($this->_tempFile) { $this->_tempFile->delete(); } return true; }
/** * {@inheritdoc} * * @throws \InvalidArgumentException */ public function transform(File $file, $self = false) { $config = $this->getConfig(); $baseWidth = $file->width(); $baseHeight = $file->height(); $width = $config['width']; $height = $config['height']; $newWidth = null; $newHeight = null; if (!is_numeric($height) && !is_numeric($width)) { throw new InvalidArgumentException('Invalid width and height for resize'); } $widthAspect = $baseWidth / $width; $heightAspect = $baseHeight / $height; $aspect = $heightAspect > $widthAspect ? $heightAspect : $widthAspect; $newWidth = $baseWidth / $aspect; $newHeight = $baseHeight / $aspect; // Do a simple resize if there is no fill defined if (!$config['fill'] || $newHeight == $height && $newWidth == $width) { return $this->_process($file, array('dest_w' => $newWidth, 'dest_h' => $newHeight, 'quality' => $config['quality'], 'overwrite' => $self)); } // Determine the alignment $vertGap = 0; $horiGap = 0; // Horizontal if ($newWidth < $width) { if ($config['horizontal'] === self::CENTER) { $horiGap = ($width - $newWidth) / 2; } else { if ($config['horizontal'] === self::RIGHT) { $horiGap = $width - $newWidth; } } // Vertical } else { if ($newHeight < $height) { if ($config['vertical'] === self::CENTER) { $vertGap = ($height - $newHeight) / 2; } else { if ($config['vertical'] === self::BOTTOM) { $vertGap = $height - $newHeight; } } } } return $this->_process($file, array('width' => $width, 'height' => $height, 'dest_x' => $horiGap, 'dest_y' => $vertGap, 'dest_w' => $newWidth, 'dest_h' => $newHeight, 'quality' => $config['quality'], 'overwrite' => $self, 'preCallback' => array($this, 'fill'))); }
/** * {@inheritdoc} */ public function transform(File $file, $self = false) { if ($file->type() !== 'image/jpeg') { return $file; // Exif only in JPGs } $width = $file->width(); $height = $file->height(); $exif = $file->exif(); $this->setConfig('degrees', 0); // Reset degrees $options = array('dest_w' => $width, 'dest_h' => $height, 'source_w' => $width, 'source_h' => $height, 'quality' => $this->getConfig('quality'), 'overwrite' => $self, 'target' => sprintf('%s-exif-%s', $file->name(), $exif['orientation'] ?: 0)); switch ($exif['orientation']) { case 2: // Flip horizontally $options['source_x'] = $width; $options['source_w'] = -$width; break; case 3: // Rotate 180 degrees $this->setConfig('degrees', 180); break; case 4: case 5: case 7: // Flip vertically $options['source_y'] = $height; $options['source_h'] = -$height; // Also rotate -90 degrees for orientation 5 if ($exif['orientation'] == 5) { $this->setConfig('degrees', -90); } // Or rotate 90 degrees for orientation 7 if ($exif['orientation'] == 7) { $this->setConfig('degrees', 90); } break; case 6: $this->setConfig('degrees', -90); break; case 8: $this->setConfig('degrees', 90); break; default: // Correct, strip exif only break; } if ($degrees = $this->getConfig('degrees')) { $options['postCallback'] = array($this, 'rotate'); } return $this->_process($file, $options); }
/** * Transport the file to Amazon Glacier and return the archive ID. * * @param \Transit\File $file * @return string * @throws \Transit\Exception\TransportationException */ public function transport(File $file) { $config = $this->_config; $response = null; // If larger then 100MB, split upload into parts if ($file->size() >= 100 * Size::MB) { $uploader = UploadBuilder::newInstance()->setClient($this->getClient())->setSource($file->path())->setVaultName($config['vault'])->setAccountId($config['accountId'] ?: '-')->setPartSize(10 * Size::MB)->build(); try { $response = $uploader->upload(); } catch (MultipartUploadException $e) { $uploader->abort(); } } else { $response = $this->getClient()->uploadArchive(array_filter(array('vaultName' => $config['vault'], 'accountId' => $config['accountId'], 'body' => EntityBody::factory(fopen($file->path(), 'r'))))); } // Return archive ID if successful if ($response) { $file->delete(); return $response->getPath('archiveId'); } throw new TransportationException(sprintf('Failed to transport %s to Amazon Glacier', $file->basename())); }
/** * Transport the file to a remote location. * * @param \Transit\File $file * @return string * @throws \Transit\Exception\TransportationException */ public function transport(File $file) { $config = $this->_config; $key = ltrim($config['folder'], '/') . $file->basename(); $response = null; // If larger then 100MB, split upload into parts if ($file->size() >= 100 * Size::MB) { $uploader = UploadBuilder::newInstance()->setClient($this->getClient())->setSource($file->path())->setBucket($config['bucket'])->setKey($key)->setMinPartSize(10 * Size::MB)->build(); try { $response = $uploader->upload(); } catch (MultipartUploadException $e) { $uploader->abort(); } } else { $response = $this->getClient()->putObject(array_filter(array('Key' => $key, 'Bucket' => $config['bucket'], 'Body' => EntityBody::factory(fopen($file->path(), 'r')), 'ACL' => $config['acl'], 'ContentType' => $file->type(), 'ServerSideEncryption' => $config['encryption'], 'StorageClass' => $config['storage'], 'Metadata' => $config['meta']))); } // Return S3 URL if successful if ($response) { $file->delete(); return sprintf('%s/%s/%s', S3Client::getEndpoint($this->getClient()->getDescription(), $config['region'], $config['scheme']), $config['bucket'], $key); } throw new TransportationException(sprintf('Failed to transport %s to Amazon S3', $file->basename())); }
/** * Attempt to delete a file using the attachment settings. * * @uses Transit\File * * @param Model $model * @param string $field * @param string $path * @return bool */ protected function _deleteFile(Model $model, $field, $path) { if (empty($this->settings[$model->alias][$field])) { return false; } $attachment = $this->_settingsCallback($model, $this->settings[$model->alias][$field]); $basePath = $attachment['uploadDir'] ?: $attachment['tempDir']; try { // Delete remote file if ($attachment['transport']) { $transporter = $this->_getTransporter($attachment['transport']); return $transporter->delete($path); // Delete local file } else { $file = new File($basePath . basename($path)); return $file->delete(); } } catch (Exception $e) { $this->log($e->getMessage(), LOG_DEBUG); } return false; }
/** * Find a valid target path taking into account file existence and overwriting. * * @param \Transit\File|string $file * @param bool $overwrite * @return string */ public function findDestination($file, $overwrite = false) { if ($file instanceof File) { $name = $file->name(); $ext = '.' . $file->ext(); } else { $name = $file; $ext = ''; if ($pos = mb_strrpos($name, '.')) { $ext = mb_substr($name, $pos, mb_strlen($name) - $pos); $name = mb_substr($name, 0, $pos); } } $target = $this->_directory . $name . $ext; if (!$overwrite) { $no = 1; while (file_exists($target)) { $target = sprintf('%s%s-%s%s', $this->_directory, $name, $no, $ext); $no++; } } return $target; }
/** * Transform the image using the defined options. * * @param \Transit\File $file * @param array $options * @return \Transit\File * @throws \DomainException */ protected function _process(File $file, array $options) { if (!$file->isImage()) { throw new DomainException(sprintf('%s is not a valid image', $file->basename())); } $sourcePath = $file->path(); $mimeType = $file->type(); // Create an image to work with switch ($mimeType) { case 'image/gif': $sourceImage = imagecreatefromgif($sourcePath); break; case 'image/png': $sourceImage = imagecreatefrompng($sourcePath); break; case 'image/jpg': case 'image/jpeg': case 'image/pjpeg': $sourceImage = imagecreatefromjpeg($sourcePath); break; default: throw new DomainException(sprintf('%s can not be transformed', $mimeType)); break; } // Gather options $options = $options + array('dest_x' => 0, 'dest_y' => 0, 'dest_w' => null, 'dest_h' => null, 'source_x' => 0, 'source_y' => 0, 'source_w' => $file->width(), 'source_h' => $file->height(), 'quality' => 100, 'overwrite' => false, 'target' => ''); $targetImage = imagecreatetruecolor($options['dest_w'], $options['dest_h']); // If gif/png allow transparencies if ($mimeType === 'image/gif' || $mimeType === 'image/png') { imagealphablending($targetImage, false); imagesavealpha($targetImage, true); imagefilledrectangle($targetImage, 0, 0, $options['dest_w'], $options['dest_h'], imagecolorallocatealpha($targetImage, 255, 255, 255, 127)); } // Lets take our source and apply it to the temporary file and resize imagecopyresampled($targetImage, $sourceImage, $options['dest_x'], $options['dest_y'], $options['source_x'], $options['source_y'], $options['dest_w'], $options['dest_h'], $options['source_w'], $options['source_h']); // Now write the transformed image to the server if ($options['overwrite']) { $options['target'] = $file->name(); } else { if (!$options['target']) { $class = explode('\\', get_class($this)); $class = str_replace('transformer', '', strtolower(end($class))); $options['target'] = sprintf('%s-%s-%sx%s', $file->name(), $class, round($options['dest_w']), round($options['dest_h'])); } } $targetPath = sprintf('%s%s.%s', $file->dir(), $options['target'], $file->ext()); switch ($mimeType) { case 'image/gif': imagegif($targetImage, $targetPath); break; case 'image/png': imagepng($targetImage, $targetPath); break; case 'image/jpg': case 'image/jpeg': case 'image/pjpeg': imagejpeg($targetImage, $targetPath, $options['quality']); break; } // Clear memory imagedestroy($sourceImage); imagedestroy($targetImage); return new File($targetPath); }
/** * Test that move() doesn't overwrite files but appends an incremented number. */ public function testMoveNoOverwrite() { $testPath = TEST_DIR . '/test.jpg'; $movePath = TEMP_DIR . '/test-1.jpg'; copy($this->baseFile, $testPath); $this->assertFalse(file_exists($movePath)); $file = new File($testPath); $file->move(TEMP_DIR); $this->assertTrue(file_exists($movePath)); $file->delete(); }
/** * Test for this bug: https://bugs.php.net/bug.php?id=53035 */ public function testTypeDetectionForMagicBugs() { $file = new File(TEMP_DIR . '/magic-mime-verify.js'); // This will actually return text/plain because magic cant determine a text/javascript file // It can also return text/x-c in some weird corner cases // If either of these happen, fall back to the extension derived mimetype (or from $_FILES) $this->assertEquals('application/javascript', $file->type()); }