MediaWiki  master
CompareHandler.php
Go to the documentation of this file.
1 <?php
2 
4 
14 use Parser;
15 use RequestContext;
16 use TextContent;
17 use User;
20 
21 class CompareHandler extends Handler {
23  private $revisionLookup;
24 
27 
29  private $parser;
30 
32  private $user;
33 
35  private $revisions = [];
36 
38  private $textCache = [];
39 
40  public function __construct(
44  ) {
45  $this->revisionLookup = $revisionLookup;
46  $this->permissionManager = $permissionManager;
47  $this->parser = $parser;
48 
49  // @todo Inject this, when there is a good way to do that
50  $this->user = RequestContext::getMain()->getUser();
51  }
52 
53  public function execute() {
54  $fromRev = $this->getRevisionOrThrow( 'from' );
55  $toRev = $this->getRevisionOrThrow( 'to' );
56 
57  if ( $fromRev->getPageId() !== $toRev->getPageId() ) {
58  throw new LocalizedHttpException(
59  new MessageValue( 'rest-compare-page-mismatch' ), 400 );
60  }
61 
62  if ( !$this->permissionManager->userCan( 'read', $this->user,
63  $toRev->getPageAsLinkTarget() )
64  ) {
65  throw new LocalizedHttpException(
66  new MessageValue( 'rest-compare-permission-denied' ), 403 );
67  }
68 
69  $data = [
70  'from' => [
71  'id' => $fromRev->getId(),
72  'slot_role' => $this->getRole(),
73  'sections' => $this->getSectionInfo( 'from' )
74  ],
75  'to' => [
76  'id' => $toRev->getId(),
77  'slot_role' => $this->getRole(),
78  'sections' => $this->getSectionInfo( 'to' )
79  ],
80  'diff' => [ 'PLACEHOLDER' => null ]
81  ];
82  $rf = $this->getResponseFactory();
83  $wrapperJson = $rf->encodeJson( $data );
84  $diff = $this->getJsonDiff();
85  $response = $rf->create();
86  $response->setHeader( 'Content-Type', 'application/json' );
87  // A hack until getJsonDiff() is moved to SlotDiffRenderer and only nested inner diff is returned
88  $innerDiff = substr( $diff, 1, -1 );
89  $response->setBody( new StringStream(
90  str_replace( '"diff":{"PLACEHOLDER":null}', $innerDiff, $wrapperJson ) ) );
91  return $response;
92  }
93 
98  private function getRevision( $paramName ) {
99  if ( !isset( $this->revisions[$paramName] ) ) {
100  $this->revisions[$paramName] =
101  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable T235355
102  $this->revisionLookup->getRevisionById( $this->getValidatedParams()[$paramName] );
103  }
104  return $this->revisions[$paramName];
105  }
106 
112  private function getRevisionOrThrow( $paramName ) {
113  $rev = $this->getRevision( $paramName );
114  if ( !$rev ) {
115  throw new LocalizedHttpException(
116  new MessageValue( 'rest-compare-nonexistent', [ $paramName ] ), 404 );
117  }
118 
119  if ( !$this->isAccessible( $rev ) ) {
120  throw new LocalizedHttpException(
121  new MessageValue( 'rest-compare-inaccessible', [ $paramName ] ), 403 );
122  }
123  return $rev;
124  }
125 
130  private function isAccessible( $rev ) {
131  return $rev->audienceCan(
132  RevisionRecord::DELETED_TEXT,
133  RevisionRecord::FOR_THIS_USER,
134  $this->user
135  );
136  }
137 
138  private function getRole() {
139  return SlotRecord::MAIN;
140  }
141 
142  private function getRevisionText( $paramName ) {
143  if ( !isset( $this->textCache[$paramName] ) ) {
144  $revision = $this->getRevision( $paramName );
145  try {
146  $content = $revision
147  ->getSlot( $this->getRole(), RevisionRecord::FOR_THIS_USER, $this->user )
148  ->getContent()
149  ->convert( CONTENT_MODEL_TEXT );
150  if ( $content instanceof TextContent ) {
151  $this->textCache[$paramName] = $content->getText();
152  } else {
153  throw new LocalizedHttpException(
154  new MessageValue(
155  'rest-compare-wrong-content',
156  [ $this->getRole(), $paramName ]
157  ),
158  400 );
159  }
160  } catch ( SuppressedDataException $e ) {
161  throw new LocalizedHttpException(
162  new MessageValue( 'rest-compare-inaccessible', [ $paramName ] ), 403 );
163  } catch ( RevisionAccessException $e ) {
164  throw new LocalizedHttpException(
165  new MessageValue( 'rest-compare-nonexistent', [ $paramName ] ), 404 );
166  }
167  }
168  return $this->textCache[$paramName];
169  }
170 
174  private function getJsonDiff() {
175  // TODO: properly implement
176  // This is a prototype only. SlotDiffRenderer should be extended to support this use case.
177  $fromText = $this->getRevisionText( 'from' );
178  $toText = $this->getRevisionText( 'to' );
179  if ( !function_exists( 'wikidiff2_inline_json_diff' ) ) {
180  throw new LocalizedHttpException(
181  new MessageValue( 'rest-compare-wikidiff2' ), 500 );
182  }
183  return wikidiff2_inline_json_diff( $fromText, $toText, 2 );
184  }
185 
189  private function getSectionInfo( $paramName ) {
190  $text = $this->getRevisionText( $paramName );
191  $parserSections = $this->parser->getFlatSectionInfo( $text );
192  $sections = [];
193  foreach ( $parserSections as $i => $parserSection ) {
194  // Skip section zero, which comes before the first heading, since
195  // its offset is always zero, so the client can assume its location.
196  if ( $i !== 0 ) {
197  $sections[] = [
198  'level' => $parserSection['level'],
199  'heading' => $parserSection['heading'],
200  'offset' => $parserSection['offset'],
201  ];
202  }
203  }
204  return $sections;
205  }
206 
207  public function getParamSettings() {
208  return [
209  'from' => [
210  ParamValidator::PARAM_TYPE => 'integer',
211  ParamValidator::PARAM_REQUIRED => true,
212  Handler::PARAM_SOURCE => 'path',
213  ],
214  'to' => [
215  ParamValidator::PARAM_TYPE => 'integer',
216  ParamValidator::PARAM_REQUIRED => true,
217  Handler::PARAM_SOURCE => 'path',
218  ],
219  ];
220  }
221 }
$response
const PARAM_SOURCE
(string) ParamValidator constant to specify the source of the parameter.
Definition: Handler.php:15
Value object representing a message for i18n.
const CONTENT_MODEL_TEXT
Definition: Defines.php:218
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
getResponseFactory()
Get the ResponseFactory which can be used to generate Response objects.
Definition: Handler.php:92
static getMain()
Get the RequestContext object associated with the main request.
A stream class which uses a string as the underlying storage.
Exception raised in response to an audience check when attempting to access suppressed information wi...
Service for looking up page revisions.
Exception representing a failure to look up a revision.
$content
Definition: router.php:78
getValidatedParams()
Fetch the validated parameters.
Definition: Handler.php:174
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
__construct(RevisionLookup $revisionLookup, PermissionManager $permissionManager, Parser $parser)