getRepositoryForDatabox($databoxId); } /** * @return Filesystem */ private function getFilesystem() { return $this->app['filesystem']; } /** @var Application */ protected $app; /** @var string */ protected $mime; /** @var string */ protected $file; /** @var string */ protected $path; /** @var record_adapter */ protected $record; /** @var media_Permalink_Adapter */ protected $permalink; /** @var boolean */ protected $is_substituted = false; /** @var string */ protected $pathfile; /** @var int */ protected $subdef_id; /** @var string */ protected $name; /** @var string */ protected $etag; /** @var DateTime */ protected $creation_date; /** @var DateTime */ protected $modification_date; /** @var bool */ protected $is_physically_present = false; /** @var integer */ private $size = 0; /** @var array */ private static $technicalFieldsList = []; /* * Players types constants */ const TYPE_VIDEO_MP4 = 'VIDEO_MP4'; const TYPE_VIDEO_FLV = 'VIDEO_FLV'; const TYPE_FLEXPAPER = 'FLEXPAPER'; const TYPE_AUDIO_MP3 = 'AUDIO_MP3'; const TYPE_IMAGE = 'IMAGE'; const TYPE_NO_PLAYER = 'UNKNOWN'; const TYPE_PDF = 'PDF'; /* * Technical datas types constants */ const TC_DATA_WIDTH = 'Width'; const TC_DATA_HEIGHT = 'Height'; const TC_DATA_COLORSPACE = 'ColorSpace'; const TC_DATA_CHANNELS = 'Channels'; const TC_DATA_ORIENTATION = 'Orientation'; const TC_DATA_THUMBNAILORIENTATION = 'ThumbnailOrientation'; const TC_DATA_COLORDEPTH = 'ColorDepth'; const TC_DATA_DURATION = 'Duration'; const TC_DATA_AUDIOCODEC = 'AudioCodec'; const TC_DATA_AUDIOSAMPLERATE = 'AudioSamplerate'; const TC_DATA_VIDEOCODEC = 'VideoCodec'; const TC_DATA_FRAMERATE = 'FrameRate'; const TC_DATA_MIMETYPE = 'MimeType'; const TC_DATA_FILESIZE = 'FileSize'; const TC_DATA_LONGITUDE = 'Longitude'; const TC_DATA_LONGITUDE_REF = 'LongitudeRef'; const TC_DATA_LATITUDE = 'Latitude'; const TC_DATA_LATITUDE_REF = 'LatitudeRef'; const TC_DATA_FOCALLENGTH = 'FocalLength'; const TC_DATA_CAMERAMODEL = 'CameraModel'; const TC_DATA_FLASHFIRED = 'FlashFired'; const TC_DATA_APERTURE = 'Aperture'; const TC_DATA_SHUTTERSPEED = 'ShutterSpeed'; const TC_DATA_HYPERFOCALDISTANCE = 'HyperfocalDistance'; const TC_DATA_ISO = 'ISO'; const TC_DATA_LIGHTVALUE = 'LightValue'; /** * @param Application $app * @param RecordReferenceInterface $record * @param string $name * @param bool $substitute * @param array|null $data */ public function __construct(Application $app, RecordReferenceInterface $record, $name, $substitute = false, array $data = null) { $this->app = $app; $this->name = $name; $this->record = $record instanceof record_adapter ? $record : $app->findDataboxById($record->getDataboxId())->get_record($record->getRecordId()); if (null !== $data) { $this->loadFromArray($data); } else { $this->load($substitute); } parent::__construct($this->width, $this->height, $this->generateUrl()); } /** * @param bool $substitute * @return void */ protected function load($substitute) { try { $data = $this->get_data_from_cache(); } catch (Exception $e) { $data = false; } if (is_array($data)) { $this->loadFromArray($data); return; } $data = self::getMediaSubdefRepository($this->app, $this->record->getDataboxId()) ->findOneByRecordIdAndName($this->record->getRecordId(), $this->name); if ($data) { $this->loadFromArray($data->toArray()); } elseif ($substitute === false) { throw new Exception_Media_SubdefNotFound($this->name . ' not found'); } $this->loadFromArray([]); $this->set_data_to_cache($this->toArray()); } private function loadFromArray(array $data) { if (!$data) { $data = [ 'mime' => 'unknown', 'width' => 0, 'height' => 0, 'size' => 0, 'path' => '', 'file' => '', 'physically_present' => false, 'is_substituted' => false, 'subdef_id' => null, 'updated_on' => null, 'created_on' => null, 'url' => null, ]; } $normalizer = function ($field, callable $then, callable $else = null) use ($data) { if (isset($data[$field]) || array_key_exists($field, $data)) { return $then($data[$field]); } return $else ? $else() : null; }; $this->mime = $data['mime']; $this->width = (int)$data['width']; $this->height = (int)$data['height']; $this->size = (int)$data['size']; $this->etag = $normalizer('etag', 'strval'); $this->path = p4string::addEndSlash($data['path']); $this->file = $data['file']; $this->is_physically_present = (bool)$data['physically_present']; $this->is_substituted = (bool)$data['is_substituted']; $this->subdef_id = $normalizer('subdef_id', 'intval'); $this->modification_date = $normalizer('updated_on', 'date_create'); $this->creation_date = $normalizer('created_on', 'date_create'); $this->url = $normalizer('url', [Url::class, 'factory'], [$this, 'generateUrl']); if (!$this->isStillAccessible()) { $this->markPhysicallyUnavailable(); } } private function toArray() { return [ 'record_id' => $this->get_record_id(), 'name' => $this->get_name(), 'width' => $this->width, 'size' => $this->size, 'height' => $this->height, 'mime' => $this->mime, 'file' => $this->file, 'path' => $this->path, 'physically_present' => $this->is_physically_present, 'is_substituted' => $this->is_substituted, 'subdef_id' => $this->subdef_id, 'updated_on' => NullableDateTime::format($this->modification_date), 'created_on' => NullableDateTime::format($this->creation_date), 'etag' => $this->etag, 'url' => (string)$this->url, ]; } /** * Removes the file associated to a subdef * * @return \media_subdef */ public function remove_file() { if ($this->is_physically_present() && is_writable($this->getRealPath())) { // @unlink($this->getWatermarkRealPath()); @unlink($this->getStampRealPath()); unlink($this->getRealPath()); $this->delete_data_from_cache(); $permalink = $this->get_permalink(); if ($permalink instanceof media_Permalink_Adapter) { $permalink->delete_data_from_cache(); } $this->markPhysicallyUnavailable(); } return $this; } /** * delete this subdef * * @throws \Doctrine\DBAL\DBALException */ public function delete() { $this->remove_file(); $connection = $this->getDataboxConnection(); $connection->executeUpdate( 'DELETE FROM permalinks WHERE subdef_id = :subdef_id', ['subdef_id' => $this->subdef_id] ); self::getMediaSubdefRepository($this->app, $this->record->getDataboxId())->delete($this); $this->delete_data_from_cache(); $this->record->delete_data_from_cache(record_adapter::CACHE_SUBDEFS); } private function getSubstituteFilename() { if ($this->record->isStory()) { return 'regroup_thumb.png'; } $mime = $this->record->getMimeType(); $mime = trim($mime) != '' ? str_replace('/', '_', $mime) : 'application_octet-stream'; return str_replace('+', '%20', $mime) . '.png'; } /** * Find a substitution file for a subdef * @return void */ protected function markPhysicallyUnavailable() { $this->is_physically_present = false; $this->mime = 'image/png'; $this->width = 256; $this->height = 256; $this->file = $this->getSubstituteFilename(); $this->etag = null; $this->path = $this->app['root.path'] . '/www/assets/common/images/icons/substitution/'; $this->url = Url::factory('/assets/common/images/icons/substitution/' . $this->file); if (!file_exists($this->getRealPath())) { $this->path = $this->app['root.path'] . '/www/assets/common/images/icons/'; $this->file = 'substitution.png'; $this->url = Url::factory('/assets/common/images/icons/' . $this->file); } } /** * @return bool */ public function is_physically_present() { return $this->is_physically_present; } /** * @return record_adapter */ public function get_record() { return $this->record; } /** * @return media_Permalink_Adapter */ public function get_permalink() { if (null === $this->permalink && $this->is_physically_present()) { $this->permalink = media_Permalink_Adapter::getPermalink($this->app, $this->record->getDatabox(), $this); } return $this->permalink; } /** * @return int */ public function get_record_id() { return $this->record->getRecordId(); } public function getEtag() { if ((!$this->etag && $this->is_physically_present())) { $file = new SplFileInfo($this->getRealPath()); if ($file->isFile()) { $this->generateEtag($file); } } return $this->etag; } /** * @param string|null $etag */ public function setEtag($etag) { $this->etag = $etag; return $this->save(); } /** * @param boolean $substit */ public function set_substituted($substit) { $this->is_substituted = !!$substit; return $this->save(); } /** * @return int */ public function get_sbas_id() { return $this->record->getDataboxId(); } /** * @return string */ public function get_type() { static $types = [ 'application/x-shockwave-flash' => self::TYPE_FLEXPAPER, 'application/pdf' => self::TYPE_PDF, 'audio/mp3' => self::TYPE_AUDIO_MP3, 'audio/mpeg' => self::TYPE_AUDIO_MP3, 'image/gif' => self::TYPE_IMAGE, 'image/jpeg' => self::TYPE_IMAGE, 'image/png' => self::TYPE_IMAGE, 'video/mp4' => self::TYPE_VIDEO_MP4, 'video/x-flv' => self::TYPE_VIDEO_FLV, ]; if (isset($types[$this->mime])) { return $types[$this->mime]; } return self::TYPE_NO_PLAYER; } /** * @return string */ public function get_mime() { return $this->mime; } /** * @return string */ public function get_path() { return $this->path; } /** * @return string */ public function get_file() { return $this->file; } /** * @return int */ public function get_size() { return $this->size; } /** * @return string */ public function get_name() { return $this->name; } /** * @return int */ public function get_subdef_id() { return $this->subdef_id; } /** * @return bool */ public function is_substituted() { return $this->is_substituted; } /** * @return string * @deprecated use {@link self::getRealPath} instead */ public function get_pathfile() { return $this->getRealPath(); } /** * @return DateTime */ public function get_modification_date() { return $this->modification_date; } /** * @return DateTime */ public function get_creation_date() { return $this->creation_date; } /** * @return Url */ public function renew_url() { $this->url = $this->generateUrl(); return $this->get_url(); } /** * Return the databox subdef corresponding to the subdef * * @return \databox_subdef */ public function getDataboxSubdef() { return $this->record ->getDatabox() ->get_subdef_structure() ->get_subdef($this->record->getType(), $this->get_name()); } public function getDevices() { if ($this->get_name() === 'document') { return [\databox_subdef::DEVICE_ALL]; } try { return $this->record ->getDatabox() ->get_subdef_structure() ->get_subdef($this->record->getType(), $this->get_name()) ->getDevices(); } catch (\Exception_Databox_SubdefNotFound $e) { return []; } } /** * @param int $angle * @param Alchemyst $alchemyst * @param MediaVorus $mediavorus * * @return media_subdef */ public function rotate($angle, Alchemyst $alchemyst, MediaVorus $mediavorus) { // @unlink($this->getWatermarkRealPath()); @unlink($this->getStampRealPath()); if (!$this->is_physically_present()) { throw new \Alchemy\Phrasea\Exception\RuntimeException('You can not rotate a substitution'); } $specs = new \MediaAlchemyst\Specification\Image(); $specs->setRotationAngle($angle); try { $alchemyst->turnInto($this->getRealPath(), $this->getRealPath(), $specs); } catch (\MediaAlchemyst\Exception\ExceptionInterface $e) { return $this; } $media = $mediavorus->guess($this->getRealPath()); $this->width = $media->getWidth(); $this->height = $media->getHeight(); // generate a new etag after rotation $file = new SplFileInfo($this->getRealPath()); $this->generateEtag($file); // with repository save return $this; } /** * Read the technical datas of the file. * Returns an empty array for non physical present files * * @return array An array of technical datas Key/values */ public function readTechnicalDatas(MediaVorus $mediavorus) { if (!$this->is_physically_present()) { return []; } $media = $mediavorus->guess($this->getRealPath()); $datas = []; $techDatas = self::getTechnicalFieldsList(); foreach ($techDatas as $tc_name => $techData) { if (array_key_exists('method', $techData)) { if (method_exists($media, $techData['method'])) { $result = call_user_func([$media, $techData['method']]); if (null !== $result) { $datas[$tc_name] = $result; } } } } $datas[self::TC_DATA_MIMETYPE] = $media->getFile()->getMimeType(); $datas[self::TC_DATA_FILESIZE] = $media->getFile()->getSize(); unset($media); return $datas; } public static function create(Application $app, RecordReferenceInterface $record, $name, MediaInterface $media) { $path = $media->getFile()->getPath(); $newname = $media->getFile()->getFilename(); $params = [ 'record_id' => $record->getRecordId(), 'name' => $name, 'path' => $path, 'file' => $newname, 'width' => 0, 'height' => 0, 'mime' => $media->getFile()->getMimeType(), 'size' => $media->getFile()->getSize(), 'physically_present' => true, 'is_substituted' => false, ]; if (method_exists($media, 'getWidth') && null !== $media->getWidth()) { $params['width'] = $media->getWidth(); } if (method_exists($media, 'getHeight') && null !== $media->getHeight()) { $params['height'] = $media->getHeight(); } /** @var callable $factoryProvider */ $factoryProvider = $app['provider.factory.media_subdef']; $factory = $factoryProvider($record->getDataboxId()); $subdef = $factory($params); Assertion::isInstanceOf($subdef, \media_subdef::class); $repository = self::getMediaSubdefRepository($app, $record->getDataboxId()); $repository->save($subdef); // Refresh from Database. $subdef = $repository->findOneByRecordIdAndName($record->getRecordId(), $name); $permalink = $subdef->get_permalink(); if ($permalink instanceof media_Permalink_Adapter) { $permalink->delete_data_from_cache(); } if ($name === 'thumbnail') { /** @var SymLinker $symlinker */ $symlinker = $app['phraseanet.thumb-symlinker']; $symlinker->symlink($subdef->getRealPath()); } // delete from cache the list of available subdef for the record to taken account the new subdef $subdef->get_record()->delete_data_from_cache(record_adapter::CACHE_SUBDEFS); return $subdef; } /** * @return Url */ protected function generateUrl() { if (!$this->is_physically_present()) { $this->markPhysicallyUnavailable(); return $this->url; } $generators = [ [$this, 'tryGetThumbnailUrl'], [$this, 'tryGetVideoUrl'], ]; foreach ($generators as $generator) { $url = $generator(); if ($url instanceof Url) { return $url; } } return Url::factory($this->app->path('datafile', [ 'sbas_id' => $this->record->getDataboxId(), 'record_id' => $this->record->getRecordId(), 'subdef' => $this->get_name(), 'etag' => $this->getEtag(), ])); } public function get_cache_key($option = null) { return 'subdef_' . $this->get_record()->getId() . '_' . $this->name . ($option ? '_' . $option : ''); } public function get_data_from_cache($option = null) { $databox = $this->get_record()->getDatabox(); return $databox->get_data_from_cache($this->get_cache_key($option)); } public function set_data_to_cache($value, $option = null, $duration = 0) { $databox = $this->get_record()->getDatabox(); return $databox->set_data_to_cache($value, $this->get_cache_key($option), $duration); } public function delete_data_from_cache($option = null) { $databox = $this->get_record()->getDatabox(); $databox->delete_data_from_cache($this->get_cache_key($option)); } /** * @return string */ public function getRealPath() { return $this->path . $this->file; } /** * @return string */ public function getWatermarkRealPath() { return $this->path . 'watermark_' . $this->file; } /** * @return string */ public function getStampRealPath() { return $this->path . 'stamp_' . $this->file; } /** * @return \Doctrine\DBAL\Connection */ private function getDataboxConnection() { return $this->record->getDatabox()->get_connection(); } /** * @return bool */ private function isStillAccessible() { // return $this->is_physically_present && $this->getFilesystem()->exists($this->getRealPath(), 10); // allow 10 secs for the file to be visible on shared fs return $this->is_physically_present && $this->getFilesystem()->exists($this->getRealPath()); // NO delay allowed, this slows down phr if any subdef is missing } /** * @return Url|null */ protected function tryGetThumbnailUrl() { if ('thumbnail' !== $this->get_name()) { return null; } $url = $this->app['phraseanet.static-file']->getUrl($this->getRealPath()); if (null === $url) { return null; } $url->getQuery()->offsetSet('etag', $this->getEtag()); return $url; } /** * @return Url|null */ protected function tryGetVideoUrl() { if ($this->mime !== 'video/mp4' || !$this->app['phraseanet.h264-factory']->isH264Enabled()) { return null; } return $this->app['phraseanet.h264']->getUrl($this->getRealPath()); } /** * @param SplFileInfo $file */ private function generateEtag(SplFileInfo $file) { $this->setEtag(md5($file->getRealPath() . $file->getMTime())); } /** * @return $this */ private function save() { self::getMediaSubdefRepository($this->app, $this->record->getDataboxId())->save($this); return $this; } /** * Return list of technical data and their attributes * * @return array */ public static function getTechnicalFieldsList() { if (empty(self::$technicalFieldsList)) { self::$technicalFieldsList = [ self::TC_DATA_WIDTH => ['method' => 'getWidth', 'type' => 'integer', 'analyzable' => false], self::TC_DATA_HEIGHT => ['method' => 'getHeight', 'type' => 'integer', 'analyzable' => false], self::TC_DATA_FOCALLENGTH => ['method' => 'getFocalLength', 'type' => 'float', 'analyzable' => false], self::TC_DATA_CHANNELS => ['method' => 'getChannels', 'type' => 'integer', 'analyzable' => false], self::TC_DATA_COLORDEPTH => ['method' => 'getColorDepth', 'type' => 'integer', 'analyzable' => false], self::TC_DATA_CAMERAMODEL => ['method' => 'getCameraModel', 'type' => 'string', 'analyzable' => false], self::TC_DATA_FLASHFIRED => ['method' => 'getFlashFired', 'type' => 'boolean', 'analyzable' => false], self::TC_DATA_APERTURE => ['method' => 'getAperture', 'type' => 'float', 'analyzable' => false], self::TC_DATA_SHUTTERSPEED => ['method' => 'getShutterSpeed', 'type' => 'float', 'analyzable' => false], self::TC_DATA_HYPERFOCALDISTANCE => ['method' => 'getHyperfocalDistance', 'type' => 'float', 'analyzable' => false], self::TC_DATA_ISO => ['method' => 'getISO', 'type' => 'integer', 'analyzable' => false], self::TC_DATA_LIGHTVALUE => ['method' => 'getLightValue', 'type' => 'float', 'analyzable' => false], self::TC_DATA_COLORSPACE => ['method' => 'getColorSpace', 'type' => 'integer', 'analyzable' => false], self::TC_DATA_DURATION => ['method' => 'getDuration', 'type' => 'float', 'analyzable' => false], self::TC_DATA_FRAMERATE => ['method' => 'getFrameRate', 'type' => 'float', 'analyzable' => false], self::TC_DATA_AUDIOSAMPLERATE => ['method' => 'getAudioSampleRate', 'type' => 'float', 'analyzable' => false], self::TC_DATA_VIDEOCODEC => ['method' => 'getVideoCodec', 'type' => 'string', 'analyzable' => false], self::TC_DATA_AUDIOCODEC => ['method' => 'getAudioCodec', 'type' => 'string', 'analyzable' => false], self::TC_DATA_ORIENTATION => ['method' => 'getOrientation', 'type' => 'integer', 'analyzable' => false], self::TC_DATA_THUMBNAILORIENTATION => ['type' => 'string', 'analyzable' => false], self::TC_DATA_LONGITUDE => ['method' => 'getLongitude', 'type' => 'float', 'analyzable' => false], self::TC_DATA_LONGITUDE_REF => ['method' => 'getLongitudeRef'], self::TC_DATA_LATITUDE => ['method' => 'getLatitude', 'type' => 'float', 'analyzable' => false], self::TC_DATA_LATITUDE_REF => ['method' => 'getLatitudeRef'], self::TC_DATA_MIMETYPE => ['type' => 'string', 'analyzable' => false], self::TC_DATA_FILESIZE => ['type' => 'long', 'analyzable' => false], ]; } return self::$technicalFieldsList; } }