Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 79 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
BoardContentHandler | |
0.00% |
0 / 79 |
|
0.00% |
0 / 11 |
650 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getDiffEngineClass | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isSupportedFormat | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
serializeContent | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
unserializeContent | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
makeEmptyContent | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
canBeUsedOn | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getActionOverrides | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
30 | |||
fillParserOutput | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
30 | |||
generateHtml | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
2 | |||
getWorkflowLoader | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace Flow\Content; |
4 | |
5 | use Article; |
6 | use Content; |
7 | use DerivativeContext; |
8 | use Flow\Actions\FlowAction; |
9 | use Flow\Container; |
10 | use Flow\Diff\FlowBoardContentDiffView; |
11 | use Flow\FlowActions; |
12 | use Flow\LinksTableUpdater; |
13 | use Flow\Model\UUID; |
14 | use Flow\View; |
15 | use Flow\WorkflowLoaderFactory; |
16 | use FormatJson; |
17 | use IContextSource; |
18 | use InvalidArgumentException; |
19 | use MediaWiki\Content\Renderer\ContentParseParams; |
20 | use MediaWiki\MediaWikiServices; |
21 | use MediaWiki\Output\OutputPage; |
22 | use MediaWiki\Request\FauxRequest; |
23 | use MediaWiki\Title\Title; |
24 | use MediaWiki\User\User; |
25 | use ParserOutput; |
26 | use RequestContext; |
27 | |
28 | class BoardContentHandler extends \ContentHandler { |
29 | public function __construct( $modelId ) { |
30 | if ( $modelId !== CONTENT_MODEL_FLOW_BOARD ) { |
31 | throw new InvalidArgumentException( __CLASS__ . " initialised for invalid content model" ); |
32 | } |
33 | |
34 | parent::__construct( CONTENT_MODEL_FLOW_BOARD, [ CONTENT_FORMAT_JSON ] ); |
35 | } |
36 | |
37 | protected function getDiffEngineClass() { |
38 | return FlowBoardContentDiffView::class; |
39 | } |
40 | |
41 | public function isSupportedFormat( $format ) { |
42 | // Necessary for backwards-compatability where |
43 | // the format "json" was used |
44 | if ( $format === 'json' ) { |
45 | $format = CONTENT_FORMAT_JSON; |
46 | } |
47 | |
48 | return parent::isSupportedFormat( $format ); |
49 | } |
50 | |
51 | /** |
52 | * Serializes a Content object of the type supported by this ContentHandler. |
53 | * |
54 | * @since 1.21 |
55 | * |
56 | * @param \Content $content The Content object to serialize |
57 | * @param string|null $format The desired serialization format |
58 | * @return string Serialized form of the content |
59 | */ |
60 | public function serializeContent( \Content $content, $format = null ) { |
61 | if ( !$content instanceof BoardContent ) { |
62 | throw new InvalidArgumentException( "Expected a BoardContent object, got a " . get_class( $content ) ); |
63 | } |
64 | |
65 | $info = []; |
66 | |
67 | if ( $content->getWorkflowId() ) { |
68 | $info['flow-workflow'] = $content->getWorkflowId()->getAlphadecimal(); |
69 | } |
70 | |
71 | return FormatJson::encode( $info ); |
72 | } |
73 | |
74 | /** |
75 | * Unserializes a Content object of the type supported by this ContentHandler. |
76 | * |
77 | * @since 1.21 |
78 | * |
79 | * @param string $blob Serialized form of the content |
80 | * @param string|null $format The format used for serialization |
81 | * |
82 | * @return BoardContent The Content object created by deserializing $blob |
83 | */ |
84 | public function unserializeContent( $blob, $format = null ) { |
85 | $info = FormatJson::decode( $blob, true ); |
86 | $uuid = null; |
87 | |
88 | if ( !$info ) { |
89 | // Temporary: Fix T167198 and instead throw an exception, to |
90 | // prevent corruption from software that does not understand |
91 | // Flow/content models. |
92 | |
93 | return $this->makeEmptyContent(); |
94 | } elseif ( isset( $info['flow-workflow'] ) ) { |
95 | $uuid = UUID::create( $info['flow-workflow'] ); |
96 | } |
97 | |
98 | return new BoardContent( CONTENT_MODEL_FLOW_BOARD, $uuid ); |
99 | } |
100 | |
101 | /** |
102 | * Creates an empty Content object of the type supported by this |
103 | * ContentHandler. |
104 | * |
105 | * @since 1.21 |
106 | * |
107 | * @return BoardContent |
108 | */ |
109 | public function makeEmptyContent() { |
110 | return new BoardContent; |
111 | } |
112 | |
113 | /** |
114 | * Don't let people turn random pages into Flow ones. They either need to be: |
115 | * * in a Flow-enabled namespace already (where content model is flow-board by |
116 | * default). In such a namespace, non-existent pages are created as Flow. |
117 | * * explicitly allowed for a user, requiring special permissions |
118 | * |
119 | * @param Title $title |
120 | * @return bool |
121 | */ |
122 | public function canBeUsedOn( Title $title ) { |
123 | /** @var \Flow\TalkpageManager $manager */ |
124 | $manager = Container::get( 'occupation_controller' ); |
125 | |
126 | /** @var User $user */ |
127 | $user = Container::get( 'user' ); |
128 | |
129 | return $manager->canBeUsedOn( $title, $user ); |
130 | } |
131 | |
132 | /** |
133 | * Returns overrides for action handlers. |
134 | * Classes listed here will be used instead of the default one when |
135 | * (and only when) $wgActions[$action] === true. This allows subclasses |
136 | * to override the default action handlers. |
137 | * |
138 | * @since 1.21 |
139 | * |
140 | * @return array Associative array mapping action names to handler callables |
141 | */ |
142 | public function getActionOverrides() { |
143 | /** @var FlowActions $actions */ |
144 | $actions = Container::get( 'flow_actions' ); |
145 | $output = []; |
146 | |
147 | foreach ( $actions->getActions() as $action ) { |
148 | $actionData = $actions->getValue( $action ); |
149 | if ( !is_array( $actionData ) ) { |
150 | continue; |
151 | } |
152 | |
153 | if ( !isset( $actionData['handler-class'] ) ) { |
154 | continue; |
155 | } |
156 | |
157 | if ( $actionData['handler-class'] === FlowAction::class ) { |
158 | $output[$action] = static function ( |
159 | Article $article, |
160 | IContextSource $source |
161 | ) use ( $action ) { |
162 | return new FlowAction( $article, $source, $action ); |
163 | }; |
164 | } else { |
165 | $output[$action] = $actionData['handler-class']; |
166 | } |
167 | } |
168 | |
169 | // Flow has its own handling for action=edit |
170 | $output['edit'] = \Flow\Actions\EditAction::class; |
171 | |
172 | return $output; |
173 | } |
174 | |
175 | /** |
176 | * @inheritDoc |
177 | */ |
178 | protected function fillParserOutput( |
179 | Content $content, |
180 | ContentParseParams $cpoParams, |
181 | ParserOutput &$output |
182 | ) { |
183 | '@phan-var BoardContent $content'; |
184 | $parserOptions = $cpoParams->getParserOptions(); |
185 | $revId = $cpoParams->getRevId(); |
186 | $title = Title::castFromPageReference( $cpoParams->getPage() ) |
187 | ?: Title::makeTitle( NS_MEDIAWIKI, 'BadTitle/Flow' ); |
188 | if ( $cpoParams->getGenerateHtml() ) { |
189 | try { |
190 | $user = MediaWikiServices::getInstance() |
191 | ->getUserFactory() |
192 | ->newFromUserIdentity( $parserOptions->getUserIdentity() ); |
193 | $this->generateHtml( $title, $user, $content, $output ); |
194 | } catch ( \Exception $e ) { |
195 | // Workflow does not yet exist (may be in the process of being created) |
196 | $output->setText( '' ); |
197 | } |
198 | } |
199 | |
200 | $output->updateCacheExpiry( 0 ); |
201 | |
202 | if ( $revId === null ) { |
203 | $wikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title ); |
204 | $timestamp = $wikiPage->getTimestamp(); |
205 | } else { |
206 | $timestamp = MediaWikiServices::getInstance()->getRevisionLookup() |
207 | ->getTimestampFromId( $revId ); |
208 | } |
209 | |
210 | $output->setTimestamp( $timestamp ); |
211 | |
212 | /** @var LinksTableUpdater $updater */ |
213 | $updater = Container::get( 'reference.updater.links-tables' ); |
214 | $updater->mutateParserOutput( $title, $output ); |
215 | } |
216 | |
217 | /** |
218 | * @param Title $title |
219 | * @param User $user |
220 | * @param BoardContent $content |
221 | * @param ParserOutput $output |
222 | */ |
223 | protected function generateHtml( |
224 | Title $title, |
225 | User $user, |
226 | BoardContent $content, |
227 | ParserOutput $output |
228 | ) { |
229 | // Set up a derivative context (which inherits the current request) |
230 | // to hold the output modules + text |
231 | $childContext = new DerivativeContext( RequestContext::getMain() ); |
232 | $childContext->setOutput( new OutputPage( $childContext ) ); |
233 | $childContext->setRequest( new FauxRequest ); |
234 | $childContext->setUser( $user ); |
235 | |
236 | // Create a View set up to output to our derivative context |
237 | $view = new View( |
238 | Container::get( 'url_generator' ), |
239 | Container::get( 'lightncandy' ), |
240 | $childContext->getOutput(), |
241 | Container::get( 'flow_actions' ) |
242 | ); |
243 | |
244 | $loader = $this->getWorkflowLoader( $title, $content ); |
245 | $view->show( $loader, 'view' ); |
246 | |
247 | // Extract data from derivative context |
248 | $output->setText( $childContext->getOutput()->getHTML() ); |
249 | $output->addModules( $childContext->getOutput()->getModules() ); |
250 | $output->addModuleStyles( $childContext->getOutput()->getModuleStyles() ); |
251 | } |
252 | |
253 | /** |
254 | * @param Title $title |
255 | * @param BoardContent $content |
256 | * @return \Flow\WorkflowLoader |
257 | * @throws \Flow\Exception\CrossWikiException |
258 | * @throws \Flow\Exception\InvalidInputException |
259 | */ |
260 | protected function getWorkflowLoader( Title $title, BoardContent $content ) { |
261 | /** @var WorkflowLoaderFactory $factory */ |
262 | $factory = Container::get( 'factory.loader.workflow' ); |
263 | return $factory->createWorkflowLoader( $title, $content->getWorkflowId() ); |
264 | } |
265 | } |