/ */ namespace Avatar\Controller; use Projects\Model\Project; use Zend\Http\Exception\InvalidArgumentException; use Zend\Mvc\Controller\AbstractActionController; use P4\File\File; use Zend\View\Model\JsonModel; /** * Class IndexController * @package Avatar\Controller * * * Remaining issues: * - display avatar and splash information on project page and in project edit form */ class IndexController extends AbstractActionController { /** * When a project is added or edited, run the Projects add action, then * handle the image. * * @return mixed */ public function projectAddAction() { $response = $this->forward()->dispatch( 'Projects\Controller\Index', array( 'action' => 'add' ) ); $this->doAddEdit('add'); return $response; } /** * When a project is added or edited, run the Projects edit action, then * handle the image. * * @return mixed */ public function projectEditAction() { $response = $this->forward()->dispatch( 'Projects\Controller\Index', array( 'action' => 'edit', 'project' => $this->getEvent()->getRouteMatch()->getParam('project') ) ); $this->doAddEdit('edit'); return $response; } /** * Handles image validation and thumbnail generation * if POST, validates file, generates thumbnail * if GET, displays the cached image for the specified project and size. If it does not exist, * create it, then display it. * * This method returns json if validating an image, exits once the image is displayed properly, and returns null if * errors are encountered. * * Project id must be set as a route parameter when using GET; size defaults to 64px square if unset * * Thumbnail generation is done by cropping the image to a square apsect ratio, then saving it in data/thumbs/ * Actual image is saved when the form is submitted. * * @thorws InvalidArgumentException * @return array|\Zend\Http\Response|null */ public function projectAction() { $request = $this->getRequest(); $services = $this->getServiceLocator(); $projectId = $this->getEvent()->getRouteMatch()->getParam('project'); $type = $this->getEvent()->getRouteMatch()->getParam('type'); $format = $this->getEvent()->getRouteMatch()->getParam('format'); // if post, handle preview if ($request->isPost()) { if ($type == 'avatar') { $filters = $services->get('InputFilterManager'); $projectFilter = $filters->get('ProjectFilter'); $imageFilter = $projectFilter->get('project-avatar'); } elseif ($type == 'splash') { $filters = $services->get('InputFilterManager'); $projectFilter = $filters->get('ProjectFilter'); $imageFilter = $projectFilter->get('project-splash'); } else { throw new InvalidArgumentException; } // Make certain to merge the files info! $post = array_merge_recursive( $request->getPost()->toArray(), $request->getFiles()->toArray() ); $imageFilter->setValue($post['file']); if (!$imageFilter->isValid()) { return new JsonModel( array( 'isValid' => $imageFilter->isValid(), 'messages' => array('project-' . $type => $imageFilter->getMessages()) ) ); } $data = $imageFilter->getValue(); $info = $this->getImageInfo($data['tmp_name']); $image = $this->fetchImage($data['tmp_name'], $info); $explodedMime = explode('/', $info['mime']); $tempFileName = $this->getFilename($projectId, $explodedMime[1], $type); unlink($data['tmp_name']); $tempFile = $this->getCacheDir() . '/' . $tempFileName; $image->writeimage($tempFile); return new JsonModel( array( 'isValid' => true, 'filename' => $tempFileName, 'content' => 'data:' . $info['mime'] . ';base64,'.base64_encode($image) ) ); } // if not post, display image $p4Admin = $services->get('p4_admin'); $storage = $services->get('depot_storage'); if ($type == 'avatar') { $size = $this->getEvent()->getRouteMatch()->getParam('size'); $size = (!empty($size)) ? $size : 64; } else { $size = 'splash'; } if (!Project::exists($projectId, $p4Admin)) { $this->getResponse()->setStatusCode(404); return; } try { $project = Project::fetch($projectId, $p4Admin); $cacheDir = $this->getCacheDir(); $filename = $project->get($type); if (empty($filename)) { $this->getResponse()->setStatusCode(404); return; } $cacheFile = $cacheDir . '/' . $size . '_' . $filename; if (!is_readable($cacheFile)) { $depotPath = $type . 's/'; $depotFile = $storage->absolutize($depotPath . $filename); try { $imageFile = File::fetch($depotFile, $p4Admin, true); } catch (\P4\File\Exception\NotFoundException $e) { $this->getResponse()->setStatusCode(404); return; } catch (\P4\Spec\Exception\NotFoundException $e) { $this->getResponse()->setStatusCode(404); return; } catch (\Exception $e) { $this->getResponse()->setStatusCode(500); return; } $pool = $p4Admin->getService('clients'); $pool->grab(); $pool->reset(true); $local = $imageFile->open()->getLocalFilename(); $image = $this->fetchImage($local); if ($type == 'avatar') { $image->scaleImage($size, $size, true); } $image->writeimage($cacheFile); } } catch (Exception $e) { $this->getResponse()->setStatusCode(404); return; } $info = $this->getImageInfo($cacheFile); // if this is a test, don't actually output the image if ($this->getRequest()->isTest) { return new JsonModel($info); } elseif ($format == 'base64') { echo 'data:' . $info['mime'] . ';base64,'.base64_encode(file_get_contents($cacheFile)); exit; } header('Content-Type: ' . $info['mime']); header('Content-Transfer-Encoding: binary'); header('Content-Length: ' . filesize($cacheFile)); header('Content-Disposition: filename="' . $cacheFile . '"'); readfile($cacheFile); exit; } /** * @param string $mode The mode to set on the filter - add or edit. */ protected function doAddEdit($mode) { if ($mode != 'add' && $mode != 'edit') { return; } $request = $this->getRequest(); $post = $request->getPost()->toArray(); if (!$request->isPost()) { return; } // configure our filter with the p4 connection and add/edit mode $filter = $this->getServiceLocator()->get('InputFilterManager')->get('ProjectFilter'); $filter->setMode($mode) ->setData($post); $isValid = $filter->isValid(); if (!$isValid) { return; } $values = $filter->getValues(); // note that this skips the empty value foreach (array('avatar', 'splash') as $type) { if (!array_key_exists($type, $post) || empty($post[$type])) { continue; } $this->submitImage($values, $type); } } /** * Handles submitting image to the depot. Triggered on add/edit. * * @param array $data The project data that was saved from the form. * @param string $type The type of image we're submitting; avatar or splash; * dictates depot and file path. */ protected function submitImage($data = array(), $type = 'avatar') { $filename = $this->getCacheDir() . '/' . $data[$type]; $id = $data['id']; $services = $this->getServiceLocator(); $depotPath = $type . 's/'; $depotFile = $depotPath . $this->getFilename($id, substr(strrchr($filename, "."), 1), $type); $storage = $services->get('depot_storage'); $depotFile = $storage->absolutize($depotFile); $storage->writeFromFile($depotFile, $filename); // clear file cache of any size so that the new file is cached instead $clearPattern = $this->getCacheDir() . '/' . '*' . $this->getFilename($id, substr(strrchr($filename, "."), 1), $type); array_map('unlink', glob($clearPattern)); } private function getImageInfo($file) { return getimagesize($file); } /** * @param $file File path to the image. * @param null $info Information array to be filled. * @return \Imagick Returns created image. * @throws \Exception Throws Exception on invalid filetype - only jpeg, png, and gif are allowed. */ private function fetchImage($file, &$info = null) { if (is_null($info)) { $info = $this->getImageInfo($file); } switch($info[2]) { case IMAGETYPE_JPEG: $image = new \Imagick('jpeg:' . $file); break; case IMAGETYPE_PNG: $image = new \Imagick('png:' . $file); break; case IMAGETYPE_GIF: $image = new \Imagick('gif:' . $file); break; default: throw new \Exception('Invalid filetype!'); } return $image; } protected function getFilename($id, $fileType, $imageType = '') { if ($imageType != '') { $imageType = $imageType . '.'; } return md5($id) . '.' . $imageType . strtolower($fileType); } /** * Get the path to write converted images to. Ensure directory is writable. * * @return string the cache directory to write to * @throws \RuntimeException if the directory cannot be created or made writable */ protected function getCacheDir() { $dir = DATA_PATH . '/cache/images'; if (!is_dir($dir)) { @mkdir($dir, 0700, true); } if (!is_writable($dir)) { @chmod($dir, 0700); } if (!is_dir($dir) || !is_writable($dir)) { throw new \RuntimeException( "Cannot write to cache directory ('" . $dir . "'). Check permissions." ); } return $dir; } }