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