Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 117 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
ExtMobileFrontend | |
0.00% |
0 / 117 |
|
0.00% |
0 / 6 |
1406 | |
0.00% |
0 / 1 |
blankUserPageHTML | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
42 | |||
domParseWithContentProvider | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
domParseMobile | |
0.00% |
0 / 58 |
|
0.00% |
0 / 1 |
240 | |||
buildPageUserObject | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
30 | |||
getUserPageContent | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
30 | |||
getWikibaseDescription | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | |
3 | use MediaWiki\Context\IContextSource; |
4 | use MediaWiki\Html\TemplateParser; |
5 | use MediaWiki\MediaWikiServices; |
6 | use MediaWiki\Output\OutputPage; |
7 | use MediaWiki\Registration\ExtensionRegistry; |
8 | use MediaWiki\Title\Title; |
9 | use MediaWiki\User\User; |
10 | use MobileFrontend\Api\ApiParseExtender; |
11 | use MobileFrontend\ContentProviders\IContentProvider; |
12 | use MobileFrontend\Features\FeaturesManager; |
13 | use MobileFrontend\Hooks\HookRunner; |
14 | use MobileFrontend\Transforms\LazyImageTransform; |
15 | use MobileFrontend\Transforms\MakeSectionsTransform; |
16 | use MobileFrontend\Transforms\MoveLeadParagraphTransform; |
17 | use MobileFrontend\Transforms\NativeLazyImageTransform; |
18 | use MobileFrontend\Transforms\RemovableClassesTransform; |
19 | use Wikibase\Client\WikibaseClient; |
20 | use Wikibase\DataModel\Entity\ItemId; |
21 | use Wikibase\DataModel\Services\Lookup\TermLookupException; |
22 | use Wikimedia\IPUtils; |
23 | |
24 | /** |
25 | * Implements additional functions to use in MobileFrontend |
26 | */ |
27 | class ExtMobileFrontend { |
28 | /** |
29 | * Provide alternative HTML for a user page which has not been created. |
30 | * Let the user know about it with pretty graphics and different texts depending |
31 | * on whether the user is the owner of the page or not. |
32 | * @internal Only for use inside MobileFrontend. |
33 | * @param OutputPage $out |
34 | * @param Title $title |
35 | * @return string that is empty if the transform does not apply. |
36 | */ |
37 | public static function blankUserPageHTML( OutputPage $out, Title $title ) { |
38 | $pageUser = self::buildPageUserObject( $title ); |
39 | $isHidden = $pageUser && $pageUser->isHidden(); |
40 | $canViewHidden = !$isHidden || $out->getAuthority()->isAllowed( 'hideuser' ); |
41 | |
42 | $out->addModuleStyles( [ |
43 | 'mobile.userpage.styles', 'mobile.userpage.images' |
44 | ] ); |
45 | |
46 | if ( $pageUser && !$title->exists() && $canViewHidden ) { |
47 | return self::getUserPageContent( |
48 | $out, $pageUser, $title ); |
49 | } else { |
50 | return ''; |
51 | } |
52 | } |
53 | |
54 | /** |
55 | * Obtains content using the given content provider and routes it to the mobile formatter |
56 | * if required. |
57 | * |
58 | * @param IContentProvider $provider |
59 | * @param OutputPage $out |
60 | * @param bool $mobileFormatHtml whether content should be run through the MobileFormatter |
61 | * |
62 | * @return string |
63 | */ |
64 | public static function domParseWithContentProvider( |
65 | IContentProvider $provider, |
66 | OutputPage $out, |
67 | $mobileFormatHtml = true |
68 | ) { |
69 | $html = $provider->getHTML(); |
70 | |
71 | // If we're not running the formatter we can exit earlier |
72 | if ( !$mobileFormatHtml ) { |
73 | return $html; |
74 | } else { |
75 | return self::domParseMobile( $out, $html ); |
76 | } |
77 | } |
78 | |
79 | /** |
80 | * Transforms content to be mobile friendly version. |
81 | * Filters out various elements and runs the MobileFormatter. |
82 | * |
83 | * @param OutputPage $out |
84 | * @param string $html to render. |
85 | * |
86 | * @return string |
87 | */ |
88 | public static function domParseMobile( OutputPage $out, $html = '' ) { |
89 | $services = MediaWikiServices::getInstance(); |
90 | /** @var FeaturesManager $featuresManager */ |
91 | $featuresManager = $services->getService( 'MobileFrontend.FeaturesManager' ); |
92 | /** @var MobileContext $context */ |
93 | $context = $services->getService( 'MobileFrontend.Context' ); |
94 | $config = $services->getService( 'MobileFrontend.Config' ); |
95 | |
96 | $title = $out->getTitle(); |
97 | $ns = $title->getNamespace(); |
98 | $action = $context->getRequest()->getText( 'action', 'view' ); |
99 | $isView = $action === 'view' || ApiParseExtender::isParseAction( $action ); |
100 | |
101 | $shouldUseParsoid = false; |
102 | if ( ExtensionRegistry::getInstance()->isLoaded( 'ParserMigration' ) ) { |
103 | $oracle = MediaWikiServices::getInstance()->getService( 'ParserMigration.Oracle' ); |
104 | $shouldUseParsoid = |
105 | $oracle->shouldUseParsoid( $context->getUser(), $context->getRequest(), $title ); |
106 | } |
107 | |
108 | $enableSections = ( |
109 | // Don't collapse sections e.g. on JS pages |
110 | $title->canExist() |
111 | && $title->getContentModel() == CONTENT_MODEL_WIKITEXT |
112 | // And not in certain namespaces |
113 | && !in_array( $ns, $config->get( 'MFNamespacesWithoutCollapsibleSections' ) ) |
114 | // And not when what's shown is not actually article text |
115 | && $isView |
116 | && !$shouldUseParsoid |
117 | ); |
118 | |
119 | // https://phabricator.wikimedia.org/T232690 |
120 | if ( !MobileFormatter::canApply( $html, $config->get( 'MFMobileFormatterOptions' ) ) ) { |
121 | // In future we might want to prepend a message feeding |
122 | // back to the user that the page is not mobile friendly. |
123 | return $html; |
124 | } |
125 | |
126 | $formatter = new MobileFormatter( $html ); |
127 | |
128 | $hookRunner = new HookRunner( $services->getHookContainer() ); |
129 | $hookRunner->onMobileFrontendBeforeDOM( $context, $formatter ); |
130 | |
131 | $shouldLazyTransformImages = $featuresManager->isFeatureAvailableForCurrentUser( 'MFLazyLoadImages' ); |
132 | $leadParagraphEnabled = in_array( $ns, $config->get( 'MFNamespacesWithLeadParagraphs' ) ); |
133 | $showFirstParagraphBeforeInfobox = $leadParagraphEnabled && |
134 | $featuresManager->isFeatureAvailableForCurrentUser( 'MFShowFirstParagraphBeforeInfobox' ); |
135 | |
136 | $transforms = []; |
137 | // Remove specified content in content namespaces |
138 | if ( in_array( $title->getNamespace(), $config->get( 'ContentNamespaces' ), true ) ) { |
139 | $mfRemovableClasses = $config->get( 'MFRemovableClasses' ); |
140 | $removableClasses = $mfRemovableClasses['base']; |
141 | if ( $context->isBetaGroupMember() ) { |
142 | $removableClasses = array_unique( |
143 | array_merge( $removableClasses, $mfRemovableClasses['beta'] ) |
144 | ); |
145 | } |
146 | |
147 | $transforms[] = new RemovableClassesTransform( $removableClasses ); |
148 | } |
149 | |
150 | if ( $enableSections ) { |
151 | $options = $config->get( 'MFMobileFormatterOptions' ); |
152 | $topHeadingTags = $options['headings']; |
153 | |
154 | $transforms[] = new MakeSectionsTransform( |
155 | $topHeadingTags, |
156 | true |
157 | ); |
158 | } |
159 | |
160 | if ( $shouldLazyTransformImages ) { |
161 | if ( $shouldUseParsoid ) { |
162 | $transforms[] = new NativeLazyImageTransform(); |
163 | } else { |
164 | $transforms[] = new LazyImageTransform( $config->get( 'MFLazyLoadSkipSmallImages' ) ); |
165 | } |
166 | } |
167 | |
168 | if ( $showFirstParagraphBeforeInfobox ) { |
169 | $transforms[] = new MoveLeadParagraphTransform( $title, $title->getLatestRevID() ); |
170 | } |
171 | |
172 | $start = microtime( true ); |
173 | $formatter->applyTransforms( $transforms ); |
174 | $end = microtime( true ); |
175 | $report = sprintf( "MobileFormatter took %.3f seconds", $end - $start ); |
176 | |
177 | return $formatter->getText() . "\n<!-- $report -->"; |
178 | } |
179 | |
180 | /** |
181 | * Return new User object based on username or IP address. |
182 | * @param Title $title |
183 | * @return User|null |
184 | */ |
185 | private static function buildPageUserObject( Title $title ) { |
186 | $titleText = $title->getText(); |
187 | |
188 | $usernameUtils = MediaWikiServices::getInstance()->getUserNameUtils(); |
189 | if ( $usernameUtils->isIP( $titleText ) || IPUtils::isIPv6( $titleText ) ) { |
190 | return User::newFromAnyId( null, $titleText, null ); |
191 | } |
192 | |
193 | $user = User::newFromName( $titleText ); |
194 | if ( $user && $user->isRegistered() ) { |
195 | return $user; |
196 | } |
197 | |
198 | return null; |
199 | } |
200 | |
201 | /** |
202 | * Generate user page content for non-existent user pages |
203 | * |
204 | * @param IContextSource $output |
205 | * @param User $pageUser owner of the user page |
206 | * @param Title $title |
207 | * @return string |
208 | */ |
209 | protected static function getUserPageContent( IContextSource $output, |
210 | User $pageUser, Title $title |
211 | ) { |
212 | /** @var MobileContext $context */ |
213 | $context = MediaWikiServices::getInstance()->getService( 'MobileFrontend.Context' ); |
214 | $pageUsername = $pageUser->getName(); |
215 | // Is the current user viewing their own page? |
216 | $isCurrentUser = $output->getUser()->getName() === $pageUsername; |
217 | |
218 | $data = [ |
219 | 'userImageClass' => 'userpage-image-placeholder', |
220 | ]; |
221 | $data['ctaHeading'] = $isCurrentUser ? |
222 | $context->msg( 'mobile-frontend-user-page-no-owner-page-yet' )->text() : |
223 | $context->msg( 'mobile-frontend-user-page-no-page-yet', $pageUsername )->parse(); |
224 | $data['ctaDescription'] = $isCurrentUser ? |
225 | $context->msg( |
226 | 'mobile-frontend-user-page-describe-yourself', |
227 | $context->msg( 'mobile-frontend-user-page-describe-yourself-editors' )->text() |
228 | )->text() : |
229 | $context->msg( 'mobile-frontend-user-page-desired-action', $pageUsername )->parse(); |
230 | $data['createPageLinkLabel'] = $isCurrentUser ? |
231 | $context->msg( 'mobile-frontend-user-page-create-owner-page-link-label' )->text() : |
232 | $context->msg( |
233 | 'mobile-frontend-user-page-create-user-page-link-label', |
234 | $pageUser->getUserPage()->getBaseTitle() |
235 | )->parse(); |
236 | // Mobile editor has trouble when section is not specified. |
237 | // It doesn't matter here since the page doesn't exist. |
238 | $data['editUrl'] = $title->getLinkURL( [ 'action' => 'edit', 'section' => 0 ] ); |
239 | $data['editSection'] = 0; |
240 | $data['createPageLinkAdditionalClasses'] = $isCurrentUser ? |
241 | 'cdx-button cdx-button--action-progressive cdx-button--weight-primary' : ''; |
242 | |
243 | $templateParser = new TemplateParser( __DIR__ . '/templates' ); |
244 | return $templateParser->processTemplate( 'UserPageCta', $data ); |
245 | } |
246 | |
247 | /** |
248 | * Returns a short description of a page from Wikidata |
249 | * |
250 | * @param string $item Wikibase id of the page |
251 | * @return string|null |
252 | */ |
253 | public static function getWikibaseDescription( $item ) { |
254 | if ( !ExtensionRegistry::getInstance()->isLoaded( 'WikibaseClient' ) ) { |
255 | return null; |
256 | } |
257 | |
258 | $contLang = MediaWikiServices::getInstance()->getContentLanguage(); |
259 | $termLookup = WikibaseClient::getTermLookup(); |
260 | try { |
261 | $itemId = new ItemId( $item ); |
262 | } catch ( InvalidArgumentException $exception ) { |
263 | return null; |
264 | } |
265 | |
266 | try { |
267 | return $termLookup->getDescription( $itemId, $contLang->getCode() ); |
268 | } catch ( TermLookupException $exception ) { |
269 | return null; |
270 | } |
271 | } |
272 | } |