Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 123
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
FeaturedFeedChannel
0.00% covered (danger)
0.00%
0 / 123
0.00% covered (danger)
0.00%
0 / 10
1122
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 fromArray
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 toArray
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
 msg
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isOK
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getLanguage
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 init
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
 getFeedItems
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
56
 getFeedItem
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
42
 getURL
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace MediaWiki\Extension\FeaturedFeeds;
4
5use MediaWiki\Content\TextContent;
6use MediaWiki\Language\Language;
7use MediaWiki\MediaWikiServices;
8use MediaWiki\Message\Message;
9use MediaWiki\Parser\Parser;
10use MediaWiki\Parser\ParserOptions;
11use MediaWiki\Revision\SlotRecord;
12use MediaWiki\SpecialPage\SpecialPage;
13use MediaWiki\Title\Title;
14use MediaWiki\Utils\MWTimestamp;
15use UnexpectedValueException;
16
17class FeaturedFeedChannel {
18    /**
19     * Class version, increment it when changing class internals.
20     */
21    public const VERSION = 2;
22
23    /**
24     * @var Parser
25     */
26    private static $parser;
27    /** @var string */
28    private $languageCode;
29
30    /** @var string */
31    private $name;
32    /** @var array */
33    private $options;
34    /**
35     * @var FeaturedFeedItem[]|false
36     */
37    private $items = false;
38    /** @var string|false */
39    private $page = false;
40    /** @var string|false */
41    private $entryName = false;
42    /** @var Title|false */
43    private $titleForParse = false;
44
45    /** @var string|false */
46    public $title = false;
47    /** @var string|false */
48    public $shortTitle = false;
49    /** @var string|false */
50    public $description = false;
51
52    /**
53     * @param string $name
54     * @param array $options
55     * @param string $languageCode
56     */
57    public function __construct( $name, $options, $languageCode ) {
58        if ( !self::$parser ) {
59            self::$parser = MediaWikiServices::getInstance()->getParserFactory()->create();
60        }
61
62        $this->name = $name;
63        $this->options = $options;
64        if ( $options['inUserLanguage'] ) {
65            $this->languageCode = $languageCode;
66        } else {
67            $contLang = MediaWikiServices::getInstance()->getContentLanguage();
68            $this->languageCode = $contLang->getCode();
69        }
70    }
71
72    public static function fromArray( array $array ) {
73        $channel = new FeaturedFeedChannel(
74            $array['name'],
75            $array['options'],
76            $array['lang']
77        );
78
79        if ( $array['items'] !== false ) {
80            $channel->items = [];
81            foreach ( $array['items'] as $item ) {
82                $channel->items[] = FeaturedFeedItem::fromArray( $item );
83            }
84        }
85
86        $channel->page = $array['page'];
87        $channel->entryName = $array['entryName'];
88        $channel->titleForParse = $array['titleForParse'];
89        $channel->title = $array['title'];
90        $channel->shortTitle = $array['shortTitle'];
91        $channel->description = $array['description'];
92
93        return $channel;
94    }
95
96    public function toArray(): array {
97        $items = false;
98        if ( $this->items !== false ) {
99            $items = [];
100
101            foreach ( $this->items as $item ) {
102                $items[] = $item->toArray();
103            }
104        }
105
106        return [
107            'name' => $this->name,
108            'options' => $this->options,
109            'lang' => $this->languageCode,
110            'items' => $items,
111            'page' => $this->page,
112            'entryName' => $this->entryName,
113            'titleForParse' => $this->titleForParse,
114            'title' => $this->title,
115            'shortTitle' => $this->shortTitle,
116            'description' => $this->description,
117        ];
118    }
119
120    /**
121     * @param string $key
122     * @return Message
123     */
124    private function msg( $key ) {
125        return wfMessage( $key )->inLanguage( $this->languageCode );
126    }
127
128    /**
129     * @return bool
130     */
131    public function isOK() {
132        $this->init();
133        return $this->page !== false;
134    }
135
136    /**
137     * Returns language used by the feed
138     * @return Language
139     */
140    public function getLanguage() {
141        return MediaWikiServices::getInstance()->getLanguageFactory()
142            ->getLanguage( $this->languageCode );
143    }
144
145    public function init() {
146        global $wgLanguageCode;
147        if ( $this->title !== false ) {
148            return;
149        }
150        $this->title = $this->msg( $this->options['title'] )->text();
151        $this->shortTitle = $this->msg( $this->options['short-title'] )->text();
152        $this->description = $this->msg( $this->options['description'] )->text();
153        $pageMsg = $this->msg( $this->options['page'] )->params( $this->languageCode );
154        if ( $pageMsg->isDisabled() ) {
155            // fall back manually, messages can be existent but empty
156            if ( $this->languageCode != $wgLanguageCode ) {
157                $pageMsg = wfMessage( $this->options['page'] )
158                    ->params( $this->languageCode )
159                    ->inContentLanguage();
160            }
161        }
162        if ( $pageMsg->isDisabled() ) {
163            return;
164        }
165        $this->page = $pageMsg->plain();
166        $this->page = str_replace( '$LANGUAGE', $this->languageCode, $this->page );
167        $this->entryName = $this->msg( $this->options['entryName'] )->plain();
168    }
169
170    /**
171     * @return FeaturedFeedItem[]
172     */
173    public function getFeedItems() {
174        $this->init();
175        if ( $this->items === false ) {
176            $this->items = [];
177            switch ( $this->options['frequency'] ) {
178                case 'daily':
179                    $ratio = 1;
180                    $baseTime = FeaturedFeeds::todaysStart();
181                    break;
182                case 'weekly':
183                    $ratio = 7;
184                    $baseTime = FeaturedFeeds::startOfThisWeek();
185                    break;
186                default:
187                    throw new UnexpectedValueException( "'{$this->options['frequency']}' is not a valid frequency" );
188            }
189            for ( $i = 1 - $this->options['limit']; $i <= 0; $i++ ) {
190                $timestamp = $baseTime + $i * $ratio * 24 * 3600;
191                $item = $this->getFeedItem( $timestamp );
192                if ( $item ) {
193                    $this->items[] = $item;
194                }
195            }
196        }
197        return $this->items;
198    }
199
200    /**
201     *
202     * @param int $date
203     * @return FeaturedFeedItem|false
204     */
205    public function getFeedItem( $date ) {
206        $ts = new MWTimestamp( $date );
207        $timestamp = $ts->getTimestamp( TS_MW );
208        $parserOptions = ParserOptions::newFromAnon();
209        $parserOptions->setTimestamp( $timestamp );
210        $parserOptions->setUserLang( $this->getLanguage() );
211
212        if ( $this->titleForParse === false ) {
213            // parsing with such title makes stuff like {{CURRENTMONTH}} localised
214            $this->titleForParse = Title::newFromText( 'MediaWiki:Dummy/' . $this->languageCode );
215        }
216
217        $titleText = self::$parser->transformMsg(
218            $this->page, $parserOptions, $this->titleForParse );
219        $title = Title::newFromText( $titleText );
220        if ( !$title ) {
221            return false;
222        }
223        $rev = MediaWikiServices::getInstance()->getRevisionLookup()
224            ->getRevisionByTitle( $title );
225        if ( !$rev ) {
226            // Page does not exist
227            return false;
228        }
229
230        $content = $rev->getContent( SlotRecord::MAIN );
231        $text = ( $content instanceof TextContent ) ? $content->getText() : null;
232
233        if ( !$text ) {
234            return false;
235        }
236        $text = self::$parser->parse( $text, $title, $parserOptions )->getText( [
237            'enableSectionEditLinks' => false,
238        ] );
239        $url = SpecialPage::getTitleFor( 'FeedItem',
240            $this->name . '/' . $timestamp . '/' . $this->languageCode
241        )->getFullURL();
242
243        return new FeaturedFeedItem(
244            self::$parser->transformMsg( $this->entryName, $parserOptions, $this->titleForParse ),
245            wfExpandUrl( $url ),
246            $text,
247            $timestamp
248        );
249    }
250
251    /**
252     * Returns a URL to the feed
253     *
254     * @param string $format Feed format, 'rss' or 'atom'
255     * @return string
256     */
257    public function getURL( $format ) {
258        $contLang = MediaWikiServices::getInstance()->getContentLanguage();
259
260        $options = [
261            'action' => 'featuredfeed',
262            'feed' => $this->name,
263            'feedformat' => $format,
264        ];
265        if ( $this->options['inUserLanguage'] && $this->languageCode != $contLang->getCode() ) {
266            $options['language'] = $this->languageCode;
267        }
268        return wfScript( 'api' ) . '?' . wfArrayToCgi( $options );
269    }
270}