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