|
| 1 | +<?php |
| 2 | +/** |
| 3 | + * @author Todd Burry <todd@vanillaforums.com> |
| 4 | + * @copyright 2009-2019 Vanilla Forums Inc. |
| 5 | + * @license GPL-2.0-only |
| 6 | + */ |
| 7 | + |
| 8 | +namespace Vanilla\Web; |
| 9 | + |
| 10 | +use Garden\EventManager; |
| 11 | +use Garden\Schema\Schema; |
| 12 | +use Garden\Schema\Validation; |
| 13 | +use Garden\Schema\ValidationException; |
| 14 | +use Garden\Web\Exception\HttpException; |
| 15 | +use Gdn_Locale as LocaleInterface; |
| 16 | +use Gdn_Session as SessionInterface; |
| 17 | +use Gdn_Upload as Upload; |
| 18 | +use Vanilla\Exception\PermissionException; |
| 19 | +use Vanilla\InjectableInterface; |
| 20 | +use Vanilla\UploadedFile; |
| 21 | +use Vanilla\Utility\ModelUtils; |
| 22 | + |
| 23 | +/** |
| 24 | + * The controller base class. |
| 25 | + */ |
| 26 | +abstract class Controller implements InjectableInterface { |
| 27 | + /** |
| 28 | + * @var SessionInterface |
| 29 | + */ |
| 30 | + private $session; |
| 31 | + |
| 32 | + /** |
| 33 | + * @var EventManager |
| 34 | + */ |
| 35 | + private $eventManager; |
| 36 | + |
| 37 | + /** |
| 38 | + * @var LocaleInterface; |
| 39 | + */ |
| 40 | + private $locale; |
| 41 | + |
| 42 | + /** @var Upload */ |
| 43 | + private $upload; |
| 44 | + |
| 45 | + /** |
| 46 | + * Set the base dependencies of the controller. |
| 47 | + * |
| 48 | + * This method allows subclasses to declare their dependencies in their constructor without worrying about these |
| 49 | + * dependencies. |
| 50 | + * |
| 51 | + * @param SessionInterface|null $session The session of the current user. |
| 52 | + * @param EventManager|null $eventManager The event manager dependency. |
| 53 | + * @param LocaleInterface|null $local The current locale for translations. |
| 54 | + * @param Upload $upload File upload handler. |
| 55 | + */ |
| 56 | + public function setDependencies( |
| 57 | + SessionInterface $session = null, |
| 58 | + EventManager $eventManager = null, |
| 59 | + LocaleInterface $local = null, |
| 60 | + Upload $upload |
| 61 | + ) { |
| 62 | + $this->session = $session; |
| 63 | + $this->eventManager = $eventManager; |
| 64 | + $this->locale = $local; |
| 65 | + $this->upload = $upload; |
| 66 | + } |
| 67 | + |
| 68 | + /** |
| 69 | + * Enforce the following permission(s) or throw an exception that the dispatcher can handle. |
| 70 | + * |
| 71 | + * When passing several permissions to check the user can have any of the permissions. If you want to force several |
| 72 | + * permissions then make several calls to this method. |
| 73 | + * |
| 74 | + * @throws \Exception if no session is available. |
| 75 | + * @throws HttpException if a ban has been applied on the permission(s) for this session. |
| 76 | + * @throws PermissionException if the user does not have the specified permission(s). |
| 77 | + * |
| 78 | + * @param string|array $permission The permissions you are requiring. |
| 79 | + * @param int|null $id The ID of the record we are checking the permission of. |
| 80 | + */ |
| 81 | + public function permission($permission = null, $id = null) { |
| 82 | + if (!$this->session instanceof SessionInterface) { |
| 83 | + throw new \Exception("Session not available.", 500); |
| 84 | + } |
| 85 | + $permissions = (array)$permission; |
| 86 | + |
| 87 | + /** |
| 88 | + * First check to see if the user is banned. |
| 89 | + */ |
| 90 | + if ($ban = $this->session->getPermissions()->getBan($permissions)) { |
| 91 | + $ban += ['code' => 401, 'msg' => 'Access denied.']; |
| 92 | + |
| 93 | + throw HttpException::createFromStatus($ban['code'], $ban['msg'], $ban); |
| 94 | + } |
| 95 | + |
| 96 | + if (!$this->session->getPermissions()->hasAny($permissions, $id)) { |
| 97 | + throw new PermissionException($permissions); |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + /** |
| 102 | + * Create a schema attached to an endpoint. |
| 103 | + * |
| 104 | + * @param array|Schema $schema The schema definition. This can be either an array or an actual schema object. |
| 105 | + * @param string|array $type The type of schema. This can be one of the following: |
| 106 | + * |
| 107 | + * - **"in" or "out"**. Says this is an input or output schema. |
| 108 | + * - **"TypeID"**. Says this the name of the schema being defined, but not used in an endpoint. You specify a type |
| 109 | + * ID so the schema is pluggable. You name a type like you name a class. |
| 110 | + * - **["TypeID", "in" or "out"]**. When you need to define both a type ID and input or output. |
| 111 | + * @return Schema Returns a schema object. |
| 112 | + */ |
| 113 | + public function schema($schema, $type = 'in') { |
| 114 | + $id = ''; |
| 115 | + if (is_array($type)) { |
| 116 | + $origType = $type; |
| 117 | + list($id, $type) = $origType; |
| 118 | + } elseif (!in_array($type, ['in', 'out'], true)) { |
| 119 | + $id = $type; |
| 120 | + $type = ''; |
| 121 | + } |
| 122 | + |
| 123 | + // Figure out the name. |
| 124 | + if (is_array($schema)) { |
| 125 | + $schema = Schema::parse($schema); |
| 126 | + } elseif ($schema instanceof Schema) { |
| 127 | + $schema = clone $schema; |
| 128 | + } |
| 129 | + |
| 130 | + // Fire an event for schema modification. |
| 131 | + if (!empty($id)) { |
| 132 | + // The type is a specific type of schema. |
| 133 | + $schema->setID($id); |
| 134 | + |
| 135 | + $this->eventManager->fire("{$id}Schema_init", $schema); |
| 136 | + } |
| 137 | + |
| 138 | + // Fire a generic schema event for documentation. |
| 139 | + if (!empty($type)) { |
| 140 | + $this->eventManager->fire("controller_schema", $this, $schema, $type); |
| 141 | + } |
| 142 | + |
| 143 | + return $schema; |
| 144 | + } |
| 145 | + |
| 146 | + /** |
| 147 | + * Get the session. |
| 148 | + * |
| 149 | + * @return SessionInterface Returns the session. |
| 150 | + */ |
| 151 | + public function getSession() { |
| 152 | + return $this->session; |
| 153 | + } |
| 154 | + |
| 155 | + /** |
| 156 | + * Set the session. |
| 157 | + * |
| 158 | + * @param SessionInterface $session The new session. |
| 159 | + * @return $this |
| 160 | + */ |
| 161 | + public function setSession($session) { |
| 162 | + $this->session = $session; |
| 163 | + return $this; |
| 164 | + } |
| 165 | + |
| 166 | + /** |
| 167 | + * Get the event manager. |
| 168 | + * |
| 169 | + * @return EventManager Returns the event manager. |
| 170 | + */ |
| 171 | + public function getEventManager() { |
| 172 | + return $this->eventManager; |
| 173 | + } |
| 174 | + |
| 175 | + /** |
| 176 | + * Set the event manager. |
| 177 | + * |
| 178 | + * @param EventManager $eventManager The new event manager. |
| 179 | + * @return $this |
| 180 | + */ |
| 181 | + public function setEventManager($eventManager) { |
| 182 | + $this->eventManager = $eventManager; |
| 183 | + return $this; |
| 184 | + } |
| 185 | + |
| 186 | + /** |
| 187 | + * Get the locale. |
| 188 | + * |
| 189 | + * @return LocaleInterface Returns the locale. |
| 190 | + */ |
| 191 | + public function getLocale() { |
| 192 | + return $this->locale; |
| 193 | + } |
| 194 | + |
| 195 | + /** |
| 196 | + * Set the locale. |
| 197 | + * |
| 198 | + * @param LocaleInterface $locale The locale of the current user. |
| 199 | + * @return $this |
| 200 | + */ |
| 201 | + public function setLocale($locale) { |
| 202 | + $this->locale = $locale; |
| 203 | + return $this; |
| 204 | + } |
| 205 | + |
| 206 | + /** |
| 207 | + * Generate a valid upload path, relative to the uploads directory. |
| 208 | + * |
| 209 | + * @param string $ext The file's extension. |
| 210 | + * @param bool $chunk Include an additional random subdirectory? |
| 211 | + * @return string |
| 212 | + */ |
| 213 | + public function generateUploadPath($ext, $chunk = false) { |
| 214 | + $path = $this->upload->generateTargetName(PATH_UPLOADS, $ext, $chunk); |
| 215 | + $result = stringBeginsWith($path, PATH_UPLOADS.'/', false, true); |
| 216 | + return $result; |
| 217 | + } |
| 218 | + |
| 219 | + /** |
| 220 | + * @param UploadedFile $upload |
| 221 | + * @param string $destination |
| 222 | + * @param string $nameFormat |
| 223 | + * @param bool $copy |
| 224 | + * @throws \Exception if failed to save the upload. |
| 225 | + * @returns array|bool |
| 226 | + */ |
| 227 | + public function saveUpload(UploadedFile $upload, $destination, $nameFormat = '%s', $copy = false, $options = []) { |
| 228 | + $destination = $result = stringBeginsWith($destination, PATH_UPLOADS.'/', false, true); |
| 229 | + $ext = pathinfo($destination, PATHINFO_EXTENSION); |
| 230 | + $baseName = basename($destination, ".{$ext}"); |
| 231 | + $dirName = dirname($destination); |
| 232 | + |
| 233 | + $target = sprintf($nameFormat, $baseName); |
| 234 | + if (!empty($ext)) { |
| 235 | + $target .= ".{$ext}"; |
| 236 | + } |
| 237 | + if (!empty($dirName)) { |
| 238 | + $target = "{$dirName}/{$target}"; |
| 239 | + } |
| 240 | + $target = PATH_UPLOADS."/{$target}"; |
| 241 | + |
| 242 | + $result = $this->upload->saveAs( |
| 243 | + $upload->getFile(), |
| 244 | + $target, |
| 245 | + $options, |
| 246 | + $copy |
| 247 | + ); |
| 248 | + return $result; |
| 249 | + } |
| 250 | + |
| 251 | + /** |
| 252 | + * Given a model, analyze its validation property and return failures. |
| 253 | + * |
| 254 | + * @param object $model The model to analyze the Validation property of. |
| 255 | + * @param bool $throw If errors are found, should an exception be thrown? |
| 256 | + * @throws ValidationException if errors are detected and $throw is true. |
| 257 | + * @return Validation |
| 258 | + */ |
| 259 | + public function validateModel($model, $throw = true) { |
| 260 | + return ModelUtils::validationResultToValidationException($model, $this->locale, $throw); |
| 261 | + } |
| 262 | +} |
0 commit comments