diff --git a/vanilla/applications/dashboard/controllers/api/MediaApiController.php b/vanilla/applications/dashboard/controllers/api/MediaApiController.php index 3821257..c103a9b 100644 --- a/vanilla/applications/dashboard/controllers/api/MediaApiController.php +++ b/vanilla/applications/dashboard/controllers/api/MediaApiController.php @@ -116,7 +116,7 @@ private function doUpload(UploadedFile $upload, $type) { $ext = pathinfo(strtolower($upload->getClientFilename()), PATHINFO_EXTENSION); $destination = $this->generateUploadPath($ext, true); - $uploadResult = $this->saveUpload($upload, $destination); + $uploadResult = $this->saveUpload($upload, $destination, '%s', false , [ 'ClientFileName' => $upload->getClientFilename()]); $media['Path'] = $uploadResult['SaveName']; $id = $this->mediaModel->save($media); diff --git a/vanilla/library/Vanilla/Web/Controller.php b/vanilla/library/Vanilla/Web/Controller.php new file mode 100644 index 0000000..9e1e6a6 --- /dev/null +++ b/vanilla/library/Vanilla/Web/Controller.php @@ -0,0 +1,262 @@ + + * @copyright 2009-2019 Vanilla Forums Inc. + * @license GPL-2.0-only + */ + +namespace Vanilla\Web; + +use Garden\EventManager; +use Garden\Schema\Schema; +use Garden\Schema\Validation; +use Garden\Schema\ValidationException; +use Garden\Web\Exception\HttpException; +use Gdn_Locale as LocaleInterface; +use Gdn_Session as SessionInterface; +use Gdn_Upload as Upload; +use Vanilla\Exception\PermissionException; +use Vanilla\InjectableInterface; +use Vanilla\UploadedFile; +use Vanilla\Utility\ModelUtils; + +/** + * The controller base class. + */ +abstract class Controller implements InjectableInterface { + /** + * @var SessionInterface + */ + private $session; + + /** + * @var EventManager + */ + private $eventManager; + + /** + * @var LocaleInterface; + */ + private $locale; + + /** @var Upload */ + private $upload; + + /** + * Set the base dependencies of the controller. + * + * This method allows subclasses to declare their dependencies in their constructor without worrying about these + * dependencies. + * + * @param SessionInterface|null $session The session of the current user. + * @param EventManager|null $eventManager The event manager dependency. + * @param LocaleInterface|null $local The current locale for translations. + * @param Upload $upload File upload handler. + */ + public function setDependencies( + SessionInterface $session = null, + EventManager $eventManager = null, + LocaleInterface $local = null, + Upload $upload + ) { + $this->session = $session; + $this->eventManager = $eventManager; + $this->locale = $local; + $this->upload = $upload; + } + + /** + * Enforce the following permission(s) or throw an exception that the dispatcher can handle. + * + * When passing several permissions to check the user can have any of the permissions. If you want to force several + * permissions then make several calls to this method. + * + * @throws \Exception if no session is available. + * @throws HttpException if a ban has been applied on the permission(s) for this session. + * @throws PermissionException if the user does not have the specified permission(s). + * + * @param string|array $permission The permissions you are requiring. + * @param int|null $id The ID of the record we are checking the permission of. + */ + public function permission($permission = null, $id = null) { + if (!$this->session instanceof SessionInterface) { + throw new \Exception("Session not available.", 500); + } + $permissions = (array)$permission; + + /** + * First check to see if the user is banned. + */ + if ($ban = $this->session->getPermissions()->getBan($permissions)) { + $ban += ['code' => 401, 'msg' => 'Access denied.']; + + throw HttpException::createFromStatus($ban['code'], $ban['msg'], $ban); + } + + if (!$this->session->getPermissions()->hasAny($permissions, $id)) { + throw new PermissionException($permissions); + } + } + + /** + * Create a schema attached to an endpoint. + * + * @param array|Schema $schema The schema definition. This can be either an array or an actual schema object. + * @param string|array $type The type of schema. This can be one of the following: + * + * - **"in" or "out"**. Says this is an input or output schema. + * - **"TypeID"**. Says this the name of the schema being defined, but not used in an endpoint. You specify a type + * ID so the schema is pluggable. You name a type like you name a class. + * - **["TypeID", "in" or "out"]**. When you need to define both a type ID and input or output. + * @return Schema Returns a schema object. + */ + public function schema($schema, $type = 'in') { + $id = ''; + if (is_array($type)) { + $origType = $type; + list($id, $type) = $origType; + } elseif (!in_array($type, ['in', 'out'], true)) { + $id = $type; + $type = ''; + } + + // Figure out the name. + if (is_array($schema)) { + $schema = Schema::parse($schema); + } elseif ($schema instanceof Schema) { + $schema = clone $schema; + } + + // Fire an event for schema modification. + if (!empty($id)) { + // The type is a specific type of schema. + $schema->setID($id); + + $this->eventManager->fire("{$id}Schema_init", $schema); + } + + // Fire a generic schema event for documentation. + if (!empty($type)) { + $this->eventManager->fire("controller_schema", $this, $schema, $type); + } + + return $schema; + } + + /** + * Get the session. + * + * @return SessionInterface Returns the session. + */ + public function getSession() { + return $this->session; + } + + /** + * Set the session. + * + * @param SessionInterface $session The new session. + * @return $this + */ + public function setSession($session) { + $this->session = $session; + return $this; + } + + /** + * Get the event manager. + * + * @return EventManager Returns the event manager. + */ + public function getEventManager() { + return $this->eventManager; + } + + /** + * Set the event manager. + * + * @param EventManager $eventManager The new event manager. + * @return $this + */ + public function setEventManager($eventManager) { + $this->eventManager = $eventManager; + return $this; + } + + /** + * Get the locale. + * + * @return LocaleInterface Returns the locale. + */ + public function getLocale() { + return $this->locale; + } + + /** + * Set the locale. + * + * @param LocaleInterface $locale The locale of the current user. + * @return $this + */ + public function setLocale($locale) { + $this->locale = $locale; + return $this; + } + + /** + * Generate a valid upload path, relative to the uploads directory. + * + * @param string $ext The file's extension. + * @param bool $chunk Include an additional random subdirectory? + * @return string + */ + public function generateUploadPath($ext, $chunk = false) { + $path = $this->upload->generateTargetName(PATH_UPLOADS, $ext, $chunk); + $result = stringBeginsWith($path, PATH_UPLOADS.'/', false, true); + return $result; + } + + /** + * @param UploadedFile $upload + * @param string $destination + * @param string $nameFormat + * @param bool $copy + * @throws \Exception if failed to save the upload. + * @returns array|bool + */ + public function saveUpload(UploadedFile $upload, $destination, $nameFormat = '%s', $copy = false, $options = []) { + $destination = $result = stringBeginsWith($destination, PATH_UPLOADS.'/', false, true); + $ext = pathinfo($destination, PATHINFO_EXTENSION); + $baseName = basename($destination, ".{$ext}"); + $dirName = dirname($destination); + + $target = sprintf($nameFormat, $baseName); + if (!empty($ext)) { + $target .= ".{$ext}"; + } + if (!empty($dirName)) { + $target = "{$dirName}/{$target}"; + } + $target = PATH_UPLOADS."/{$target}"; + + $result = $this->upload->saveAs( + $upload->getFile(), + $target, + $options, + $copy + ); + return $result; + } + + /** + * Given a model, analyze its validation property and return failures. + * + * @param object $model The model to analyze the Validation property of. + * @param bool $throw If errors are found, should an exception be thrown? + * @throws ValidationException if errors are detected and $throw is true. + * @return Validation + */ + public function validateModel($model, $throw = true) { + return ModelUtils::validationResultToValidationException($model, $this->locale, $throw); + } +}