MediaWiki master
CompareHandler.php
Go to the documentation of this file.
1<?php
2
4
17
18class CompareHandler extends Handler {
19 private RevisionLookup $revisionLookup;
20 private ParserFactory $parserFactory;
21
23 private $revisions = [];
24
26 private $textCache = [];
27
28 public function __construct(
29 RevisionLookup $revisionLookup,
30 ParserFactory $parserFactory
31 ) {
32 $this->revisionLookup = $revisionLookup;
33 $this->parserFactory = $parserFactory;
34 }
35
36 public function execute() {
37 $fromRev = $this->getRevisionOrThrow( 'from' );
38 $toRev = $this->getRevisionOrThrow( 'to' );
39
40 if ( $fromRev->getPageId() !== $toRev->getPageId() ) {
41 throw new LocalizedHttpException(
42 new MessageValue( 'rest-compare-page-mismatch' ), 400 );
43 }
44
45 if ( !$this->getAuthority()->authorizeRead( 'read', $toRev->getPage() ) ) {
46 throw new LocalizedHttpException(
47 new MessageValue( 'rest-compare-permission-denied' ), 403 );
48 }
49
50 $data = [
51 'from' => [
52 'id' => $fromRev->getId(),
53 'slot_role' => $this->getRole(),
54 'sections' => $this->getSectionInfo( 'from' )
55 ],
56 'to' => [
57 'id' => $toRev->getId(),
58 'slot_role' => $this->getRole(),
59 'sections' => $this->getSectionInfo( 'to' )
60 ],
61 'diff' => [ 'PLACEHOLDER' => null ]
62 ];
63 $rf = $this->getResponseFactory();
64 $wrapperJson = $rf->encodeJson( $data );
65 $diff = $this->getJsonDiff();
66 $response = $rf->create();
67 $response->setHeader( 'Content-Type', 'application/json' );
68 // A hack until getJsonDiff() is moved to SlotDiffRenderer and only nested inner diff is returned
69 $innerDiff = substr( $diff, 1, -1 );
70 $response->setBody( new StringStream(
71 str_replace( '"diff":{"PLACEHOLDER":null}', $innerDiff, $wrapperJson ) ) );
72 return $response;
73 }
74
79 private function getRevision( $paramName ) {
80 if ( !isset( $this->revisions[$paramName] ) ) {
81 $this->revisions[$paramName] =
82 $this->revisionLookup->getRevisionById( $this->getValidatedParams()[$paramName] );
83 }
84 return $this->revisions[$paramName];
85 }
86
92 private function getRevisionOrThrow( $paramName ) {
93 $rev = $this->getRevision( $paramName );
94 if ( !$rev ) {
95 throw new LocalizedHttpException(
96 new MessageValue( 'rest-compare-nonexistent', [ $paramName ] ), 404 );
97 }
98
99 if ( !$this->isAccessible( $rev ) ) {
100 throw new LocalizedHttpException(
101 new MessageValue( 'rest-compare-inaccessible', [ $paramName ] ), 403 );
102 }
103 return $rev;
104 }
105
110 private function isAccessible( $rev ) {
111 return $rev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() );
112 }
113
114 private function getRole() {
115 return SlotRecord::MAIN;
116 }
117
118 private function getRevisionText( $paramName ) {
119 if ( !isset( $this->textCache[$paramName] ) ) {
120 $revision = $this->getRevision( $paramName );
121 try {
122 $content = $revision
123 ->getSlot( $this->getRole(), RevisionRecord::FOR_THIS_USER, $this->getAuthority() )
124 ->getContent()
125 ->convert( CONTENT_MODEL_TEXT );
126 if ( $content instanceof TextContent ) {
127 $this->textCache[$paramName] = $content->getText();
128 } else {
129 throw new LocalizedHttpException(
130 new MessageValue(
131 'rest-compare-wrong-content',
132 [ $this->getRole(), $paramName ]
133 ),
134 400 );
135 }
136 } catch ( SuppressedDataException $e ) {
137 throw new LocalizedHttpException(
138 new MessageValue( 'rest-compare-inaccessible', [ $paramName ] ), 403 );
139 } catch ( RevisionAccessException $e ) {
140 throw new LocalizedHttpException(
141 new MessageValue( 'rest-compare-nonexistent', [ $paramName ] ), 404 );
142 }
143 }
144 return $this->textCache[$paramName];
145 }
146
150 private function getJsonDiff() {
151 // TODO: properly implement
152 // This is a prototype only. SlotDiffRenderer should be extended to support this use case.
153 $fromText = $this->getRevisionText( 'from' );
154 $toText = $this->getRevisionText( 'to' );
155 if ( !function_exists( 'wikidiff2_inline_json_diff' ) ) {
156 throw new LocalizedHttpException(
157 new MessageValue( 'rest-compare-wikidiff2' ), 500 );
158 }
159 return wikidiff2_inline_json_diff( $fromText, $toText, 2 );
160 }
161
166 private function getSectionInfo( $paramName ) {
167 $text = $this->getRevisionText( $paramName );
168 $parserSections = $this->parserFactory->getInstance()->getFlatSectionInfo( $text );
169 $sections = [];
170 foreach ( $parserSections as $i => $parserSection ) {
171 // Skip section zero, which comes before the first heading, since
172 // its offset is always zero, so the client can assume its location.
173 if ( $i !== 0 ) {
174 $sections[] = [
175 'level' => $parserSection['level'],
176 'heading' => $parserSection['heading'],
177 'offset' => $parserSection['offset'],
178 ];
179 }
180 }
181 return $sections;
182 }
183
187 public function needsWriteAccess() {
188 return false;
189 }
190
191 public function getParamSettings() {
192 return [
193 'from' => [
194 ParamValidator::PARAM_TYPE => 'integer',
195 ParamValidator::PARAM_REQUIRED => true,
196 Handler::PARAM_SOURCE => 'path',
197 Handler::PARAM_DESCRIPTION => new MessageValue( 'rest-param-desc-compare-from' ),
198 ],
199 'to' => [
200 ParamValidator::PARAM_TYPE => 'integer',
201 ParamValidator::PARAM_REQUIRED => true,
202 Handler::PARAM_SOURCE => 'path',
203 Handler::PARAM_DESCRIPTION => new MessageValue( 'rest-param-desc-compare-to' ),
204 ],
205 ];
206 }
207}
const CONTENT_MODEL_TEXT
Definition Defines.php:231
Content object implementation for representing flat text.
needsWriteAccess()
Indicates whether this route requires write access to the wiki.Handlers may override this method to r...
__construct(RevisionLookup $revisionLookup, ParserFactory $parserFactory)
getParamSettings()
Fetch ParamValidator settings for parameters.
Base class for REST route handlers.
Definition Handler.php:25
getValidatedParams()
Fetch the validated parameters.
Definition Handler.php:887
getAuthority()
Get the current acting authority.
Definition Handler.php:343
getResponseFactory()
Get the ResponseFactory which can be used to generate Response objects.
Definition Handler.php:365
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...
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.