MediaWiki master
FeedUtils.php
Go to the documentation of this file.
1<?php
10namespace MediaWiki\Feed;
11
23use UtfNormal;
24use Wikimedia\Timestamp\TimestampFormat as TS;
25
31class FeedUtils {
32
41 public static function checkFeedOutput( $type, $output = null ) {
42 $feed = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::Feed );
43 $feedClasses = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::FeedClasses );
44 if ( $output === null ) {
45 // Todo update GoogleNewsSitemap and deprecate
46 global $wgOut;
47 $output = $wgOut;
48 }
49
50 if ( !$feed ) {
51 $output->addWikiMsg( 'feed-unavailable' );
52 return false;
53 }
54
55 if ( !isset( $feedClasses[$type] ) ) {
56 $output->addWikiMsg( 'feed-invalid' );
57 return false;
58 }
59
60 return true;
61 }
62
72 public static function formatDiff( $row, $formattedComment = null ) {
73 $titleObj = Title::makeTitle( $row->rc_namespace, $row->rc_title );
74 $timestamp = wfTimestamp( TS::MW, $row->rc_timestamp );
75 $actiontext = '';
76 if ( $row->rc_source === RecentChange::SRC_LOG ) {
77 $rcRow = (array)$row; // newFromRow() only accepts arrays for RC rows
78 $actiontext = MediaWikiServices::getInstance()->getLogFormatterFactory()
79 ->newFromRow( $rcRow )->getActionText();
80 }
81 if ( $row->rc_deleted & RevisionRecord::DELETED_COMMENT ) {
82 $formattedComment = wfMessage( 'rev-deleted-comment' )->escaped();
83 } elseif ( $formattedComment === null ) {
85 $formattedComment = $services->getCommentFormatter()->format(
86 $services->getCommentStore()->getComment( 'rc_comment', $row )->text );
87 }
88 return self::formatDiffRow2( $titleObj,
89 $row->rc_last_oldid, $row->rc_this_oldid,
90 $timestamp,
91 $formattedComment,
92 $actiontext
93 );
94 }
95
108 public static function formatDiffRow( $title, $oldid, $newid, $timestamp,
109 $comment, $actiontext = ''
110 ) {
111 $formattedComment = MediaWikiServices::getInstance()->getCommentFormatter()
112 ->format( $comment );
113 return self::formatDiffRow2( $title, $oldid, $newid, $timestamp,
114 $formattedComment, $actiontext );
115 }
116
129 public static function formatDiffRow2(
130 $title, $oldid, $newid, $timestamp, $formattedComment, $actiontext = ''
131 ) {
132 $feedDiffCutoff = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::FeedDiffCutoff );
133
134 // log entries
135 $unwrappedText = implode(
136 ' ',
137 array_filter( [ $actiontext, $formattedComment ] )
138 );
139 $completeText = Html::rawElement( 'p', [], $unwrappedText ) . "\n";
140
141 // NOTE: Check permissions for anonymous users, not current user.
142 // No "privileged" version should end up in the cache.
143 // Most feed readers will not log in anyway.
144 $services = MediaWikiServices::getInstance();
145 $anon = $services->getUserFactory()->newAnonymous();
146 $permManager = $services->getPermissionManager();
147 $userCan = $permManager->userCan(
148 'read',
149 $anon,
150 $title
151 );
152
153 // Can't diff special pages, unreadable pages or pages with no new revision
154 // to compare against: just return the text.
155 if ( $title->getNamespace() < 0 || !$userCan || !$newid ) {
156 return $completeText;
157 }
158
159 $revLookup = $services->getRevisionLookup();
160 $contentHandlerFactory = $services->getContentHandlerFactory();
161 if ( $oldid ) {
162 $diffText = '';
163 // Don't bother generating the diff if we won't be able to show it
164 if ( $feedDiffCutoff > 0 ) {
165 $revRecord = $revLookup->getRevisionById( $oldid );
166
167 if ( !$revRecord ) {
168 $diffText = false;
169 } else {
170 $context = new DerivativeContext( RequestContext::getMain() );
171 $context->setTitle( $title );
172
173 $model = $revRecord->getSlot(
174 SlotRecord::MAIN,
175 RevisionRecord::RAW
176 )->getModel();
177 $contentHandler = $contentHandlerFactory->getContentHandler( $model );
178 $de = $contentHandler->createDifferenceEngine( $context, $oldid, $newid );
179 $lang = $context->getLanguage();
180 $user = $context->getUser();
181 $diffText = $de->getDiff(
182 $context->msg( 'previousrevision' )->text(), // hack
183 $context->msg( 'revisionasof',
184 $lang->userTimeAndDate( $timestamp, $user ),
185 $lang->userDate( $timestamp, $user ),
186 $lang->userTime( $timestamp, $user ) )->text() );
187 }
188 }
189
190 if ( $feedDiffCutoff <= 0 || ( strlen( $diffText ) > $feedDiffCutoff ) ) {
191 // Omit large diffs
192 $diffText = self::getDiffLink( $title, $newid, $oldid );
193 } elseif ( $diffText === false ) {
194 // Error in diff engine, probably a missing revision
195 $diffText = Html::element(
196 'p',
197 [],
198 "Can't load revision $newid"
199 );
200 } else {
201 // Diff output fine, clean up any illegal UTF-8
202 $diffText = UtfNormal\Validator::cleanUp( $diffText );
203 $diffText = self::applyDiffStyle( $diffText );
204 }
205 } else {
206 $revRecord = $revLookup->getRevisionById( $newid );
207 if ( $feedDiffCutoff <= 0 || $revRecord === null ) {
208 $newContent = $contentHandlerFactory
209 ->getContentHandler( $title->getContentModel() )
210 ->makeEmptyContent();
211 } else {
212 $newContent = $revRecord->getContent( SlotRecord::MAIN );
213 }
214
215 if ( $newContent instanceof TextContent ) {
216 // only textual content has a "source view".
217 $text = $newContent->getText();
218
219 if ( $feedDiffCutoff <= 0 || strlen( $text ) > $feedDiffCutoff ) {
220 $html = null;
221 } else {
222 $html = nl2br( htmlspecialchars( $text ) );
223 }
224 } else {
225 // XXX: we could get an HTML representation of the content via getParserOutput, but that may
226 // contain JS magic and generally may not be suitable for inclusion in a feed.
227 // Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
228 // Compare also ApiFeedContributions::feedItemDesc
229 $html = null;
230 }
231
232 if ( $html === null ) {
233 // Omit large new page diffs, T31110
234 // Also use diff link for non-textual content
235 $diffText = self::getDiffLink( $title, $newid );
236 } else {
237 $diffText = Html::rawElement(
238 'p',
239 [],
240 Html::element( 'b', [], wfMessage( 'newpage' )->text() )
241 );
242 $diffText .= Html::rawElement( 'div', [], $html );
243 }
244 }
245 $completeText .= $diffText;
246
247 return $completeText;
248 }
249
259 protected static function getDiffLink( Title $title, $newid, $oldid = null ) {
260 $queryParameters = [ 'diff' => $newid ];
261 if ( $oldid != null ) {
262 $queryParameters['oldid'] = $oldid;
263 }
264 $diffUrl = $title->getFullURL( $queryParameters );
265
266 $diffLink = Html::element( 'a', [ 'href' => $diffUrl ],
267 wfMessage( 'showdiff' )->inContentLanguage()->text() );
268
269 return $diffLink;
270 }
271
280 public static function applyDiffStyle( $text ) {
281 $styles = [
282 'diff' => 'background-color: #fff; color: #202122;',
283 'diff-otitle' => 'background-color: #fff; color: #202122; text-align: center;',
284 'diff-ntitle' => 'background-color: #fff; color: #202122; text-align: center;',
285 'diff-addedline' => 'color: #202122; font-size: 88%; border-style: solid; '
286 . 'border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; '
287 . 'vertical-align: top; white-space: pre-wrap;',
288 'diff-deletedline' => 'color: #202122; font-size: 88%; border-style: solid; '
289 . 'border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; '
290 . 'vertical-align: top; white-space: pre-wrap;',
291 'diff-context' => 'background-color: #f8f9fa; color: #202122; font-size: 88%; '
292 . 'border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; '
293 . 'border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;',
294 'diffchange' => 'font-weight: bold; text-decoration: none;',
295 ];
296
297 foreach ( $styles as $class => $style ) {
298 $text = preg_replace( '/(<\w+\b[^<>]*)\bclass=([\'"])(?:[^\'"]*\s)?' .
299 preg_quote( $class ) . '(?:\s[^\'"]*)?\2(?=[^<>]*>)/',
300 '$1style="' . $style . '"', $text );
301 }
302
303 return $text;
304 }
305
306}
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(MW_ENTRY_POINT==='index') if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli' $wgOut
Definition Setup.php:551
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.
makeTitle( $linkId)
Convert a link ID to a Title.to override Title
Helper functions for feeds.
Definition FeedUtils.php:31
static formatDiff( $row, $formattedComment=null)
Format a diff for the newsfeed.
Definition FeedUtils.php:72
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:41
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:43
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.
Utility class for creating and reading rows in the recentchanges table.
Page revision base class.
Value object representing a content slot associated with a page revision.
Represents a title within MediaWiki.
Definition Title.php:69
getFullURL( $query='', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition Title.php:2152
element(SerializerNode $parent, SerializerNode $node, $contents)