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