499 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
			
		
		
	
	
			499 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
| <?php
 | |
| 
 | |
| declare(strict_types=1);
 | |
| 
 | |
| namespace Grav\Plugin\FlexObjects\Controllers;
 | |
| 
 | |
| use Exception;
 | |
| use Grav\Common\Page\Interfaces\PageInterface;
 | |
| use Grav\Common\Page\Medium\Medium;
 | |
| use Grav\Common\Page\Medium\MediumFactory;
 | |
| use Grav\Common\Utils;
 | |
| use Grav\Framework\Flex\FlexObject;
 | |
| use Grav\Framework\Flex\Interfaces\FlexAuthorizeInterface;
 | |
| use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
 | |
| use Grav\Framework\Media\Interfaces\MediaInterface;
 | |
| use LogicException;
 | |
| use Psr\Http\Message\ResponseInterface;
 | |
| use Psr\Http\Message\UploadedFileInterface;
 | |
| use RocketTheme\Toolbox\Event\Event;
 | |
| use RuntimeException;
 | |
| use function is_array;
 | |
| use function is_string;
 | |
| 
 | |
| /**
 | |
|  * Class MediaController
 | |
|  * @package Grav\Plugin\FlexObjects\Controllers
 | |
|  */
 | |
| class MediaController extends AbstractController
 | |
