MediaWiki master
CompareHandler.php
Go to the documentation of this file.
1<?php
2
4
14use TextContent;
17
18class CompareHandler extends Handler {
20 private $revisionLookup;
21
23 private $parserFactory;
24
26 private $revisions = [];
27
29 private $textCache = [];
30
31 public function __construct(
32 RevisionLookup $revisionLookup,
33 ParserFactory $parserFactory
34 ) {
35 $this->revisionLookup = $revisionLookup;
36 $this->parserFactory = $parserFactory;
37 }
38
39 public function execute() {
40 $fromRev = $this->getRevisionOrThrow( 'from' );
41 $toRev = $this->getRevisionOrThrow( 'to' );
42
43 if ( $fromRev->getPageId() !== $toRev->getPageId() ) {
44 throw new LocalizedHttpException(
45 new MessageValue( 'rest-compare-page-mismatch' ), 400 );
46 }
47
48 if ( !$this->getAuthority()->authorizeRead( 'read', $toRev->getPage() ) ) {
49 throw new LocalizedHttpException(
50 new MessageValue( 'rest-compare-permission-denied' ), 403 );
51 }
52
53 $data = [
54 'from' => [
55 'id' => $fromRev->getId(),
56 'slot_role' => $this->getRole(),
57 'sections' => $this->getSectionInfo( 'from' )
58 ],
59 'to' => [
60 'id' => $toRev->getId(),
61 'slot_role' => $this->getRole(),
62 'sections' => $this->getSectionInfo( 'to' )
63 ],
64 'diff' => [ 'PLACEHOLDER' => null ]
65 ];
66 $rf = $this->getResponseFactory();
67 $wrapperJson = $rf->encodeJson( $data );
68 $diff = $this->getJsonDiff();
69 $response = $rf->create();
70 $response->setHeader( 'Content-Type', 'application/json' );
71 // A hack until getJsonDiff() is moved to SlotDiffRenderer and only nested inner diff is returned
72 $innerDiff = substr( $diff, 1, -1 );
73 $response->setBody( new StringStream(
74 str_replace( '"diff":{"PLACEHOLDER":null}', $innerDiff, $wrapperJson ) ) );
75 return $response;
76 }
77
82 private function getRevision( $paramName ) {
83 if ( !isset( $this->revisions[$paramName] ) ) {
84 $this->revisions[$paramName] =
85 $this->revisionLookup->getRevisionById( $this->getValidatedParams()[$paramName] );
86 }
87 return $this->revisions[$paramName];
88 }
89
95 private function getRevisionOrThrow( $paramName ) {
96 $rev = $this->getRevision( $paramName );
97 if ( !$rev ) {
98 throw new LocalizedHttpException(
99 new MessageValue( 'rest-compare-nonexistent', [ $paramName ] ), 404 );
100 }
101
102 if ( !$this->isAccessible( $rev ) ) {
103 throw new LocalizedHttpException(
104 new MessageValue( 'rest-compare-inaccessible', [ $paramName ] ), 403 );
105 }
106 return $rev;
107 }
108
113 private function isAccessible( $rev ) {
114 return $rev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() );
115 }
116
117 private function getRole() {
118 return SlotRecord::MAIN;
119 }
120
121 private function getRevisionText( $paramName ) {
122 if ( !isset( $this->textCache[$paramName] ) ) {
123 $revision = $this->getRevision( $paramName );
124 try {
125 $content = $revision
126 ->getSlot( $this->getRole(), RevisionRecord::FOR_THIS_USER, $this->getAuthority() )
127 ->getContent()
128 ->convert( CONTENT_MODEL_TEXT );
129 if ( $content instanceof TextContent ) {
130 $this->textCache[$paramName] = $content->getText();
131 } else {
132 throw new LocalizedHttpException(
133 new MessageValue(
134 'rest-compare-wrong-content',
135 [ $this->getRole(), $paramName ]
136 ),
137 400 );
138 }
139 } catch ( SuppressedDataException $e ) {
140 throw new LocalizedHttpException(
141 new MessageValue( 'rest-compare-inaccessible', [ $paramName ] ), 403 );
142 } catch ( RevisionAccessException $e ) {
143 throw new LocalizedHttpException(
144 new MessageValue( 'rest-compare-nonexistent', [ $paramName ] ), 404 );
145 }
146 }
147 return $this->textCache[$paramName];
148 }
149
153 private function getJsonDiff() {
154 // TODO: properly implement
155 // This is a prototype only. SlotDiffRenderer should be extended to support this use case.
156 $fromText = $this->getRevisionText( 'from' );
157 $toText = $this->getRevisionText( 'to' );
158 if ( !function_exists( 'wikidiff2_inline_json_diff' ) ) {
159 throw new LocalizedHttpException(
160 new MessageValue( 'rest-compare-wikidiff2' ), 500 );
161 }
162 return wikidiff2_inline_json_diff( $fromText, $toText, 2 );
163 }
164
169 private function getSectionInfo( $paramName ) {
170 $text = $this->getRevisionText( $paramName );
171 $parserSections = $this->parserFactory->getInstance()->getFlatSectionInfo( $text );
172 $sections = [];
173 foreach ( $parserSections as $i => $parserSection ) {
174 // Skip section zero, which comes before the first heading, since
175 // its offset is always zero, so the client can assume its location.
176 if ( $i !== 0 ) {
177 $sections[] = [
178 'level' => $parserSection['level'],
179 'heading' => $parserSection['heading'],
180 'offset' => $parserSection['offset'],
181 ];
182 }
183 }
184 return $sections;
185 }
186
190 public function needsWriteAccess() {
191 return false;
192 }
193
194 public function getParamSettings() {
195 return [
196 'from' => [
197 ParamValidator::PARAM_TYPE => 'integer',
198 ParamValidator::PARAM_REQUIRED => true,
199 Handler::PARAM_SOURCE => 'path',
200 ],
201 'to' => [
202 ParamValidator::PARAM_TYPE => 'integer',
203 ParamValidator::PARAM_REQUIRED => true,
204 Handler::PARAM_SOURCE => 'path',
205 ],
206 ];
207 }
208}
const CONTENT_MODEL_TEXT
Definition Defines.php:223
needsWriteAccess()
Indicates whether this route requires write access.The handler should override this if the route does...
__construct(RevisionLookup $revisionLookup, ParserFactory $parserFactory)
getParamSettings()
Fetch ParamValidator settings for parameters.
Base class for REST route handlers.
Definition Handler.php:21
getValidatedParams()
Fetch the validated parameters.
Definition Handler.php:597
getAuthority()
Get the current acting authority.
Definition Handler.php:205
getResponseFactory()
Get the ResponseFactory which can be used to generate Response objects.
Definition Handler.php:227
A stream class which uses a string as the underlying storage.
Exception representing a failure to look up a revision.
Page revision base class.
Value object representing a content slot associated with a page revision.
Exception raised in response to an audience check when attempting to access suppressed information wi...
Content object implementation for representing flat text.
Value object representing a message for i18n.
Service for formatting and validating API parameters.
Service for looking up page revisions.
Copyright (C) 2011-2020 Wikimedia Foundation and others.