Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 57 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
| SpecialHideBanners | |
0.00% |
0 / 57 |
|
0.00% |
0 / 4 |
156 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| execute | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
56 | |||
| setHideCookie | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
6 | |||
| setP3P | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
| 1 | <?php |
| 2 | |
| 3 | use MediaWiki\Extension\CentralAuth\User\CentralAuthUser; |
| 4 | use MediaWiki\Registration\ExtensionRegistry; |
| 5 | use MediaWiki\SpecialPage\SpecialPage; |
| 6 | use MediaWiki\SpecialPage\UnlistedSpecialPage; |
| 7 | |
| 8 | /** |
| 9 | * Unlisted Special Page which sets a cookie for hiding banners across all languages of a project. |
| 10 | * This is typically used on donation thank-you pages so that users who have donated will no longer |
| 11 | * see fundrasing banners. |
| 12 | */ |
| 13 | class SpecialHideBanners extends UnlistedSpecialPage { |
| 14 | // Cache this blank response for a day or so (60 * 60 * 24 s.) |
| 15 | private const CACHE_EXPIRY = 86400; |
| 16 | // Hard-coded upper limit of 10 years for the user-provided …&duration=… parameter |
| 17 | private const MAX_COOKIE_DURATION = 10 * 365 * 86400; |
| 18 | private const P3P_SUBPAGE = 'P3P'; |
| 19 | |
| 20 | public function __construct() { |
| 21 | parent::__construct( 'HideBanners' ); |
| 22 | } |
| 23 | |
| 24 | /** @inheritDoc */ |
| 25 | public function execute( $par ) { |
| 26 | $config = $this->getConfig(); |
| 27 | // Handle /P3P subpage with explanation of invalid P3P header |
| 28 | if ( ( strval( $par ) === self::P3P_SUBPAGE ) && |
| 29 | !$config->get( 'CentralNoticeHideBannersP3P' ) |
| 30 | ) { |
| 31 | $this->setHeaders(); |
| 32 | $this->getOutput()->addWikiMsg( 'centralnotice-specialhidebanners-p3p' ); |
| 33 | return; |
| 34 | } |
| 35 | |
| 36 | $reason = $this->getRequest()->getText( 'reason', 'donate' ); |
| 37 | |
| 38 | // No duration parameter for a custom reason is not expected; we have a |
| 39 | // fallback value, but we log that this happened. |
| 40 | $duration = $this->getRequest()->getInt( 'duration' ); |
| 41 | if ( $duration <= 0 || $duration > self::MAX_COOKIE_DURATION ) { |
| 42 | $noticeCookieDurations = $config->get( 'NoticeCookieDurations' ); |
| 43 | if ( isset( $noticeCookieDurations[$reason] ) ) { |
| 44 | $duration = $noticeCookieDurations[$reason]; |
| 45 | } else { |
| 46 | $duration = $config->get( 'CentralNoticeFallbackHideCookieDuration' ); |
| 47 | wfLogWarning( 'Missing or invalid duration for hide cookie reason "' |
| 48 | . $reason . '".' ); |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | $category = $this->getRequest()->getText( 'category', 'fundraising' ); |
| 53 | $category = Banner::sanitizeRenderedCategory( $category ); |
| 54 | $this->setHideCookie( $category, $duration, $reason ); |
| 55 | $this->setP3P(); |
| 56 | |
| 57 | $this->getOutput()->disable(); |
| 58 | wfResetOutputBuffers(); |
| 59 | |
| 60 | header( 'Content-Type: image/png' ); |
| 61 | header( 'access-control-allow-origin: *' ); |
| 62 | |
| 63 | if ( !$this->getUser()->isRegistered() ) { |
| 64 | $expiry = self::CACHE_EXPIRY; |
| 65 | header( "Cache-Control: public, s-maxage={$expiry}, max-age=0" ); |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * Set the cookie for hiding fundraising banners. |
| 71 | * @param string $category |
| 72 | * @param int $duration |
| 73 | * @param string $reason |
| 74 | */ |
| 75 | private function setHideCookie( $category, $duration, $reason ) { |
| 76 | $created = time(); |
| 77 | $exp = $created + $duration; |
| 78 | $value = [ |
| 79 | 'v' => 1, |
| 80 | 'created' => $created, |
| 81 | 'reason' => $reason |
| 82 | ]; |
| 83 | |
| 84 | if ( ExtensionRegistry::getInstance()->isLoaded( 'CentralAuth' ) ) { |
| 85 | $cookieDomain = CentralAuthUser::getCookieDomain(); |
| 86 | } else { |
| 87 | $cookieDomain = $this->getConfig()->get( 'NoticeCookieDomain' ); |
| 88 | } |
| 89 | setcookie( |
| 90 | "centralnotice_hide_{$category}", |
| 91 | json_encode( $value ), |
| 92 | [ |
| 93 | 'expires' => $exp, |
| 94 | 'path' => '/', |
| 95 | 'domain' => $cookieDomain, |
| 96 | 'secure' => true, |
| 97 | 'httponly' => false, |
| 98 | 'samesite' => 'None', |
| 99 | ] |
| 100 | ); |
| 101 | } |
| 102 | |
| 103 | /** |
| 104 | * Set an invalid P3P policy header to make IE accept third-party hide cookies. |
| 105 | */ |
| 106 | private function setP3P() { |
| 107 | $centralNoticeHideBannersP3P = $this->getConfig()->get( 'CentralNoticeHideBannersP3P' ); |
| 108 | |
| 109 | if ( !$centralNoticeHideBannersP3P ) { |
| 110 | $url = SpecialPage::getTitleFor( |
| 111 | 'HideBanners', self::P3P_SUBPAGE ) |
| 112 | ->getCanonicalURL(); |
| 113 | |
| 114 | $p3p = "CP=\"This is not a P3P policy! See $url for more info.\""; |
| 115 | |
| 116 | } else { |
| 117 | $p3p = $centralNoticeHideBannersP3P; |
| 118 | } |
| 119 | |
| 120 | header( "P3P: $p3p", true ); |
| 121 | } |
| 122 | } |