1 <?php
  2 
  3 /**
  4  * @package redaxo\media-manager
  5  */
  6 class rex_media_manager
  7 {
  8     private $media;
  9     private $originalFilename;
 10     private $cache_path;
 11     private $type;
 12     private $use_cache;
 13     private $cache;
 14 
 15     private static $effects = [];
 16 
 17     public function __construct(rex_managed_media $media)
 18     {
 19         $this->media = $media;
 20         $this->originalFilename = $media->getMediaFilename();
 21         $this->useCache(true);
 22     }
 23 
 24     /**
 25      * Creates a rex_managed_media object for the given file and mediatype.
 26      * This object might be used to determine the dimension of a image or similar.
 27      *
 28      * @param string $type Media type
 29      * @param string $file Media file
 30      *
 31      * @return self
 32      */
 33     public static function create($type, $file)
 34     {
 35         $mediaPath = rex_path::media($file);
 36         $cachePath = rex_path::addonCache('media_manager');
 37 
 38         $media = new rex_managed_media($mediaPath);
 39         $manager = new self($media);
 40         $manager->setCachePath($cachePath);
 41         $manager->applyEffects($type);
 42 
 43         if ($manager->use_cache && $manager->isCached()) {
 44             $media->setSourcePath($manager->getCacheFilename());
 45 
 46             $cache = $manager->getHeaderCache();
 47 
 48             $media->setFormat($cache['format']);
 49 
 50             foreach ($cache['headers'] as $key => $value) {
 51                 $media->setHeader($key, $value);
 52             }
 53         } elseif ($manager->use_cache) {
 54             $media->save($manager->getCacheFilename(), $manager->getHeaderCacheFilename());
 55         }
 56 
 57         $media->refreshImageDimensions();
 58 
 59         return $manager;
 60     }
 61 
 62     /**
 63      * @return rex_managed_media
 64      */
 65     public function getMedia()
 66     {
 67         return $this->media;
 68     }
 69 
 70     protected function applyEffects($type)
 71     {
 72         $this->type = $type;
 73 
 74         if (!$this->isCached()) {
 75             $set = $this->effectsFromType($type);
 76             $set = rex_extension::registerPoint(new rex_extension_point('MEDIA_MANAGER_FILTERSET', $set, ['rex_media_type' => $type]));
 77 
 78             if (count($set) == 0) {
 79                 $this->use_cache = false;
 80                 return $this->media;
 81             }
 82 
 83             // execute effects on image
 84             foreach ($set as $effect_params) {
 85                 $effect_class = 'rex_effect_' . $effect_params['effect'];
 86                 /** @var rex_effect_abstract $effect */
 87                 $effect = new $effect_class();
 88                 $effect->setMedia($this->media);
 89                 $effect->setParams($effect_params['params']);
 90                 $effect->execute();
 91             }
 92         }
 93     }
 94 
 95     public function effectsFromType($type)
 96     {
 97         $qry = '
 98             SELECT e.*
 99             FROM ' . rex::getTablePrefix() . 'media_manager_type t, ' . rex::getTablePrefix() . 'media_manager_type_effect e
100             WHERE e.type_id = t.id AND t.name=? order by e.priority';
101 
102         $sql = rex_sql::factory();
103         // $sql->setDebug();
104         $sql->setQuery($qry, [$type]);
105 
106         $effects = [];
107         foreach ($sql as $row) {
108             $effname = $row->getValue('effect');
109             $params = json_decode($row->getValue('parameters'), true);
110             $effparams = [];
111 
112             // extract parameter out of array
113             if (isset($params['rex_effect_' . $effname])) {
114                 foreach ($params['rex_effect_' . $effname] as $name => $value) {
115                     $effparams[str_replace('rex_effect_' . $effname . '_', '', $name)] = $value;
116                     unset($effparams[$name]);
117                 }
118             }
119 
120             $effect = [
121                 'effect' => $effname,
122                 'params' => $effparams,
123             ];
124 
125             $effects[] = $effect;
126         }
127 
128         return $effects;
129     }
130 
131     public function setCachePath($cache_path = '')
132     {
133         $this->cache_path = $cache_path;
134     }
135 
136     public function getCachePath()
137     {
138         return $this->cache_path;
139     }
140 
141     protected function useCache($t = true)
142     {
143         $this->use_cache = $t;
144     }
145 
146     public function isCached()
147     {
148         $cache_file = $this->getCacheFilename();
149 
150         if (!file_exists($cache_file)) {
151             return false;
152         }
153 
154         $cache = $this->getHeaderCache();
155 
156         if (!$cache) {
157             return false;
158         }
159 
160         $mediapath = $cache['media_path'];
161 
162         if (null === $mediapath) {
163             return true;
164         }
165 
166         if (!file_exists($mediapath)) {
167             return false;
168         }
169 
170         $cachetime = filemtime($cache_file);
171         $filetime = filemtime($mediapath);
172 
173         // cache is newer?
174         return $cachetime > $filetime;
175     }
176 
177     public function getCacheFilename()
178     {
179         return $this->cache_path.$this->type.'/'.$this->originalFilename;
180     }
181 
182     public function getHeaderCacheFilename()
183     {
184         return $this->getCacheFilename() . '.header';
185     }
186 
187     private function getHeaderCache()
188     {
189         if ($this->cache) {
190             return $this->cache;
191         }
192 
193         return $this->cache = rex_file::getCache($this->getHeaderCacheFilename(), null);
194     }
195 
196     public static function deleteCacheByType($type_id)
197     {
198         $qry = 'SELECT * FROM ' . rex::getTablePrefix() . 'media_manager_type' . ' WHERE id=?';
199         $sql = rex_sql::factory();
200         //  $sql->setDebug();
201         $sql->setQuery($qry, [$type_id]);
202         $counter = 0;
203         foreach ($sql as $row) {
204             $counter += self::deleteCache(null, $row->getValue('name'));
205         }
206 
207         rex_file::delete(rex_path::addonCache('media_manager', 'types.cache'));
208 
209         return $counter;
210     }
211 
212     public static function deleteCache($filename = null, $type = null)
213     {
214         $filename = ($filename ?: '').'*';
215 
216         if (!$type) {
217             $type = '*';
218         }
219 
220         $counter = 0;
221         $folder = rex_path::addonCache('media_manager');
222 
223         $glob = glob($folder.$type.'/'.$filename, GLOB_NOSORT);
224         if ($glob) {
225             foreach ($glob as $file) {
226                 if (rex_file::delete($file)) {
227                     ++$counter;
228                 }
229             }
230         }
231 
232         return $counter;
233     }
234 
235     public function sendMedia()
236     {
237         rex_extension::registerPoint(new rex_extension_point('MEDIA_MANAGER_BEFORE_SEND', $this, []));
238 
239         $headerCacheFilename = $this->getHeaderCacheFilename();
240         $CacheFilename = $this->getCacheFilename();
241 
242         rex_response::cleanOutputBuffers();
243 
244         // check for a cache-buster. this needs to be done, before the session gets closed/aborted.
245         // the header is sent directly, to make sure it gets not cached with the other media related headers.
246         if (rex_get('buster')) {
247             if (PHP_SESSION_ACTIVE == session_status()) {
248                 // short lived cache, for resources which might be affected by e.g. permissions
249                 rex_response::sendCacheControl('private, max-age=7200');
250             } else {
251                 rex_response::sendCacheControl('public, max-age=31536000, immutable');
252             }
253         }
254 
255         // prevent session locking trough other addons
256         if (function_exists('session_abort')) {
257             session_abort();
258         } else {
259             session_write_close();
260         }
261 
262         if ($this->use_cache && $this->isCached()) {
263             $header = $this->getHeaderCache()['headers'];
264             if (isset($header['Last-Modified'])) {
265                 rex_response::sendLastModified(strtotime($header['Last-Modified']));
266                 unset($header['Last-Modified']);
267             }
268             foreach ($header as $t => $c) {
269                 rex_response::setHeader($t, $c);
270             }
271             rex_response::sendFile($CacheFilename, $header['Content-Type']);
272         } else {
273             $this->media->sendMedia($CacheFilename, $headerCacheFilename, $this->use_cache);
274         }
275 
276         rex_extension::registerPoint(new rex_extension_point('MEDIA_MANAGER_AFTER_SEND', $this, []));
277 
278         exit;
279     }
280 
281     public static function getSupportedEffects()
282     {
283         $dirs = [
284             __DIR__ . '/effects/',
285         ];
286 
287         $effects = [];
288         foreach ($dirs as $dir) {
289             $files = glob($dir . 'effect_*.php');
290             if ($files) {
291                 foreach ($files as $file) {
292                     $effects[self::getEffectClass($file)] = self::getEffectName($file);
293                 }
294             }
295         }
296 
297         foreach (self::$effects as $class) {
298             $effects[$class] = str_replace(['rex_', 'effect_'], '', $class);
299         }
300 
301         return $effects;
302     }
303 
304     public static function addEffect($class)
305     {
306         self::$effects[] = $class;
307     }
308 
309     private static function getEffectName($effectFile)
310     {
311         return str_replace(
312             ['effect_', '.php'],
313             '',
314             basename($effectFile)
315         );
316     }
317 
318     private static function getEffectClass($effectFile)
319     {
320         return 'rex_' . str_replace(
321             '.php',
322             '',
323             basename($effectFile)
324         );
325     }
326 
327     /*
328      * For ExtensionPoints.
329      */
330     public static function mediaUpdated(rex_extension_point $ep)
331     {
332         self::deleteCache($ep->getParam('filename'));
333     }
334 
335     public static function init()
336     {
337         //--- handle image request
338         $rex_media_manager_file = self::getMediaFile();
339         $rex_media_manager_type = self::getMediaType();
340 
341         if ($rex_media_manager_file != '' && $rex_media_manager_type != '') {
342             $media_path = rex_path::media($rex_media_manager_file);
343             $cache_path = rex_path::addonCache('media_manager');
344 
345             $media = new rex_managed_media($media_path);
346             $media_manager = new self($media);
347             $media_manager->setCachePath($cache_path);
348             $media_manager->applyEffects($rex_media_manager_type);
349             $media_manager->sendMedia();
350 
351             exit();
352         }
353     }
354 
355     public static function getMediaFile()
356     {
357         $rex_media_file = rex_get('rex_media_file', 'string');
358 
359         // can be used with REDAXO >= 5.5.1
360         // $rex_media_file = rex_path::basename($rex_media_file);
361         $rex_media_file = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $rex_media_file);
362         $rex_media_file = basename($rex_media_file);
363 
364         return $rex_media_file;
365     }
366 
367     public static function getMediaType()
368     {
369         $type = rex_get('rex_media_type', 'string');
370 
371         // can be used with REDAXO >= 5.5.1
372         // $type = rex_path::basename($type);
373         $type = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $type);
374         $type = basename($type);
375 
376         return $type;
377     }
378 
379     /**
380      * @param string           $type      Media type
381      * @param string|rex_media $file      Media file
382      * @param null|int         $timestamp Last change timestamp of given file, for cache buster parameter
383      *                                    (not nessary when the file is given by a `rex_media` object)
384      * @param bool             $escape
385      *
386      * @return string
387      */
388     public static function getUrl($type, $file, $timestamp = null, $escape = true)
389     {
390         if ($file instanceof rex_media) {
391             if (null === $timestamp) {
392                 $timestamp = $file->getUpdateDate();
393             }
394 
395             $file = $file->getFileName();
396         }
397 
398         $params = [
399             'rex_media_type' => $type,
400             'rex_media_file' => $file,
401         ];
402 
403         if (null !== $timestamp) {
404             $cache = self::getTypeCache();
405 
406             if (isset($cache[$type])) {
407                 $params['buster'] = max($timestamp, $cache[$type]);
408             }
409         }
410 
411         if (rex::isBackend()) {
412             $url = rex_url::backendController($params, $escape);
413         } else {
414             $url = rex_url::frontendController($params, $escape);
415         }
416 
417         return rex_extension::registerPoint(new rex_extension_point('MEDIA_MANAGER_URL', $url, [
418             'type' => $type,
419             'file' => $file,
420             'buster' => isset($params['buster']) ? $params['buster'] : null,
421             'escape' => $escape,
422         ]));
423     }
424 
425     private static function getTypeCache()
426     {
427         $file = rex_path::addonCache('media_manager', 'types.cache');
428 
429         if (null !== $cache = rex_file::getCache($file, null)) {
430             return $cache;
431         }
432 
433         $cache = [];
434 
435         $sql = rex_sql::factory();
436         $sql->setQuery('SELECT name, updatedate FROM '.rex::getTable('media_manager_type'));
437 
438         /** @var rex_sql $row */
439         foreach ($sql as $row) {
440             $cache[$row->getValue('name')] = $row->getDateTimeValue('updatedate');
441         }
442 
443         rex_file::putCache($file, $cache);
444 
445         return $cache;
446     }
447 }
448