MediaWiki master
FeedUtils.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Feed;
25
36use UtfNormal;
37
43class FeedUtils {
44
54 public static function checkFeedOutput( $type, $output = null ) {
55 $feed = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::Feed );
56 $feedClasses = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::FeedClasses );
57 if ( $output === null ) {
58 // Todo update GoogleNewsSitemap and deprecate
59 global $wgOut;
60 $output = $wgOut;
61 }
62
63 if ( !$feed ) {
64 $output->addWikiMsg( 'feed-unavailable' );
65 return false;
66 }
67
68 if ( !isset( $feedClasses[$type] ) ) {
69 $output->addWikiMsg( 'feed-invalid' );
70 return false;
71 }
72
73 return true;
74 }
75
85 public static function formatDiff( $row, $formattedComment = null ) {
86 $titleObj = Title::makeTitle( $row->rc_namespace, $row->rc_title );
87 $timestamp = wfTimestamp( TS_MW, $row->rc_timestamp );
88 $actiontext = '';
89 if ( $row->rc_type == RC_LOG ) {
90 $rcRow = (array)$row; // newFromRow() only accepts arrays for RC rows
91 $actiontext = MediaWikiServices::getInstance()->getLogFormatterFactory()
92 ->newFromRow( $rcRow )->getActionText();
93 }
94 if ( $row->rc_deleted & RevisionRecord::DELETED_COMMENT ) {
95 $formattedComment = wfMessage( 'rev-deleted-comment' )->escaped();
96 } elseif ( $formattedComment === null ) {
98 $formattedComment = $services->getCommentFormatter()->format(
99 $services->getCommentStore()->getComment( 'rc_comment', $row )->text );
100 }
101 return self::formatDiffRow2( $titleObj,
102 $row->rc_last_oldid, $row->rc_this_oldid,
103 $timestamp,
104 $formattedComment,
105 $actiontext
106 );
107 }
108
122 public static function formatDiffRow( $title, $oldid, $newid, $timestamp,
123 $comment, $actiontext = ''
124 ) {
125 $formattedComment = MediaWikiServices::getInstance()->getCommentFormatter()
126 ->format( $comment );
127 return self::formatDiffRow2( $title, $oldid, $newid, $timestamp,
128 $formattedComment, $actiontext );
129 }
130
143 public static function formatDiffRow2(
144 $title, $oldid, $newid, $timestamp, $formattedComment, $actiontext = ''
145 ) {
146 $feedDiffCutoff = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::FeedDiffCutoff );
147
148 // log entries
149 $unwrappedText = implode(
150 ' ',
151 array_filter( [ $actiontext, $formattedComment ] )
152 );
153 $completeText = Html::rawElement( 'p', [], $unwrappedText ) . "\n";
154
155 // NOTE: Check permissions for anonymous users, not current user.
156 // No "privileged" version should end up in the cache.
157 // Most feed readers will not log in anyway.
158 $services = MediaWikiServices::getInstance();
159 $anon = $services->getUserFactory()->newAnonymous();
160 $permManager = $services->getPermissionManager();
161 $userCan = $permManager->userCan(
162 'read',
163 $anon,
164 $title
165 );
166
167 // Can't diff special pages, unreadable pages or pages with no new revision
168 // to compare against: just return the text.
169 if ( $title->getNamespace() < 0 || !$userCan || !$newid ) {
170 return $completeText;
171 }
172
173 $revLookup = $services->getRevisionLookup();
174 $contentHandlerFactory = $services->getContentHandlerFactory();
175 if ( $oldid ) {
176 $diffText = '';
177 // Don't bother generating the diff if we won't be able to show it
178 if ( $feedDiffCutoff > 0 ) {
179 $revRecord = $revLookup->getRevisionById( $oldid );
180
181 if ( !$revRecord ) {
182 $diffText = false;
183 } else {
184 $context = new DerivativeContext( RequestContext::getMain() );
185 $context->setTitle( $title );
186
187 $model = $revRecord->getSlot(
188 SlotRecord::MAIN,
189 RevisionRecord::RAW
190 )->getModel();
191 $contentHandler = $contentHandlerFactory->getContentHandler( $model );
192 $de = $contentHandler->createDifferenceEngine( $context, $oldid, $newid );
193 $lang = $context->getLanguage();
194 $user = $context->getUser();
195 $diffText = $de->getDiff(
196 $context->msg( 'previousrevision' )->text(), // hack
197 $context->msg( 'revisionasof',
198 $lang->userTimeAndDate( $timestamp, $user ),
199 $lang->userDate( $timestamp, $user ),
200 $lang->userTime( $timestamp, $user ) )->text() );
201 }
202 }
203
204 if ( $feedDiffCutoff <= 0 || ( strlen( $diffText ) > $feedDiffCutoff ) ) {
205 // Omit large diffs
206 $diffText = self::getDiffLink( $title, $newid, $oldid );
207 } elseif ( $diffText === false ) {
208 // Error in diff engine, probably a missing revision
209 $diffText = Html::rawElement(
210 'p',
211 [],
212 "Can't load revision $newid"
213 );
214 } else {
215 // Diff output fine, clean up any illegal UTF-8
216 $diffText = UtfNormal\Validator::cleanUp( $diffText );
217 $diffText = self::applyDiffStyle( $diffText );
218 }
219 } else {
220 $revRecord = $revLookup->getRevisionById( $newid );
221 if ( $feedDiffCutoff <= 0 || $revRecord === null ) {
222 $newContent = $contentHandlerFactory
223 ->getContentHandler( $title->getContentModel() )
224 ->makeEmptyContent();
225 } else {
226 $newContent = $revRecord->getContent( SlotRecord::MAIN );
227 }
228
229 if ( $newContent instanceof TextContent ) {
230 // only textual content has a "source view".
231 $text = $newContent->getText();
232
233 if ( $feedDiffCutoff <= 0 || strlen( $text ) > $feedDiffCutoff ) {
234 $html = null;
235 } else {
236 $html = nl2br( htmlspecialchars( $text ) );
237 }
238 } else {
239 // XXX: we could get an HTML representation of the content via getParserOutput, but that may
240 // contain JS magic and generally may not be suitable for inclusion in a feed.
241 // Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
242 // Compare also ApiFeedContributions::feedItemDesc
243 $html = null;
244 }
245
246 if ( $html === null ) {
247 // Omit large new page diffs, T31110
248 // Also use diff link for non-textual content
249 $diffText = self::getDiffLink( $title, $newid );
250 } else {
251 $diffText = Html::rawElement(
252 'p',
253 [],
254 Html::rawElement( 'b', [], wfMessage( 'newpage' )->text() )
255 );
256 $diffText .= Html::rawElement( 'div', [], $html );
257 }
258 }
259 $completeText .= $diffText;
260
261 return $completeText;
262 }
263
273 protected static function getDiffLink( Title $title, $newid, $oldid = null ) {
274 $queryParameters = [ 'diff' => $newid ];
275 if ( $oldid != null ) {
276 $queryParameters['oldid'] = $oldid;
277 }
278 $diffUrl = $title->getFullURL( $queryParameters );
279
280 $diffLink = Html::element( 'a', [ 'href' => $diffUrl ],
281 wfMessage( 'showdiff' )->inContentLanguage()->text() );
282
283 return $diffLink;
284 }
285
294 public static function applyDiffStyle( $text ) {
295 $styles = [
296 'diff' => 'background-color: #fff; color: #202122;',
297 'diff-otitle' => 'background-color: #fff; color: #202122; text-align: center;',
298 'diff-ntitle' => 'background-color: #fff; color: #202122; text-align: center;',
299 'diff-addedline' => 'color: #202122; font-size: 88%; border-style: solid; '
300 . 'border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; '
301 . 'vertical-align: top; white-space: pre-wrap;',
302 'diff-deletedline' => 'color: #202122; font-size: 88%; border-style: solid; '
303 . 'border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; '
304 . 'vertical-align: top; white-space: pre-wrap;',
305 'diff-context' => 'background-color: #f8f9fa; color: #202122; font-size: 88%; '
306 . 'border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; '
307 . 'border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;',
308 'diffchange' => 'font-weight: bold; text-decoration: none;',
309 ];
310
311 foreach ( $styles as $class => $style ) {
312 $text = preg_replace( '/(<\w+\b[^<>]*)\bclass=([\'"])(?:[^\'"]*\s)?' .
313 preg_quote( $class ) . '(?:\s[^\'"]*)?\2(?=[^<>]*>)/',
314 '$1style="' . $style . '"', $text );
315 }
316
317 return $text;
318 }
319
320}
321
323class_alias( FeedUtils::class, 'FeedUtils' );
const RC_LOG
Definition Defines.php:119
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli' $wgOut
Definition Setup.php:541
Content object implementation for representing flat text.
An IContextSource implementation which will inherit context from another source but allow individual ...
Group all the pieces relevant to the context of a request into one instance.
Helper functions for feeds.
Definition FeedUtils.php:43
static formatDiff( $row, $formattedComment=null)
Format a diff for the newsfeed.
Definition FeedUtils.php:85
static getDiffLink(Title $title, $newid, $oldid=null)
Generates a diff link.
static formatDiffRow2( $title, $oldid, $newid, $timestamp, $formattedComment, $actiontext='')
Really really format a diff for the newsfeed.
static formatDiffRow( $title, $oldid, $newid, $timestamp, $comment, $actiontext='')
Really format a diff for the newsfeed.
static checkFeedOutput( $type, $output=null)
Check whether feeds can be used and that $type is a valid feed type.
Definition FeedUtils.php:54
static applyDiffStyle( $text)
Hacky application of diff styles for the feeds.
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
A class containing constants representing the names of configuration variables.
const FeedClasses
Name constant for the FeedClasses setting, for use with Config::get()
const Feed
Name constant for the Feed setting, for use with Config::get()
const FeedDiffCutoff
Name constant for the FeedDiffCutoff setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
This is one of the Core classes and should be read at least once by any new developers.
Page revision base class.
Value object representing a content slot associated with a page revision.
Represents a title within MediaWiki.
Definition Title.php:78
getFullURL( $query='', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition Title.php:2130
element(SerializerNode $parent, SerializerNode $node, $contents)