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