| {
 | |
|     /**
 | |
|      * @return ResponseInterface
 | |
|      */
 | |
|     public function taskMediaUpload(): ResponseInterface
 | |
|     {
 | |
|         $this->checkAuthorization('media.create');
 | |
| 
 | |
|         $object = $this->getObject();
 | |
|         if (null === $object) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         if (!method_exists($object, 'checkUploadedMediaFile')) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         // Get updated object from Form Flash.
 | |
|         $flash = $this->getFormFlash($object);
 | |
|         if ($flash->exists()) {
 | |
|             $object = $flash->getObject() ?? $object;
 | |
|             $object->update([], $flash->getFilesByFields());
 | |
|         }
 | |
| 
 | |
|         // Get field for the uploaded media.
 | |
|         $field = $this->getPost('name', 'undefined');
 | |
|         if ($field === 'undefined') {
 | |
|             $field = null;
 | |
|         }
 | |
| 
 | |
|         $files = $this->getRequest()->getUploadedFiles();
 | |
|         if ($field && isset($files['data'])) {
 | |
|             $files = $files['data'];
 | |
|             $parts = explode('.', $field);
 | |
|             $last = array_pop($parts);
 | |
|             foreach ($parts as $name) {
 | |
|                 if (!is_array($files[$name])) {
 | |
|                     throw new RuntimeException($this->translate('PLUGIN_ADMIN.INVALID_PARAMETERS'), 400);
 | |
|                 }
 | |
|                 $files = $files[$name];
 | |
|             }
 | |
|             $file = $files[$last] ?? null;
 | |
| 
 | |
|         } else {
 | |
|             // Legacy call with name being the filename instead of field name.
 | |
|             $file = $files['file'] ?? null;
 | |
|             $field = null;
 | |
|         }
 | |
| 
 | |
|         /** @var UploadedFileInterface $file */
 | |
|         if (is_array($file)) {
 | |
|             $file = reset($file);
 | |
|         }
 | |
| 
 | |
|         if (!$file instanceof UploadedFileInterface) {
 | |
|             throw new RuntimeException($this->translate('PLUGIN_ADMIN.INVALID_PARAMETERS'), 400);
 | |
|         }
 | |
| 
 | |
|         $filename = $file->getClientFilename();
 | |
| 
 | |
|         $object->checkUploadedMediaFile($file, $filename, $field);
 | |
| 
 | |
|         try {
 | |
|             $crop = $this->getPost('crop');
 | |
|             if (is_string($crop)) {
 | |
|                 $crop = json_decode($crop, true, 512, JSON_THROW_ON_ERROR);
 | |
|             }
 | |
| 
 | |
|             $flash->addUploadedFile($file, $field, $crop);
 | |
|             $flash->save();
 | |
|         } catch (Exception $e) {
 | |
|             throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
 | |
|         }
 | |
| 
 | |
|         // Include exif metadata into the response if configured to do so
 | |
|         $metadata = [];
 | |
|         $include_metadata = $this->grav['config']->get('system.media.auto_metadata_exif', false);
 | |
|         if ($include_metadata) {
 | |
|             $medium = MediumFactory::fromUploadedFile($file);
 | |
| 
 | |
|             $media = $object->getMedia();
 | |
|             $media->add($filename, $medium);
 | |
| 
 | |
|             $basename = str_replace(['@3x', '@2x'], '', pathinfo($filename, PATHINFO_BASENAME));
 | |
|             if (isset($media[$basename])) {
 | |
|                 $metadata = $media[$basename]->metadata() ?: [];
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $response = [
 | |
|             'code'    => 200,
 | |
|             'status'  => 'success',
 | |
|             'message' => $this->translate('PLUGIN_ADMIN.FILE_UPLOADED_SUCCESSFULLY'),
 | |
|             'filename' => $filename,
 | |
|             'metadata' => $metadata
 | |
|         ];
 | |
| 
 | |
|         return $this->createJsonResponse($response);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return ResponseInterface
 | |
|      */
 | |
|     public function taskMediaDelete(): ResponseInterface
 | |
|     {
 | |
|         $this->checkAuthorization('media.delete');
 | |
| 
 | |
|         /** @var FlexObjectInterface|null $object */
 | |
|         $object = $this->getObject();
 | |
|         if (!$object) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         $filename = $this->getPost('filename');
 | |
| 
 | |
|         // Handle bad filenames.
 | |
|         if (!Utils::checkFilename($filename)) {
 | |
|             throw new RuntimeException($this->translate('PLUGIN_ADMIN.NO_FILE_FOUND'), 400);
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             $field = $this->getPost('name');
 | |
|             $flash = $this->getFormFlash($object);
 | |
|             $flash->removeFile($filename, $field);
 | |
|             $flash->save();
 | |
|         } catch (Exception $e) {
 | |
|             throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
 | |
|         }
 | |
| 
 | |
|         $response = [
 | |
|             'code'    => 200,
 | |
|             'status'  => 'success',
 | |
|             'message' => $this->translate('PLUGIN_ADMIN.FILE_DELETED') . ': ' . $filename
 | |
|         ];
 | |
| 
 | |
|         return $this->createJsonResponse($response);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Used in pagemedia field.
 | |
|      *
 | |
|      * @return ResponseInterface
 | |
|      */
 | |
|     public function taskMediaCopy(): ResponseInterface
 | |
|     {
 | |
|         $this->checkAuthorization('media.create');
 | |
| 
 | |
|         /** @var FlexObjectInterface|null $object */
 | |
|         $object = $this->getObject();
 | |
|         if (!$object) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         if (!method_exists($object, 'uploadMediaFile')) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         $request = $this->getRequest();
 | |
|         $files = $request->getUploadedFiles();
 | |
| 
 | |
|         $file = $files['file'] ?? null;
 | |
|         if (!$file instanceof UploadedFileInterface) {
 | |
|             throw new RuntimeException($this->translate('PLUGIN_ADMIN.INVALID_PARAMETERS'), 400);
 | |
|         }
 | |
| 
 | |
|         $post = $request->getParsedBody();
 | |
|         $filename = $post['name'] ?? $file->getClientFilename();
 | |
| 
 | |
|         // Upload media right away.
 | |
|         $object->uploadMediaFile($file, $filename);
 | |
| 
 | |
|         // Include exif metadata into the response if configured to do so
 | |
|         $metadata = [];
 | |
|         $include_metadata = $this->grav['config']->get('system.media.auto_metadata_exif', false);
 | |
|         if ($include_metadata) {
 | |
|             $basename = str_replace(['@3x', '@2x'], '', pathinfo($filename, PATHINFO_BASENAME));
 | |
|             $media = $object->getMedia();
 | |
|             if (isset($media[$basename])) {
 | |
|                 $metadata = $media[$basename]->metadata() ?: [];
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if ($object instanceof PageInterface) {
 | |
|             // Backwards compatibility to existing plugins.
 | |
|             // DEPRECATED: page
 | |
|             $this->grav->fireEvent('onAdminAfterAddMedia', new Event(['object' => $object, 'page' => $object]));
 | |
|         }
 | |
| 
 | |
|         $response = [
 | |
|             'code'    => 200,
 | |
|             'status'  => 'success',
 | |
|             'message' => $this->translate('PLUGIN_ADMIN.FILE_UPLOADED_SUCCESSFULLY'),
 | |
|             'filename' => $filename,
 | |
|             'metadata' => $metadata
 | |
|         ];
 | |
| 
 | |
|         return $this->createJsonResponse($response);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Used in pagemedia field.
 | |
|      *
 | |
|      * @return ResponseInterface
 | |
|      */
 | |
|     public function taskMediaRemove(): ResponseInterface
 | |
|     {
 | |
|         $this->checkAuthorization('media.delete');
 | |
| 
 | |
|         /** @var FlexObjectInterface|null $object */
 | |
|         $object = $this->getObject();
 | |
|         if (!$object) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         if (!method_exists($object, 'deleteMediaFile')) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         $filename = $this->getPost('filename');
 | |
| 
 | |
|         // Handle bad filenames.
 | |
|         if (!Utils::checkFilename($filename)) {
 | |
|             throw new RuntimeException($this->translate('PLUGIN_ADMIN.NO_FILE_FOUND'), 400);
 | |
|         }
 | |
| 
 | |
|         $object->deleteMediaFile($filename);
 | |
| 
 | |
|         if ($object instanceof PageInterface) {
 | |
|             // Backwards compatibility to existing plugins.
 | |
|             // DEPRECATED: page
 | |
|             $this->grav->fireEvent('onAdminAfterDelMedia', new Event(['object' => $object, 'page' => $object, 'media' => $object->getMedia(), 'filename' => $filename]));
 | |
|         }
 | |
| 
 | |
|         $response = [
 | |
|             'code'    => 200,
 | |
|             'status'  => 'success',
 | |
|             'message' => $this->translate('PLUGIN_ADMIN.FILE_DELETED') . ': ' . $filename
 | |
|         ];
 | |
| 
 | |
|         return $this->createJsonResponse($response);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return ResponseInterface
 | |
|      */
 | |
|     public function actionMediaList(): ResponseInterface
 | |
|     {
 | |
|         $this->checkAuthorization('media.list');
 | |
| 
 | |
|         /** @var MediaInterface|FlexObjectInterface $object */
 | |
|         $object = $this->getObject();
 | |
|         if (!$object) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         // Get updated object from Form Flash.
 | |
|         $flash = $this->getFormFlash($object);
 | |
|         if ($flash->exists()) {
 | |
|             $object = $flash->getObject() ?? $object;
 | |
|             $object->update([], $flash->getFilesByFields());
 | |
|         }
 | |
| 
 | |
|         $media = $object->getMedia();
 | |
|         $media_list = [];
 | |
| 
 | |
|         /**
 | |
|          * @var string $name
 | |
|          * @var Medium $medium
 | |
|          */
 | |
|         foreach ($media->all() as $name => $medium) {
 | |
|             $media_list[$name] = [
 | |
|                 'url' => $medium->display($medium->get('extension') === 'svg' ? 'source' : 'thumbnail')->cropZoom(400, 300)->url(),
 | |
|                 'size' => $medium->get('size'),
 | |
|                 'metadata' => $medium->metadata() ?: [],
 | |
|                 'original' => $medium->higherQualityAlternative()->get('filename')
 | |
|             ];
 | |
|         }
 | |
| 
 | |
|         $response = [
 | |
|             'code' => 200,
 | |
|             'status' => 'success',
 | |
|             'results' => $media_list
 | |
|         ];
 | |
| 
 | |
|         return $this->createJsonResponse($response);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Used by the filepicker field to get a list of files in a folder.
 | |
|      *
 | |
|      * @return ResponseInterface
 | |
|      */
 | |
|     protected function actionMediaPicker(): ResponseInterface
 | |
|     {
 | |
|         $this->checkAuthorization('media.list');
 | |
| 
 | |
|         /** @var FlexObject $object */
 | |
|         $object = $this->getObject();
 | |
|         if (!$object || !\is_callable([$object, 'getFieldSettings'])) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         // Get updated object from Form Flash.
 | |
|         $flash = $this->getFormFlash($object);
 | |
|         if ($flash->exists()) {
 | |
|             $object = $flash->getObject() ?? $object;
 | |
|             $object->update([], $flash->getFilesByFields());
 | |
|         }
 | |
| 
 | |
|         $name = $this->getPost('name');
 | |
|         $settings = $name ? $object->getFieldSettings($name) : null;
 | |
|         if (empty($settings['media_picker_field'])) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         $media = $object->getMediaField($name);
 | |
| 
 | |
|         $available_files = [];
 | |
|         $metadata = [];
 | |
|         $thumbs = [];
 | |
| 
 | |
|         /**
 | |
|          * @var string $name
 | |
|          * @var Medium $medium
 | |
|          */
 | |
|         foreach ($media->all() as $name => $medium) {
 | |
|             $available_files[] = $name;
 | |
| 
 | |
|             if (isset($settings['include_metadata'])) {
 | |
|                 $img_metadata = $medium->metadata();
 | |
|                 if ($img_metadata) {
 | |
|                     $metadata[$name] = $img_metadata;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|         }
 | |
| 
 | |
|         // Peak in the flashObject for optimistic filepicker updates
 | |
|         $pending_files = [];
 | |
|         $sessionField = base64_encode($this->grav['uri']->url());
 | |
|         $flash = $this->getSession()->getFlashObject('files-upload');
 | |
|         $folder = $media->getPath() ?: null;
 | |
| 
 | |
|         if ($flash && isset($flash[$sessionField])) {
 | |
|             foreach ($flash[$sessionField] as $field => $data) {
 | |
|                 foreach ($data as $file) {
 | |
|                     $test = \dirname($file['path']);
 | |
|                     if ($test === $folder) {
 | |
|                         $pending_files[] = $file['name'];
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $this->getSession()->setFlashObject('files-upload', $flash);
 | |
| 
 | |
|         // Handle Accepted file types
 | |
|         // Accept can only be file extensions (.pdf|.jpg)
 | |
|         if (isset($settings['accept'])) {
 | |
|             $available_files = array_filter($available_files, function ($file) use ($settings) {
 | |
|                 return $this->filterAcceptedFiles($file, $settings);
 | |
|             });
 | |
| 
 | |
|             $pending_files = array_filter($pending_files, function ($file) use ($settings) {
 | |
|                 return $this->filterAcceptedFiles($file, $settings);
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         if (isset($settings['deny'])) {
 | |
|             $available_files = array_filter($available_files, function ($file) use ($settings) {
 | |
|                 return $this->filterDeniedFiles($file, $settings);
 | |
|             });
 | |
| 
 | |
|             $pending_files = array_filter($pending_files, function ($file) use ($settings) {
 | |
|                 return $this->filterDeniedFiles($file, $settings);
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         // Generate thumbs if needed
 | |
|         if (isset($settings['preview_images']) && $settings['preview_images'] === true) {
 | |
|             foreach ($available_files as $filename) {
 | |
|                 $thumbs[$filename] = $media[$filename]->zoomCrop(100,100)->url();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $response = [
 | |
|             'code' => 200,
 | |
|             'status' => 'success',
 | |
|             'files' => array_values($available_files),
 | |
|             'pending' => array_values($pending_files),
 | |
|             'folder' => $folder,
 | |
|             'metadata' => $metadata,
 | |
|             'thumbs' => $thumbs
 | |
|         ];
 | |
| 
 | |
|         return $this->createJsonResponse($response);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string $file
 | |
|      * @param array $settings
 | |
|      * @return false|int
 | |
|      */
 | |
|     protected function filterAcceptedFiles(string $file, array $settings)
 | |
|     {
 | |
|         $valid = false;
 | |
| 
 | |
|         foreach ((array)$settings['accept'] as $type) {
 | |
|             $find = str_replace('*', '.*', $type);
 | |
|             $valid |= preg_match('#' . $find . '$#i', $file);
 | |
|         }
 | |
| 
 | |
|         return $valid;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string $file
 | |
|      * @param array $settings
 | |
|      * @return false|int
 | |
|      */
 | |
|     protected function filterDeniedFiles(string $file, array $settings)
 | |
|     {
 | |
|         $valid = true;
 | |
| 
 | |
|         foreach ((array)$settings['deny'] as $type) {
 | |
|             $find = str_replace('*', '.*', $type);
 | |
|             $valid = !preg_match('#' . $find . '$#i', $file);
 | |
|         }
 | |
| 
 | |
|         return $valid;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string $action
 | |
|      * @return void
 | |
|      * @throws LogicException
 | |
|      * @throws RuntimeException
 | |
|      */
 | |
|     protected function checkAuthorization(string $action): void
 | |
|     {
 | |
|         $object = $this->getObject();
 | |
|         if (!$object) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         // If object does not have ACL support ignore ACL checks.
 | |
|         if (!$object instanceof FlexAuthorizeInterface) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         switch ($action) {
 | |
|             case 'media.list':
 | |
|                 $action = 'read';
 | |
|                 break;
 | |
| 
 | |
|             case 'media.create':
 | |
|             case 'media.delete':
 | |
|                 $action = $object->exists() ? 'update' : 'create';
 | |
|                 break;
 | |
| 
 | |
|             default:
 | |
|                 throw new LogicException(sprintf('Unsupported authorize action %s', $action), 500);
 | |
|         }
 | |
| 
 | |
|         if (!$object->isAuthorized($action, null, $this->user)) {
 | |
|             throw new RuntimeException('Forbidden', 403);
 | |
|         }
 | |
|     }
 | |
| }
 |