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