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
40 public static function checkFeedOutput( $type, $output ): bool {
41 $feed = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::Feed );
43
44 if ( !$feed ) {
45 $output->addWikiMsg( 'feed-unavailable' );
46 return false;
47 }
48
49 if ( !isset( $feedClasses[$type] ) ) {
50 $output->addWikiMsg( 'feed-invalid' );
51 return false;
52 }
53
54 return true;
55 }
56
66 public static function formatDiff( $row, $formattedComment = null ) {
67 $titleObj = Title::makeTitle( $row->rc_namespace, $row->rc_title );
68 $timestamp = wfTimestamp( TS::MW, $row->rc_timestamp );
69 $actiontext = '';
70 if ( $row->rc_source === RecentChange::SRC_LOG ) {
71 $rcRow = (array)$row; // newFromRow() only accepts arrays for RC rows
72 $actiontext = MediaWikiServices::getInstance()->getLogFormatterFactory()
73 ->newFromRow( $rcRow )->getActionText();
74 }
75 if ( $row->rc_deleted & RevisionRecord::DELETED_COMMENT ) {
76 $formattedComment = wfMessage( 'rev-deleted-comment' )->escaped();
77 } elseif ( $formattedComment === null ) {
79 $formattedComment = $services->getCommentFormatter()->format(
80 $services->getCommentStore()->getComment( 'rc_comment', $row )->text );
81 }
82 return self::formatDiffRow2( $titleObj,
83 $row->rc_last_oldid, $row->rc_this_oldid,
84 $timestamp,
85 $formattedComment,
86 $actiontext
87 );
88 }
89
102 public static function formatDiffRow( $title, $oldid, $newid, $timestamp,
103 $comment, $actiontext = ''
104 ) {
105 $formattedComment = MediaWikiServices::getInstance()->getCommentFormatter()
106 ->format( $comment );
107 return self::formatDiffRow2( $title, $oldid, $newid, $timestamp,
108 $formattedComment, $actiontext );
109 }
110
123 public static function formatDiffRow2(
124 $title, $oldid, $newid, $timestamp, $formattedComment, $actiontext = ''
125 ) {
126 $feedDiffCutoff = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::FeedDiffCutoff );
127
128 // log entries
129 $unwrappedText = implode(
130 ' ',
131 array_filter( [ $actiontext, $formattedComment ] )
132 );
133 $completeText = Html::rawElement( 'p', [], $unwrappedText ) . "\n";
134
135 // NOTE: Check permissions for anonymous users, not current user.
136 // No "privileged" version should end up in the cache.
137 // Most feed readers will not log in anyway.
138 $services = MediaWikiServices::getInstance();
139 $anon = $services->getUserFactory()->newAnonymous();
140 $permManager = $services->getPermissionManager();
141 $userCan = $permManager->userCan(
142 'read',
143 $anon,
144 $title
145 );
146
147 // Can't diff special pages, unreadable pages or pages with no new revision
148 // to compare against: just return the text.
149 if ( $title->getNamespace() < 0 || !$userCan || !$newid ) {
150 return $completeText;
151 }
152
153 $revLookup = $services->getRevisionLookup();
154 $contentHandlerFactory = $services->getContentHandlerFactory();
155 if ( $oldid ) {
156 $diffText = '';
157 // Don't bother generating the diff if we won't be able to show it
158 if ( $feedDiffCutoff > 0 ) {
159 $revRecord = $revLookup->getRevisionById( $oldid );
160
161 if ( !$revRecord ) {
162 $diffText = false;
163 } else {
164 $context = new DerivativeContext( RequestContext::getMain() );
165 $context->setTitle( $title );
166
167 $model = $revRecord->getSlot(
168 SlotRecord::MAIN,
169 RevisionRecord::RAW
170 )->getModel();
171 $contentHandler = $contentHandlerFactory->getContentHandler( $model );
172 $de = $contentHandler->createDifferenceEngine( $context, $oldid, $newid );
173 $lang = $context->getLanguage();
174 $user = $context->getUser();
175 $diffText = $de->getDiff(
176 $context->msg( 'previousrevision' )->text(), // hack
177 $context->msg( 'revisionasof',
178 $lang->userTimeAndDate( $timestamp, $user ),
179 $lang->userDate( $timestamp, $user ),
180 $lang->userTime( $timestamp, $user ) )->text() );
181 }
182 }
183
184 if ( $feedDiffCutoff <= 0 || ( strlen( $diffText ) > $feedDiffCutoff ) ) {
185 // Omit large diffs
186 $diffText = self::getDiffLink( $title, $newid, $oldid );
187 } elseif ( $diffText === false ) {
188 // Error in diff engine, probably a missing revision
189 $diffText = Html::element(
190 'p',
191 [],
192 "Can't load revision $newid"
193 );
194 } else {
195 // Diff output fine, clean up any illegal UTF-8
196 $diffText = UtfNormal\Validator::cleanUp( $diffText );
197 $diffText = self::applyDiffStyle( $diffText );
198 }
199 } else {
200 $revRecord = $revLookup->getRevisionById( $newid );
201 if ( $feedDiffCutoff <= 0 || $revRecord === null ) {
202 $newContent = $contentHandlerFactory
203 ->getContentHandler( $title->getContentModel() )
204 ->makeEmptyContent();
205 } else {
206 $newContent = $revRecord->getContent( SlotRecord::MAIN );
207 }
208
209 if ( $newContent instanceof TextContent ) {
210 // only textual content has a "source view".
211 $text = $newContent->getText();
212
213 if ( $feedDiffCutoff <= 0 || strlen( $text ) > $feedDiffCutoff ) {
214 $html = null;
215 } else {
216 $html = nl2br( htmlspecialchars( $text ) );
217 }
218 } else {
219 // XXX: we could get an HTML representation of the content via getParserOutput, but that may
220 // contain JS magic and generally may not be suitable for inclusion in a feed.
221 // Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
222 // Compare also ApiFeedContributions::feedItemDesc
223 $html = null;
224 }
225
226 if ( $html === null ) {
227 // Omit large new page diffs, T31110
228 // Also use diff link for non-textual content
229 $diffText = self::getDiffLink( $title, $newid );
230 } else {
231 $diffText = Html::rawElement(
232 'p',
233 [],
234 Html::element( 'b', [], wfMessage( 'newpage' )->text() )
235 );
236 $diffText .= Html::rawElement( 'div', [], $html );
237 }
238 }
239 $completeText .= $diffText;
240
241 return $completeText;
242 }
243
253 protected static function getDiffLink( Title $title, $newid, $oldid = null ) {
254 $queryParameters = [ 'diff' => $newid ];
255 if ( $oldid != null ) {
256 $queryParameters['oldid'] = $oldid;
257 }
258 $diffUrl = $title->getFullURL( $queryParameters );
259
260 $diffLink = Html::element( 'a', [ 'href' => $diffUrl ],
261 wfMessage( 'showdiff' )->inContentLanguage()->text() );
262
263 return $diffLink;
264 }
265
274 public static function applyDiffStyle( $text ) {
275 $styles = [
276 'diff' => 'background-color: #fff; color: #202122;',
277 'diff-otitle' => 'background-color: #fff; color: #202122; text-align: center;',
278 'diff-ntitle' => 'background-color: #fff; color: #202122; text-align: center;',
279 'diff-addedline' => 'color: #202122; font-size: 88%; border-style: solid; '
280 . 'border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; '
281 . 'vertical-align: top; white-space: pre-wrap;',
282 'diff-deletedline' => 'color: #202122; font-size: 88%; border-style: solid; '
283 . 'border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; '
284 . 'vertical-align: top; white-space: pre-wrap;',
285 'diff-context' => 'background-color: #f8f9fa; color: #202122; font-size: 88%; '
286 . 'border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; '
287 . 'border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;',
288 'diffchange' => 'font-weight: bold; text-decoration: none;',
289 ];
290
291 foreach ( $styles as $class => $style ) {
292 $text = preg_replace( '/(<\w+\b[^<>]*)\bclass=([\'"])(?:[^\'"]*\s)?' .
293 preg_quote( $class ) . '(?:\s[^\'"]*)?\2(?=[^<>]*>)/',
294 '$1style="' . $style . '"', $text );
295 }
296
297 return $text;
298 }
299
300}
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.
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:66
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 checkFeedOutput( $type, $output)
Check whether feeds can be used and that $type is a valid feed type.
Definition FeedUtils.php:40
static formatDiffRow( $title, $oldid, $newid, $timestamp, $comment, $actiontext='')
Really format a diff for the newsfeed.
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:44
A class containing constants representing the names of configuration variables.
const FeedClasses
Name constant for the FeedClasses 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.
getMainConfig()
Returns the Config object that provides configuration for MediaWiki core.
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:2156
element(SerializerNode $parent, SerializerNode $node, $contents)