Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
262 changes: 262 additions & 0 deletions vanilla/library/Vanilla/Web/Controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
<?php
/**
* @author Todd Burry <todd@vanillaforums.com>
* @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);
}
}