Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 79
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
BoardContentHandler
0.00% covered (danger)
0.00%
0 / 79
0.00% covered (danger)
0.00%
0 / 11
650
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getDiffEngineClass
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isSupportedFormat
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 serializeContent
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 unserializeContent
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 makeEmptyContent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 canBeUsedOn
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getActionOverrides
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 fillParserOutput
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
30
 generateHtml
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
2
 getWorkflowLoader
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace Flow\Content;
4
5use Article;
6use Content;
7use DerivativeContext;
8use Flow\Actions\FlowAction;
9use Flow\Container;
10use Flow\Diff\FlowBoardContentDiffView;
11use Flow\FlowActions;
12use Flow\LinksTableUpdater;
13use Flow\Model\UUID;
14use Flow\View;
15use Flow\WorkflowLoaderFactory;
16use FormatJson;
17use IContextSource;
18use InvalidArgumentException;
19use MediaWiki\Content\Renderer\ContentParseParams;
20use MediaWiki\MediaWikiServices;
21use MediaWiki\Output\OutputPage;
22use MediaWiki\Request\FauxRequest;
23use MediaWiki\Title\Title;
24use MediaWiki\User\User;
25use ParserOutput;
26use RequestContext;
27
28class 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}