Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
46.15% |
78 / 169 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
StoryRenderer | |
46.15% |
78 / 169 |
|
0.00% |
0 / 8 |
97.56 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
renderNoJS | |
79.55% |
35 / 44 |
|
0.00% |
0 / 1 |
4.14 | |||
getStoryData | |
95.56% |
43 / 45 |
|
0.00% |
0 / 1 |
8 | |||
getUrl | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getAttribution | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
12 | |||
getNoJsFrameHtmlString | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
getAttributionHtmlString | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
2 | |||
getLicensesHtmlString | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\Wikistories; |
4 | |
5 | use File; |
6 | use FormatMetadata; |
7 | use MediaWiki\Context\RequestContext; |
8 | use MediaWiki\Html\Html; |
9 | use MediaWiki\Page\PageLookup; |
10 | use MediaWiki\Page\RedirectLookup; |
11 | use MediaWiki\SpecialPage\SpecialPage; |
12 | use MediaWiki\Title\Title; |
13 | use RepoGroup; |
14 | |
15 | class StoryRenderer { |
16 | |
17 | /** @var RepoGroup */ |
18 | private $repoGroup; |
19 | |
20 | /** @var RedirectLookup */ |
21 | private $redirectLookup; |
22 | |
23 | /** @var PageLookup */ |
24 | private $pageLookup; |
25 | |
26 | /** @var StoryContentAnalyzer */ |
27 | private $analyzer; |
28 | |
29 | /** @var StoryTrackingCategories */ |
30 | private $storyTrackingCategories; |
31 | |
32 | /** |
33 | * @param RepoGroup $repoGroup |
34 | * @param RedirectLookup $redirectLookup |
35 | * @param PageLookup $pageLookup |
36 | * @param StoryContentAnalyzer $analyzer |
37 | * @param StoryTrackingCategories $storyTrackingCategories |
38 | */ |
39 | public function __construct( |
40 | RepoGroup $repoGroup, |
41 | RedirectLookup $redirectLookup, |
42 | PageLookup $pageLookup, |
43 | StoryContentAnalyzer $analyzer, |
44 | StoryTrackingCategories $storyTrackingCategories |
45 | ) { |
46 | $this->repoGroup = $repoGroup; |
47 | $this->redirectLookup = $redirectLookup; |
48 | $this->pageLookup = $pageLookup; |
49 | $this->analyzer = $analyzer; |
50 | $this->storyTrackingCategories = $storyTrackingCategories; |
51 | } |
52 | |
53 | /** |
54 | * @param array $storyData |
55 | * @return array [ 'html', 'style' ] |
56 | */ |
57 | public function renderNoJS( array $storyData ): array { |
58 | $articleTitle = Title::makeTitle( |
59 | NS_MAIN, |
60 | $storyData[ 'articleTitle' ], |
61 | '/story/' . $storyData[ 'articleId' ] |
62 | ); |
63 | $missingImages = array_filter( $storyData[ 'frames' ], static function ( $frame ) { |
64 | return $frame[ 'fileNotFound' ]; |
65 | } ); |
66 | |
67 | $html = Html::element( |
68 | 'a', |
69 | [ 'href' => $articleTitle->getLinkURL() ], |
70 | $articleTitle->getText() |
71 | ); |
72 | |
73 | if ( in_array( $this->storyTrackingCategories::TC_NO_ARTICLE, $storyData[ 'trackingCategories' ] ) ) { |
74 | $context = RequestContext::getMain(); |
75 | $html .= Html::errorBox( |
76 | $context->msg( 'wikistories-nojs-viewer-no-article' )->parse() |
77 | ); |
78 | } |
79 | |
80 | if ( count( $missingImages ) > 0 ) { |
81 | $context = RequestContext::getMain(); |
82 | $html .= Html::warningBox( |
83 | $context->msg( 'wikistories-nojs-viewer-error' )->params( count( $missingImages ) )->parse() |
84 | ); |
85 | } |
86 | |
87 | $html .= Html::rawElement( |
88 | 'div', |
89 | [ 'class' => 'ext-wikistories-viewer-nojs' ], |
90 | implode( '', array_map( function ( $frame ) { |
91 | return Html::rawElement( |
92 | 'div', |
93 | [ |
94 | 'class' => 'ext-wikistories-viewer-nojs-frame', |
95 | 'style' => $frame[ 'url' ] === '' ? |
96 | 'background-color: #000' |
97 | : |
98 | 'background-image:url(' . $frame[ 'url' ] . ');', |
99 | ], |
100 | $this->getNoJsFrameHtmlString( $frame ) |
101 | ); |
102 | }, $storyData[ 'frames' ] ) ) |
103 | ); |
104 | |
105 | return [ |
106 | 'html' => $html, |
107 | 'style' => 'ext.wikistories.viewer-nojs' |
108 | ]; |
109 | } |
110 | |
111 | /** |
112 | * @param StoryContent $story |
113 | * @param Title $storyTitle |
114 | * @return array |
115 | */ |
116 | public function getStoryData( |
117 | StoryContent $story, |
118 | Title $storyTitle |
119 | ): array { |
120 | $frames = $story->getFrames(); |
121 | $filesUsed = array_map( static function ( $frame ) { |
122 | return $frame->image->filename; |
123 | }, $frames ); |
124 | $files = $this->repoGroup->findFiles( $filesUsed ); |
125 | $firstFrame = reset( $frames ); |
126 | $thumb = $firstFrame ? $this->getUrl( $files, $firstFrame->image->filename, 52 ) : ''; |
127 | $article = $story->getArticleTitle(); |
128 | $trackingCategories = []; |
129 | |
130 | if ( !$article ) { |
131 | $trackingCategories[] = $this->storyTrackingCategories::TC_NO_ARTICLE; |
132 | } |
133 | |
134 | $data = [ |
135 | 'articleId' => $article ? $article->getId() : 0, |
136 | 'articleTitle' => $article ? $article->getDBkey() : '', |
137 | 'storyId' => $storyTitle->getId(), |
138 | 'storyTitle' => $storyTitle->getText(), |
139 | 'editUrl' => SpecialPage::getTitleFor( 'StoryBuilder', $storyTitle->getPrefixedDBkey() )->getLinkURL(), |
140 | 'talkUrl' => $storyTitle->getTalkPageIfDefined()->getLinkURL(), |
141 | 'shareUrl' => $storyTitle->getFullURL( [ 'action' => 'storyview' ] ), |
142 | 'thumbnail' => $thumb, |
143 | 'frames' => array_map( function ( $frame ) use ( $files, $article, &$trackingCategories ) { |
144 | $url = $this->getUrl( $files, $frame->image->filename, 640 ); |
145 | if ( $url === '' ) { |
146 | $trackingCategories[] = $this->storyTrackingCategories::TC_NO_IMAGE; |
147 | } |
148 | $outdatedText = $article && $this->analyzer->isOutdatedText( |
149 | $this->analyzer->getArticleText( $article ), |
150 | $frame->text->value, |
151 | $frame->text->fromArticle->originalText ?? '' |
152 | ); |
153 | if ( $outdatedText ) { |
154 | $trackingCategories[] = $this->storyTrackingCategories::TC_OUTDATED_TEXT; |
155 | } |
156 | return [ |
157 | 'url' => $url, |
158 | 'filename' => $frame->image->filename, |
159 | 'focalRect' => $frame->image->focalRect ?? null, |
160 | 'fileNotFound' => $url === '', |
161 | 'text' => $frame->text->value, |
162 | 'textFromArticle' => $frame->text->fromArticle->originalText ?? '', |
163 | 'outdatedText' => $outdatedText, |
164 | 'attribution' => $this->getAttribution( $files, $frame->image->filename ), |
165 | ]; |
166 | }, $frames ) |
167 | ]; |
168 | $data[ 'trackingCategories' ] = array_unique( $trackingCategories ); |
169 | return $data; |
170 | } |
171 | |
172 | /** |
173 | * @param array $files |
174 | * @param string $filename |
175 | * @param int $size |
176 | * @return string |
177 | */ |
178 | private function getUrl( array $files, string $filename, int $size ): string { |
179 | /** @var File $file */ |
180 | $file = $files[ strtr( $filename, ' ', '_' ) ] ?? false; |
181 | if ( !$file ) { |
182 | return ''; |
183 | } |
184 | return $file->createThumb( $size ); |
185 | } |
186 | |
187 | /** |
188 | * @param array $files |
189 | * @param string $filename |
190 | * @return array Data structure with attribution information such author or license |
191 | */ |
192 | private function getAttribution( array $files, string $filename ): array { |
193 | /** @var File $file */ |
194 | $file = $files[ strtr( $filename, ' ', '_' ) ] ?? false; |
195 | if ( !$file ) { |
196 | return [ |
197 | 'author' => '', |
198 | 'license' => [], |
199 | 'url' => '', |
200 | ]; |
201 | } |
202 | |
203 | $formatMetadata = new FormatMetadata(); |
204 | $metadata = $formatMetadata->fetchExtendedMetadata( $file ); |
205 | $rawAuthor = $metadata[ 'Artist' ][ 'value' ] ?? ''; |
206 | $author = is_array( $rawAuthor ) ? reset( $rawAuthor ) : $rawAuthor; |
207 | $licenseString = $metadata[ 'LicenseShortName' ][ 'value' ] ?? ''; |
208 | $licenseTypes = [ 'CC', 'BY', 'SA', 'Fair', 'Public' ]; |
209 | $license = array_filter( $licenseTypes, static function ( $license ) use ( $licenseString ) { |
210 | return strpos( $licenseString, $license ) !== false; |
211 | } ); |
212 | |
213 | return [ |
214 | 'author' => strip_tags( $author ), |
215 | 'license' => $license, |
216 | 'url' => $file->getDescriptionUrl(), |
217 | ]; |
218 | } |
219 | |
220 | /** |
221 | * @param array $frame |
222 | * @return string html of the given frame data |
223 | */ |
224 | private function getNoJsFrameHtmlString( array $frame ): string { |
225 | $html = Html::element( |
226 | 'div', |
227 | [ 'class' => 'ext-wikistories-viewer-nojs-frame-text' ], |
228 | $frame[ 'text' ] |
229 | ); |
230 | |
231 | if ( $frame[ 'attribution' ][ 'license'] !== [] ) { |
232 | $html .= Html::rawElement( |
233 | 'div', |
234 | [ 'class' => 'ext-wikistories-viewer-nojs-frame-attribution' ], |
235 | $this->getAttributionHtmlString( $frame[ 'attribution' ] ) |
236 | ); |
237 | } |
238 | return $html; |
239 | } |
240 | |
241 | /** |
242 | * @param array $attribution |
243 | * @return string html of the given attribution data |
244 | */ |
245 | private function getAttributionHtmlString( array $attribution ): string { |
246 | $attributionString = Html::rawElement( |
247 | 'div', |
248 | [ 'class' => 'ext-wikistories-viewer-nojs-frame-attribution-info' ], |
249 | implode( '', [ |
250 | 'license' => $this->getLicensesHtmlString( $attribution[ 'license' ] ), |
251 | 'author' => Html::rawElement( |
252 | 'div', |
253 | [ |
254 | 'class' => 'ext-wikistories-viewer-nojs-frame-attribution-info-author', |
255 | 'title' => $attribution[ 'author' ], |
256 | ], |
257 | $attribution[ 'author' ] |
258 | ) |
259 | ] ) |
260 | ); |
261 | |
262 | $attributionString .= Html::rawElement( |
263 | 'div', |
264 | [ 'class' => 'ext-wikistories-viewer-nojs-frame-attribution-more-info' ], |
265 | Html::element( |
266 | 'a', |
267 | [ 'href' => $attribution[ 'url' ], |
268 | 'target' => '_blank', |
269 | 'class' => 'ext-wikistories-viewer-nojs-frame-attribution-more-info-link' |
270 | ], |
271 | '' |
272 | ) |
273 | ); |
274 | |
275 | return $attributionString; |
276 | } |
277 | |
278 | /** |
279 | * @param array $license |
280 | * @return string html of the given license data |
281 | */ |
282 | private function getLicensesHtmlString( $license ): string { |
283 | return implode( '', array_map( static function ( $licenseType ) { |
284 | return Html::rawElement( |
285 | 'div', |
286 | [ |
287 | 'class' => 'ext-wikistories-viewer-nojs-frame-attribution-info-license' . |
288 | ' ext-wikistories-viewer-nojs-frame-attribution-info-' . strtolower( $licenseType ) |
289 | ], |
290 | '' |
291 | ); |
292 | }, $license ) ); |
293 | } |
294 | } |