Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 81
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
FundraiserLandingPage
0.00% covered (danger)
0.00%
0 / 81
0.00% covered (danger)
0.00%
0 / 8
506
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 1
72
 getRobotPolicy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 fundraiserLandingPageMakeSafe
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 fundraiserLandingPageSwitchLanguage
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 fundraiserLandingPageSwitchCountry
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 isFundraiseUp
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFundraiseUpJavascript
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\FundraiserLandingPage\Specials;
4
5/*
6 * SpecialPage definition for FundraiserLandingPage.  Extending UnlistedSpecialPage
7 * since this page does not need to listed in Special:SpecialPages.
8 *
9 * @author Peter Gehres <pgehres@wikimedia.org>
10 */
11
12use MediaWiki\Parser\Parser;
13use MediaWiki\SpecialPage\UnlistedSpecialPage;
14use MediaWiki\Title\Title;
15
16class FundraiserLandingPage extends UnlistedSpecialPage {
17    public function __construct() {
18        parent::__construct( 'FundraiserLandingPage' );
19    }
20
21    /**
22     * @param string $par
23     */
24    public function execute( $par ) {
25        $config = $this->getConfig();
26
27        $out = $this->getOutput();
28        $request = $this->getRequest();
29
30        // Set squid age
31        $out->setCdnMaxage( $config->get( 'FundraiserLandingPageMaxAge' ) );
32
33        if ( $this->isFundraiseUp() ) {
34            $out->addScript( $this->getFundraiseUpJavascript() );
35        }
36        $this->setHeaders();
37
38        // set the page title to something useful
39        $titleMsg = $this->msg( 'donate_interface-make-your-donation' );
40        if ( !is_callable( [ $out, 'setPageTitleMsg' ] ) ) {
41            // Backward compatibility with MW < 1.41
42            $out->setPageTitle( $titleMsg );
43        } else {
44            // MW >= 1.41
45            $out->setPageTitleMsg( $titleMsg );
46        }
47
48        // and add a <meta name="description"> tag to give search engines a useful blurb
49        $out->addMeta( 'description', $this->msg( 'fundraiserlandingpage-meta-description' ) );
50
51        // Instruct browsers to pre-fetch the DNS for payments-wiki to speed up loading the next form
52        $out->addHeadItem(
53            'payments-dns-prefetch',
54            '<link rel="dns-prefetch" href="' . $this->getConfig()->get( 'FundraiserLandingPagePaymentsHost' ) . '" />'
55        );
56
57        // clear output variable to be safe
58        $output = '';
59
60        $fundraiserLPDefaults = $config->get( 'FundraiserLPDefaults' );
61        // begin generating the template call
62        $template = self::fundraiserLandingPageMakeSafe(
63            $request->getText( 'template', $fundraiserLPDefaults[ 'template' ] )
64        );
65        $output .= "{{ $template\n";
66
67        // get the required variables (except template and country) to use for the landing page
68        $requiredParams = [
69            'appeal',
70            'appeal-template',
71            'form-template',
72            'form-countryspecific'
73        ];
74        foreach ( $requiredParams as $requiredParam ) {
75            $param = self::fundraiserLandingPageMakeSafe(
76                $request->getText( $requiredParam, $fundraiserLPDefaults[$requiredParam] )
77            );
78            // Add them to the template call
79            $output .= "$requiredParam = $param\n";
80        }
81
82        // get the country code
83        $country = $request->getVal( 'country' );
84        // If country still isn't set, set it to the default
85        if ( !$country ) {
86            $country = $fundraiserLPDefaults[ 'country' ];
87        }
88        $country = self::fundraiserLandingPageMakeSafe( $country );
89        $output .= "| country = $country\n";
90
91        // @phan-suppress-next-line PhanUselessBinaryAddRight
92        $excludeKeys = $requiredParams + [ 'template', 'country', 'title' ];
93
94        // if there are any other parameters passed in the querystring, add them
95        if ( $request->getQueryValuesOnly() ) {
96            foreach ( $request->getQueryValuesOnly() as $k_unsafe => $v_unsafe ) {
97                // skip the required variables
98                if ( in_array( $k_unsafe, $excludeKeys ) ) {
99                    continue;
100                }
101                // get the variable's name and value
102                $key = self::fundraiserLandingPageMakeSafe( $k_unsafe );
103                $val = self::fundraiserLandingPageMakeSafe( $v_unsafe );
104                // print to the template in wiki-syntax
105                $output .= "$key = $val\n";
106            }
107        }
108
109        // close the template call
110        $output .= "}}";
111
112        // Hijack parser internals to workaround T156184.  This should be safe
113        // since we've sanitized all params.
114        $parserOptions = $out->parserOptions();
115        $parserOptions->setAllowUnsafeRawHtml( true );
116
117        // print the output to the page
118        $out->addWikiTextAsInterface( $output );
119    }
120
121    /**
122     * Mark the page as allowed for search engine indexing
123     * (default for SpecialPages is noindex)
124     *
125     * @return string
126     */
127    protected function getRobotPolicy() {
128        return 'index,nofollow';
129    }
130
131    /**
132     * This function limits the possible characters passed as template keys and
133     * values to letters, numbers, hyphens, underscores, and the forward slash.
134     * The function also performs standard escaping of the passed values.
135     *
136     * @param mixed $value The unsafe value to escape and check for invalid characters
137     * @param string $default A default value to return if when making the $string safe no
138     *                 results are returned.
139     *
140     * @return string A string matching the regex or an empty string
141     * @suppress SecurityCheck-DoubleEscaped double escaping is on purpose per the inline
142     *                                       comment
143     */
144    private static function fundraiserLandingPageMakeSafe( $value, $default = '' ) {
145        if ( $default != '' ) {
146            $default = self::fundraiserLandingPageMakeSafe( $default );
147        }
148
149        if ( !is_string( $value ) ) {
150            // In case someone has passed in an array as a request parameter
151            return $default;
152        }
153
154        $num = preg_match( '/^([-a-zA-Z0-9_\/]+)$/', $value, $matches );
155
156        if ( $num == 1 ) {
157            # theoretically this is overkill, but better safe than sorry
158            return wfEscapeWikiText( htmlspecialchars( $matches[1] ) );
159        }
160        return $default;
161    }
162
163    /**
164     * Attempts to load a language localized template. Precedence is Language,
165     * Country, Root. It is assumed that all parts of the title are separated
166     * with '/'.
167     *
168     * @param Parser $parser Reference to the WM parser object
169     * @param string $page The template page root to load
170     * @param string $language The language to attempt to localize onto
171     * @param string $country The country to attempt to localize onto
172     *
173     * @return array The wikitext template
174     */
175    public static function fundraiserLandingPageSwitchLanguage( $parser, $page = '',
176        $language = 'en', $country = 'XX'
177    ) {
178        $page = self::fundraiserLandingPageMakeSafe( $page );
179        $country = self::fundraiserLandingPageMakeSafe( $country, 'XX' );
180        $language = self::fundraiserLandingPageMakeSafe( $language, 'en' );
181
182        if ( Title::newFromText( "Template:$page/$language/$country" )->exists() ) {
183            $tpltext = "$page/$language/$country";
184        } elseif ( Title::newFromText( "Template:$page/$language" )->exists() ) {
185            $tpltext = "$page/$language";
186        } else {
187            // If all the variants don't exist, then merely return the base. If
188            // something really screwy happened and the base doesn't exist either
189            // we will let the WM error handler sort it out.
190
191            $tpltext = $page;
192        }
193
194        return [ "{{Template:$tpltext}}", 'noparse' => false ];
195    }
196
197    /**
198     * Attempts to load a language localized template. Precedence is Country,
199     * Language, Root. It is assumed that all parts of the title are separated
200     * with '/'.
201     *
202     * @param Parser $parser Reference to the WM parser object
203     * @param string $page The template page root to load
204     * @param string $country The country to attempt to localize onto
205     * @param string $language The language to attempt to localize onto
206     *
207     * @return array The wikitext template
208     */
209    public static function fundraiserLandingPageSwitchCountry( $parser, $page = '', $country = 'XX',
210        $language = 'en'
211    ) {
212        $page = self::fundraiserLandingPageMakeSafe( $page );
213        $country = self::fundraiserLandingPageMakeSafe( $country, 'XX' );
214        $language = self::fundraiserLandingPageMakeSafe( $language, 'en' );
215
216        if ( Title::newFromText( "Template:$page/$country/$language" )->exists() ) {
217            $tpltext = "$page/$country/$language";
218
219        } elseif ( Title::newFromText( "Template:$page/$country" )->exists() ) {
220            $tpltext = "$page/$country";
221
222        } else {
223            // If all the variants don't exist, then merely return the base. If
224            // something really screwy happened and the base doesn't exist either
225            // we will let the WM error handler sort it out.
226
227            $tpltext = $page;
228        }
229
230        return [ "{{Template:$tpltext}}", 'noparse' => false ];
231    }
232
233    /**
234     * Check if template is fundraiseup
235     * @return bool
236     */
237    private function isFundraiseUp() {
238        return $this->getRequest()->getVal( 'fundraiseupScript' ) === "1";
239    }
240
241    /**
242     * Javascript to add fundraiseup to DonateWiki page
243     *
244     * @return string
245     */
246    private function getFundraiseUpJavascript() {
247        return <<<EOF
248            <script>(function(w,d,s,n,a){if(!w[n]){var l='call,catch,on,once,set,then,track'
249            .split(','),i,o=function(n){return'function'==typeof n?o.l.push([arguments])&&o
250            :function(){return o.l.push([n,arguments])&&o}},t=d.getElementsByTagName(s)[0],
251            j=d.createElement(s);j.async=!0;j.src='https://cdn.fundraiseup.com/widget/'+a;
252            t.parentNode.insertBefore(j,t);o.s=Date.now();o.v=4;o.h=w.location.href;o.l=[];
253            for(i=0;i<7;i++)o[l[i]]=o(l[i]);w[n]=o}
254            })(window,document,'script','FundraiseUp','AVLMPSRU');
255            </script>
256EOF;
257    }
258}