Fix codestyle
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / background / job / UnfurlUrlBackgroundJob.class.php
1 <?php
2
3 namespace wcf\system\background\job;
4
5 use BadMethodCallException;
6 use GuzzleHttp\Psr7\Response;
7 use wcf\data\unfurl\url\UnfurlUrl;
8 use wcf\data\unfurl\url\UnfurlUrlAction;
9 use wcf\system\message\unfurl\exception\DownloadFailed;
10 use wcf\system\message\unfurl\exception\ParsingFailed;
11 use wcf\system\message\unfurl\exception\UrlInaccessible;
12 use wcf\system\message\unfurl\UnfurlResponse;
13 use wcf\util\FileUtil;
14 use wcf\util\StringUtil;
15
16 /**
17 * Represents a background job to get information for an url.
18 *
19 * @author Joshua Ruesweg
20 * @copyright 2001-2020 WoltLab GmbH
21 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
22 * @package WoltLabSuite\Core\System\Background\Job
23 * @since 5.4
24 */
25 final class UnfurlUrlBackgroundJob extends AbstractBackgroundJob
26 {
27 /**
28 * @var int
29 */
30 private $urlID;
31
32 /**
33 * UnfurlURLJob constructor.
34 *
35 * @param UnfurlUrl $url
36 */
37 public function __construct(UnfurlUrl $url)
38 {
39 $this->urlID = $url->urlID;
40 }
41
42 /**
43 * @inheritDoc
44 */
45 public function retryAfter()
46 {
47 switch ($this->getFailures()) {
48 case 1:
49 // 5 minutes
50 return 5 * 60;
51 case 2:
52 // 30 minutes
53 return 30 * 60;
54 case 3:
55 // 2 hours
56 return 2 * 60 * 60;
57 }
58 }
59
60 /**
61 * @inheritDoc
62 */
63 public function perform()
64 {
65 $unfurlUrl = new UnfurlUrl($this->urlID);
66
67 try {
68 $unfurlResponse = UnfurlResponse::fetch($unfurlUrl->url);
69
70 if (empty(StringUtil::trim($unfurlResponse->getTitle()))) {
71 $this->save(UnfurlUrl::STATUS_REJECTED);
72
73 return;
74 }
75
76 $title = StringUtil::truncate($unfurlResponse->getTitle(), 255);
77 $description = "";
78 if ($unfurlResponse->getDescription()) {
79 $description = StringUtil::truncate($unfurlResponse->getDescription());
80 }
81
82 if ($unfurlResponse->getImageUrl()) {
83 try {
84 $image = $this->downloadImage($unfurlResponse->getImage());
85 $imageData = \getimagesizefromstring($image);
86 if ($imageData !== false) {
87 $imageType = $this->validateImage($imageData);
88 if (!(MODULE_IMAGE_PROXY || IMAGE_ALLOW_EXTERNAL_SOURCE)) {
89 $imageHash = $this->saveImage($imageData, $image);
90 } else {
91 $imageHash = "";
92 }
93 } else {
94 $imageType = UnfurlUrl::IMAGE_NO_IMAGE;
95 }
96
97 if ($imageType === UnfurlUrl::IMAGE_NO_IMAGE) {
98 $imageUrl = $imageHash = "";
99 } else {
100 $imageUrl = $unfurlResponse->getImageUrl();
101 }
102 } catch (UrlInaccessible | DownloadFailed $e) {
103 $imageType = UnfurlUrl::IMAGE_NO_IMAGE;
104 $imageUrl = $imageHash = "";
105 }
106 } else {
107 $imageType = UnfurlUrl::IMAGE_NO_IMAGE;
108 $imageUrl = $imageHash = "";
109 }
110
111 $this->save(
112 UnfurlUrl::STATUS_SUCCESSFUL,
113 $title,
114 $description,
115 $imageType,
116 $imageUrl,
117 $imageHash
118 );
119 } catch (UrlInaccessible | ParsingFailed $e) {
120 if (\ENABLE_DEBUG_MODE) {
121 \wcf\functions\exception\logThrowable($e);
122 }
123
124 $this->save(UnfurlUrl::STATUS_REJECTED);
125 }
126 }
127
128 private function downloadImage(Response $imageResponse): string
129 {
130 $image = "";
131 while (!$imageResponse->getBody()->eof()) {
132 $image .= $imageResponse->getBody()->read(8192);
133
134 if ($imageResponse->getBody()->tell() >= UnfurlResponse::MAX_IMAGE_SIZE) {
135 break;
136 }
137 }
138 $imageResponse->getBody()->close();
139
140 return $image;
141 }
142
143 private function validateImage(array $imageData): string
144 {
145 $isSquared = $imageData[0] === $imageData[1];
146 if (
147 (!$isSquared && ($imageData[0] < 300 && $imageData[1] < 150))
148 || \min($imageData[0], $imageData[1]) < 50
149 ) {
150 return UnfurlUrl::IMAGE_NO_IMAGE;
151 } else {
152 if ($isSquared) {
153 return UnfurlUrl::IMAGE_SQUARED;
154 } else {
155 return UnfurlUrl::IMAGE_COVER;
156 }
157 }
158 }
159
160 private function saveImage(array $imageData, string $image): string
161 {
162 switch ($imageData[2]) {
163 case \IMAGETYPE_PNG:
164 $extension = 'png';
165 break;
166 case \IMAGETYPE_GIF:
167 $extension = 'gif';
168 break;
169 case \IMAGETYPE_JPEG:
170 $extension = 'jpg';
171 break;
172
173 default:
174 throw new DownloadFailed();
175 }
176
177 $imageHash = \sha1($image);
178
179 $path = WCF_DIR . 'images/unfurlUrl/' . \substr($imageHash, 0, 2);
180 FileUtil::makePath($path);
181
182 $fileLocation = $path . '/' . $imageHash . '.' . $extension;
183
184 \file_put_contents($fileLocation, $image);
185
186 @\touch($fileLocation);
187
188 return $imageHash . '.' . $extension;
189 }
190
191 private function save(
192 string $status,
193 string $title = "",
194 string $description = "",
195 string $imageType = UnfurlUrl::IMAGE_NO_IMAGE,
196 string $imageUrl = "",
197 string $imageHash = ""
198 ): void {
199 switch ($status) {
200 case UnfurlUrl::STATUS_PENDING:
201 case UnfurlUrl::STATUS_REJECTED:
202 case UnfurlUrl::STATUS_SUCCESSFUL:
203 break;
204
205 default:
206 throw new BadMethodCallException("Invalid status '{$status}' given.");
207 }
208
209 switch ($imageType) {
210 case UnfurlUrl::IMAGE_COVER:
211 case UnfurlUrl::IMAGE_NO_IMAGE:
212 case UnfurlUrl::IMAGE_SQUARED:
213 break;
214
215 default:
216 throw new BadMethodCallException("Invalid imageType '{$imageType}' given.");
217 }
218
219 $urlAction = new UnfurlUrlAction([$this->urlID], 'update', [
220 'data' => [
221 'status' => $status,
222 'title' => $title,
223 'description' => $description,
224 'imageType' => $imageType,
225 'imageUrl' => $imageUrl,
226 'imageHash' => $imageHash,
227 'lastFetch' => TIME_NOW,
228 ],
229 ]);
230 $urlAction->executeAction();
231 }
232
233 /**
234 * @inheritDoc
235 */
236 public function onFinalFailure()
237 {
238 $this->save(UnfurlUrl::STATUS_REJECTED);
239 }
240 }