1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6namespace App\Http\Controllers;
7
8use App\Libraries\LocaleMeta;
9use App\Libraries\OsuWiki;
10use App\Libraries\Search\WikiSuggestions;
11use App\Libraries\Search\WikiSuggestionsRequestParams;
12use App\Libraries\Wiki\WikiSitemap;
13use App\Libraries\WikiRedirect;
14use App\Models\Wiki;
15use Request;
16
17/**
18 * @group Wiki
19 */
20class WikiController extends Controller
21{
22 /**
23 * Get Wiki Page
24 *
25 * The wiki article or image data.
26 *
27 * ---
28 *
29 * ### Response Format
30 *
31 * Returns [WikiPage](#wikipage).
32 *
33 * @urlParam locale string required Two-letter language code of the wiki page. Example: en
34 * @urlParam path string required The path name of the wiki page. Example: Welcome
35 */
36 public function show($locale = null, $path = null)
37 {
38 // redirect to default page if missing all parameters
39 if ($locale === null) {
40 return ujs_redirect(wiki_url(null, $this->locale()));
41 }
42
43 $cleanLocale = LocaleMeta::sanitizeCode($locale);
44
45 // if images slip through the markdown processing, redirect them to the correct place
46 if (OsuWiki::isImage($path)) {
47 $prependPath = $locale === 'images' || $cleanLocale === null ? $locale : null;
48
49 return ujs_redirect(wiki_image_url(concat_path([$prependPath, $path])));
50 }
51
52 // if invalid locale, assume locale to be part of path and
53 // actual locale to be either user locale or passed as parameter
54 if ($cleanLocale === null) {
55 return ujs_redirect(wiki_url(concat_path([$locale, $path]), $this->locale()));
56 }
57
58 // in case locale is passed as query parameter (legacy url inside the page)
59 // or it's in wrong case, redirect to new path
60 $queryLocale = $this->locale();
61 if ($queryLocale !== $locale && present($path)) {
62 return ujs_redirect(wiki_url($path, $queryLocale));
63 }
64
65 // normalize path by making sure no trailing slash and encoded forward slash (%2F)
66 $rawPath = request()->getPathInfo();
67 if (substr($rawPath, -1) === '/' || strpos($rawPath, '%2F') !== false) {
68 return ujs_redirect(wiki_url(rtrim($path, '/'), $locale));
69 }
70
71 // legal pages should be displayed with their own style etc
72 if (starts_with("{$path}/", 'Legal/') && !is_api_request()) {
73 return ujs_redirect(wiki_url($path, $locale));
74 }
75
76 $page = Wiki\Page::lookupForController($path, $locale);
77
78 if (!$page->isVisible()) {
79 $redirect = (new WikiRedirect())->sync();
80 $redirectTarget = $redirect->resolve($path);
81 if ($redirectTarget !== null && $redirectTarget !== $path) {
82 return ujs_redirect(wiki_url(ltrim($redirectTarget, '/'), $locale));
83 }
84
85 $redirectTarget = $redirect->resolve(concat_path([$locale, $path]));
86 if ($redirectTarget !== null && $redirectTarget !== $path) {
87 return ujs_redirect(wiki_url(ltrim($redirectTarget, '/')));
88 }
89
90 if (!present($path)) {
91 return ujs_redirect(wiki_url($locale));
92 }
93
94 $correctPath = Wiki\Page::searchPath($path, $locale);
95 if ($correctPath !== null && $correctPath !== $path) {
96 return ujs_redirect(wiki_url($correctPath, $locale));
97 }
98
99 $status = 404;
100 }
101
102 if (is_json_request()) {
103 if (!$page->isVisible()) {
104 return response(null, 404);
105 }
106
107 return json_item($page, 'WikiPage');
108 }
109
110 set_opengraph($page);
111
112 return ext_view(
113 $page->template(),
114 ['contentLocale' => $page->locale, 'page' => $page],
115 null,
116 $status ?? null
117 );
118 }
119
120 public function image($path)
121 {
122 if (!OsuWiki::isImage($path)) {
123 return response('Invalid file format', 422);
124 }
125
126 $image = (new Wiki\Image($path))->sync();
127
128 if (!$image->isVisible()) {
129 return response('Not found', 404);
130 }
131
132 $imageData = $image->get();
133
134 return response($imageData['content'], 200)
135 ->header('Content-Type', $imageData['type'])
136 // 10 years max-age
137 ->header('Cache-Control', 'max-age=315360000, public');
138 }
139
140 public function sitemap($locale)
141 {
142 if (!LocaleMeta::isValid($locale)) {
143 return ujs_redirect(route('wiki.sitemap', ['locale' => app()->getLocale()]));
144 }
145
146 return ext_view('wiki.sitemap', [
147 'locale' => $locale,
148 'sitemap' => WikiSitemap::get(),
149 ]);
150 }
151
152 public function suggestions()
153 {
154 $search = new WikiSuggestions(new WikiSuggestionsRequestParams(request()->all()));
155
156 $response = [];
157 foreach ($search->response() as $hit) {
158 $response[] = [
159 'highlight' => $hit->highlights('title.autocomplete')[0],
160 'path' => $hit->source('path'),
161 'title' => $hit->source('title'),
162 ];
163 }
164
165 return $response;
166 }
167
168 public function update($locale, $path)
169 {
170 priv_check('WikiPageRefresh')->ensureCan();
171
172 if (strtolower($path) === 'sitemap') {
173 WikiSitemap::expire();
174 } else {
175 (new Wiki\Page($path, $locale))->sync(true);
176 }
177
178 return ujs_redirect(Request::getUri());
179 }
180}