PluginProbe
User Access Manager / trunk
User Access Manager vtrunk
2.3.20 2.3.19 2.3.18 2.3.17 2.3.16 2.3.15 2.3.14 2.3.13 trunk 0.6 0.6.1 0.6.2 0.7 0.7 Beta 0.7.0.1 0.8 0.8.0.1 0.8.0.2 0.9 0.9.1 0.9.1.1 0.9.1.2 0.9.1.3 0.9.1.4 1.0 All 136 releases
user-access-manager / src / Controller / Frontend / RedirectController.php

RedirectController.php in User Access Manager trunk, at src/Controller/Frontend/RedirectController.php

389 lines 13.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace UserAccessManager\Controller\Frontend;
6
7 use JetBrains\PhpStorm\NoReturn;
8 use UserAccessManager\Access\AccessHandler;
9 use UserAccessManager\Cache\Cache;
10 use UserAccessManager\Config\MainConfig;
11 use UserAccessManager\Config\WordpressConfig;
12 use UserAccessManager\Controller\Controller;
13 use UserAccessManager\Controller\Frontend\Authentication\LoginControllerTrait;
14 use UserAccessManager\Database\Database;
15 use UserAccessManager\File\FileHandler;
16 use UserAccessManager\File\Delivery\FileObject;
17 use UserAccessManager\File\Delivery\FileObjectFactory;
18 use UserAccessManager\Object\ObjectHandler;
19 use UserAccessManager\UserGroup\UserGroupTypeException;
20 use UserAccessManager\Util\Util;
21 use UserAccessManager\Wrapper\Php;
22 use UserAccessManager\Wrapper\Wordpress;
23
24 class RedirectController extends Controller
25 {
26 use LoginControllerTrait;
27
28 public const POST_URL_CACHE_KEY = 'PostUrls';
29 public const REDIRECT_TO_PARAMETER = 'redirect_to';
30
31 public function __construct(
32 Php $php,
33 Wordpress $wordpress,
34 WordpressConfig $wordpressConfig,
35 private MainConfig $mainConfig,
36 private Database $database,
37 private Util $util,
38 private Cache $cache,
39 private ObjectHandler $objectHandler,
40 private AccessHandler $accessHandler,
41 private FileHandler $fileHandler,
42 private FileObjectFactory $fileObjectFactory
43 ) {
44 parent::__construct($php, $wordpress, $wordpressConfig);
45 }
46
47 protected function getWordpress(): Wordpress
48 {
49 return $this->wordpress;
50 }
51
52 public function getPostIdByUrl(string $url): int
53 {
54 $postUrls = (array)$this->cache->getFromRuntimeCache(self::POST_URL_CACHE_KEY);
55
56 if (isset($postUrls[$url]) === true) {
57 return $postUrls[$url];
58 }
59
60 //Filter size
61 $newUrlPieces = preg_split('/-[0-9]+x[0-9]+(_[a-z])?/', $url);
62 $newUrl = (count($newUrlPieces) === 2) ? $newUrlPieces[0] . $newUrlPieces[1] : $newUrlPieces[0];
63 $newUrl = preg_replace('/-pdf\.jpg$/', '.pdf', $newUrl);
64
65 $postId = $this->wordpress->attachmentUrlToPostId($newUrl);
66
67 if ($postId === 0) {
68 $newUrl = preg_replace('/(\\.[^.\\s]{3,4})$/', '-scaled$1', $newUrl);
69 $postId = $this->wordpress->attachmentUrlToPostId($newUrl);
70 }
71
72 $postUrls[$url] = $postId;
73 $this->cache->addToRuntimeCache(self::POST_URL_CACHE_KEY, $postUrls);
74
75 return $postUrls[$url];
76 }
77
78 private function normalizeAttachmentUrl(array $uploadDirs, string $objectUrl): string
79 {
80 $uploadDir = str_replace(ABSPATH, '/', $uploadDirs['basedir']);
81 $regex = '/.*' . str_replace('/', '\/', $uploadDir) . '\//i';
82 $cleanObjectUrl = preg_replace($regex, '', $objectUrl);
83 $uploadUrl = str_replace('/files', $uploadDir, $uploadDirs['baseurl']);
84
85 return rtrim($uploadUrl, '/') . '/' . ltrim($cleanObjectUrl, '/');
86 }
87
88 private function isRegisteredSize(int $attachmentId, string $fileName): bool
89 {
90 $metaData = $this->wordpress->getAttachmentMetadata($attachmentId);
91 $sizes = is_array($metaData) === true ? (array) ($metaData['sizes'] ?? []) : [];
92
93 return in_array($fileName, array_column($sizes, 'file'), true);
94 }
95
96 /**
97 * Returns the file the request asks for and tells through $isImage whether it can be shown as image.
98 * A requested generated size is only used if it is registered at the attachment and stored inside the
99 * upload directory, so no arbitrary and no missing file becomes reachable. Generated sizes are always
100 * images, also for documents like PDFs, where they are the preview images shown in the media library.
101 */
102 private function getAttachmentFile(
103 int $attachmentId,
104 string $attachedFile,
105 string $requestedUrl,
106 string $uploadBaseDir,
107 ?bool &$isImage
108 ): string {
109 $requestedFileName = basename((string) parse_url($requestedUrl, PHP_URL_PATH));
110 $sizeFile = dirname($attachedFile) . DIRECTORY_SEPARATOR . $requestedFileName;
111
112 if ($requestedFileName !== basename($attachedFile)
113 && $this->isRegisteredSize($attachmentId, $requestedFileName) === true
114 && $this->isInsideUploadDirectory($sizeFile, $uploadBaseDir) === true
115 ) {
116 $isImage = true;
117
118 return $sizeFile;
119 }
120
121 $isImage = $this->wordpress->attachmentIsImage($attachmentId);
122
123 return $attachedFile;
124 }
125
126 private function getAttachmentFileObject(string $objectUrl): ?FileObject
127 {
128 $uploadDirs = $this->wordpress->getUploadDir();
129 $requestedUrl = $this->normalizeAttachmentUrl($uploadDirs, $objectUrl);
130 $postId = $this->getPostIdByUrl($requestedUrl);
131
132 if ($postId < 1) {
133 return null;
134 }
135
136 $post = $this->objectHandler->getPost($postId);
137
138 if (($post->post_type ?? '') !== ObjectHandler::ATTACHMENT_OBJECT_TYPE) {
139 return null;
140 }
141
142 // Unfiltered, because the plugin denies the path through the get_attached_file filter for
143 // users without access. Filtered it would hide the file from the access check below, which
144 // then could not answer the request with the no rights page any more.
145 $attachedFile = $this->wordpress->getAttachedFile($post->ID, true);
146
147 if ($attachedFile === false
148 || $this->isInsideUploadDirectory($attachedFile, $uploadDirs['basedir']) === false
149 ) {
150 return null;
151 }
152
153 $file = $this->getAttachmentFile(
154 $post->ID,
155 $attachedFile,
156 $requestedUrl,
157 $uploadDirs['basedir'],
158 $isImage
159 );
160
161 return $this->fileObjectFactory->createFileObject(
162 $post->ID,
163 ObjectHandler::ATTACHMENT_OBJECT_TYPE,
164 $file,
165 $isImage
166 );
167 }
168
169 private function isInsideUploadDirectory(string $file, string $uploadBaseDir): bool
170 {
171 $realFile = $this->php->realpath($file);
172 $realUploadBaseDir = $this->php->realpath($uploadBaseDir);
173
174 return $realFile !== false
175 && $realUploadBaseDir !== false
176 && str_starts_with($realFile, $realUploadBaseDir . DIRECTORY_SEPARATOR);
177 }
178
179 private function getFileSettingsByType(string $objectType, string $objectUrl): ?FileObject
180 {
181 if ($objectType === ObjectHandler::ATTACHMENT_OBJECT_TYPE) {
182 return $this->getAttachmentFileObject($objectUrl);
183 }
184
185 $extraParameter = $this->getRequestParameter('uamextra');
186
187 return $this->wordpress->applyFilters(
188 'uam_get_file_settings_by_type',
189 null,
190 $objectType,
191 $objectUrl,
192 $extraParameter
193 );
194 }
195
196 /**
197 * @throws UserGroupTypeException
198 */
199 public function getFile(string $objectType, string $objectUrl): void
200 {
201 $fileObject = $this->getFileSettingsByType($objectType, $objectUrl);
202
203 if ($fileObject === null) {
204 return;
205 }
206
207 if ($this->accessHandler->checkObjectAccess($fileObject->getType(), $fileObject->getId()) === true) {
208 $file = $fileObject->getFile();
209 } elseif ($fileObject->isImage() === true) {
210 if ($this->mainConfig->getNoAccessImageType() === 'custom') {
211 $file = $this->mainConfig->getCustomNoAccessImage();
212 } else {
213 $realPath = $this->wordpressConfig->getRealPath();
214 $file = $realPath . 'assets' . DIRECTORY_SEPARATOR . 'gfx' . DIRECTORY_SEPARATOR . 'noAccessPic.png';
215 }
216 } else {
217 $this->wordpress->wpDie(TXT_UAM_NO_RIGHTS_MESSAGE, TXT_UAM_NO_RIGHTS_TITLE, ['response' => 403]);
218 return;
219 }
220
221 $this->fileHandler->getFile($file, $fileObject->isImage());
222 }
223
224 private function getRedirectUrlAndPermalink(?string &$permalink): ?string
225 {
226 $permalink = null;
227 $redirect = $this->mainConfig->getRedirect();
228
229 if ($redirect === 'custom_page') {
230 $redirectCustomPage = $this->mainConfig->getRedirectCustomPage();
231 $post = $this->objectHandler->getPost($redirectCustomPage);
232 $url = null;
233
234 if ($post !== false) {
235 $url = $post->guid;
236 $permalink = $this->wordpress->getPageLink($post);
237 }
238 } elseif ($redirect === 'custom_url') {
239 $url = $this->mainConfig->getRedirectCustomUrl();
240 } elseif ($redirect === 'login') {
241 $url = $this->getLoginUrl();
242 } elseif ($redirect === 'origin') {
243 $referer = $this->wordpress->getReferer();
244 $url = $referer !== false ? $referer : $this->wordpress->getHomeUrl('/');
245 } else {
246 $url = $this->wordpress->getHomeUrl('/');
247 }
248
249 return $url;
250 }
251
252 /**
253 * @throws UserGroupTypeException
254 */
255 public function redirectUser(bool $checkPosts = true): void
256 {
257 if ($checkPosts === true) {
258 $posts = $this->wordpress->getWpQuery()->get_posts();
259
260 foreach ($posts as $post) {
261 if ($this->accessHandler->checkObjectAccess($post->post_type, $post->ID)) {
262 return;
263 }
264 }
265 }
266
267 $url = $this->getRedirectUrlAndPermalink($permalink);
268 $currentUrl = $this->util->getCurrentUrl();
269
270 if ($url !== null && $url !== $currentUrl && $permalink !== $currentUrl) {
271 if ($this->mainConfig->appendRedirectToParameter() === true) {
272 $url = $this->wordpress->addQueryArg([self::REDIRECT_TO_PARAMETER => $currentUrl], $url);
273 }
274
275 $this->wordpress->wpRedirect($url);
276 $this->php->callExit();
277 }
278 }
279
280 private function getPostIdByName(string $name): int
281 {
282 $postableTypes = implode('\',\'', $this->objectHandler->getPostTypes());
283
284 $query = $this->database->prepare(
285 "SELECT `ID`
286 FROM `{$this->database->getPostsTable()}`
287 WHERE `post_name` = %s
288 AND `post_type` IN ('$postableTypes')",
289 $name
290 );
291
292 return (int) $this->database->getVariable($query);
293 }
294
295 private function extractObjectTypeAndId(mixed $pageParams, ?string &$objectType, int|string|null &$objectId): void
296 {
297 $objectType = null;
298 $objectId = null;
299
300 $simpleTypes = [
301 'p' => ObjectHandler::GENERAL_POST_OBJECT_TYPE,
302 'page_id' => ObjectHandler::GENERAL_POST_OBJECT_TYPE,
303 'cat_id' => ObjectHandler::GENERAL_TERM_OBJECT_TYPE
304 ];
305
306 foreach ($simpleTypes as $queryVar => $newObjectType) {
307 if (isset($pageParams->query_vars[$queryVar]) === true) {
308 $objectType = $newObjectType;
309 $objectId = $pageParams->query_vars[$queryVar];
310 }
311 }
312
313 if (isset($pageParams->query_vars['name']) === true) {
314 $objectType = ObjectHandler::GENERAL_POST_OBJECT_TYPE;
315 $objectId = $this->getPostIdByName($pageParams->query_vars['name']);
316 } elseif (isset($pageParams->query_vars['pagename']) === true) {
317 $object = $this->wordpress->getPageByPath($pageParams->query_vars['pagename']);
318
319 if ($object !== null) {
320 $objectType = $object->post_type ?? null;
321 $objectId = $object->ID ?? null;
322 }
323 }
324 }
325
326 /**
327 * @throws UserGroupTypeException
328 */
329 public function redirect(?array $headers, mixed $pageParams): ?array
330 {
331 $fileUrl = $this->getRequestParameter('uamgetfile');
332 $fileType = $this->getRequestParameter('uamfiletype');
333
334 if ($fileUrl !== null && $fileType !== null) {
335 $this->getFile($fileType, $fileUrl);
336 } elseif ($this->wordpressConfig->atAdminPanel() === false
337 && $this->mainConfig->getRedirect() !== 'false'
338 ) {
339 $this->extractObjectTypeAndId($pageParams, $objectType, $objectId);
340
341 if ($this->accessHandler->checkObjectAccess($objectType, $objectId) === false) {
342 $this->redirectUser(false);
343 }
344 }
345
346 return $headers;
347 }
348
349 public function getFileUrl(string $url, int|string|null $id): string
350 {
351 // Nginx always supports real urls so we need the new urls only
352 // if we don't use nginx and mod_rewrite is disabled
353 if ($this->mainConfig->lockFile() === true
354 && $this->wordpress->isNginx() === false
355 && $this->wordpress->gotModRewrite() === false
356 ) {
357 $post = $this->objectHandler->getPost($id);
358
359 if ($post !== false) {
360 $type = explode('/', $post->post_mime_type);
361 $type = $type[1] ?? $type[0];
362
363 $lockedFileTypes = $this->mainConfig->getLockedFiles();
364 $fileTypes = explode(',', $lockedFileTypes);
365
366 if ($lockedFileTypes === 'all' || in_array($type, $fileTypes) === true) {
367 $url = $this->wordpress->getHomeUrl('/') . '?uamfiletype=attachment&uamgetfile=' . $url;
368 }
369 }
370 }
371
372 return $url;
373 }
374
375 public function cachePostLinks(string $url, object $post): string
376 {
377 $postUrls = (array) $this->cache->getFromRuntimeCache(self::POST_URL_CACHE_KEY);
378 $postUrls[$url] = $post->ID;
379 $this->cache->addToRuntimeCache(self::POST_URL_CACHE_KEY, $postUrls);
380 return $url;
381 }
382
383 #[NoReturn]
384 public function testXSendFile(): void
385 {
386 $this->fileHandler->deliverXSendFileTestFile();
387 }
388 }
389