Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
19.64% covered (danger)
19.64%
11 / 56
37.50% covered (danger)
37.50%
3 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
TalkpageManager
19.64% covered (danger)
19.64%
11 / 56
37.50% covered (danger)
37.50%
3 / 8
297.49
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 ensureFlowRevision
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
 checkIfCreationIsPossible
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 checkIfUserHasPermission
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 safeAllowCreation
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 forceAllowCreation
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 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getTalkpageManager
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2
3namespace Flow;
4
5use Flow\Content\BoardContent;
6use Flow\Exception\InvalidInputException;
7use Flow\Model\Workflow;
8use MediaWiki\Extension\CentralAuth\User\CentralAuthUser;
9use MediaWiki\MediaWikiServices;
10use MediaWiki\Page\WikiPage;
11use MediaWiki\Registration\ExtensionRegistry;
12use MediaWiki\Revision\SlotRecord;
13use MediaWiki\Status\Status;
14use MediaWiki\Title\Title;
15use MediaWiki\User\User;
16use MediaWiki\User\UserGroupManager;
17use MediaWiki\WikiMap\WikiMap;
18use Wikimedia\Rdbms\IDBAccessObject;
19
20class TalkpageManager implements OccupationController {
21    /**
22     * @var UserGroupManager
23     */
24    private $userGroupManager;
25
26    /**
27     * @var string[]
28     */
29    protected $allowedPageNames = [];
30
31    /**
32     * Cached talk page manager user
33     * @var User
34     */
35    protected $talkPageManagerUser;
36
37    public function __construct( UserGroupManager $userGroupManager ) {
38        $this->userGroupManager = $userGroupManager;
39    }
40
41    /**
42     * When a page is taken over by Flow, add a revision.
43     *
44     * First, it provides a clearer history should Flow be disabled again later,
45     * and a descriptive message when people attempt to use regular API to fetch
46     * data for this "Page", which will no longer contain any useful content,
47     * since Flow has taken over.
48     *
49     * Also: Parsoid performs an API call to fetch page information, so we need
50     * to make sure a page actually exists ;)
51     *
52     * This method does not do any security checks regarding content model changes
53     * or the like.  Those happen much earlier in the request and should be checked
54     * before even attempting to create revisions.
55     *
56     * @param WikiPage $page
57     * @param Workflow $workflow
58     * @return Status<array> Status for revision creation; On success (including if it already
59     *  had a top-most Flow revision), it will return a good status with an associative
60     *  array value.  $status->getValue()['revision-record'] will be a RevisionRecord
61     *  $status->getValue()['already-existed'] will be set to true if no revision needed
62     *  to be created
63     * @throws InvalidInputException
64     */
65    public function ensureFlowRevision( WikiPage $page, Workflow $workflow ) {
66        $revision = $page->getRevisionRecord();
67
68        if ( $revision !== null ) {
69            $content = $revision->getContent( SlotRecord::MAIN );
70            if ( $content instanceof BoardContent && $content->getWorkflowId() ) {
71                // Revision is already a valid BoardContent
72                return Status::newGood( [
73                    'revision' => $revision,
74                    'already-existed' => true,
75                ] );
76            }
77        }
78
79        $status = $page->doUserEditContent(
80            new BoardContent( CONTENT_MODEL_FLOW_BOARD, $workflow->getId() ),
81            $this->getTalkpageManager(),
82            wfMessage( 'flow-talk-taken-over-comment' )->plain(),
83            EDIT_FORCE_BOT | EDIT_SUPPRESS_RC
84        );
85        $value = $status->getValue();
86        $value['already-existed'] = false;
87        $status->setResult( $status->isOK(), $value );
88
89        return $status;
90    }
91
92    /**
93     * @inheritDoc
94     */
95    public function checkIfCreationIsPossible( Title $title, $mustNotExist = true, $forWrite = true ) {
96        // Only allow converting a non-existent page to Flow
97        if ( $mustNotExist ) {
98            if ( $title->exists( $forWrite ? IDBAccessObject::READ_LATEST : 0 ) ) {
99                return Status::newFatal( 'flow-error-allowcreation-already-exists' );
100            }
101        }
102
103        return Status::newGood();
104    }
105
106    /**
107     * @inheritDoc
108     */
109    public function checkIfUserHasPermission( Title $title, User $user ) {
110        $services = MediaWikiServices::getInstance();
111        if (
112            // If the title is default-Flow, the user always has permission
113            $services->getSlotRoleRegistry()->getRoleHandler( SlotRecord::MAIN )
114                ->getDefaultModel( $title ) === CONTENT_MODEL_FLOW_BOARD
115        ) {
116            return Status::newGood();
117        } else {
118            // Gate this on the flow-create-board right, essentially giving
119            // wiki communities control over if Flow board creation is allowed
120            // to everyone or just a select few.
121            return Status::wrap( $services->getPermissionManager()
122                ->getPermissionStatus( 'flow-create-board', $user, $title ) );
123        }
124    }
125
126    /**
127     * @inheritDoc
128     */
129    public function safeAllowCreation( Title $title, User $user, $mustNotExist = true, $forWrite = true ) {
130        $status = Status::newGood();
131
132        $technicallyAllowedStatus = $this->checkIfCreationIsPossible( $title, $mustNotExist, $forWrite );
133
134        $permissionStatus = $this->checkIfUserHasPermission( $title, $user );
135
136        $status->merge( $technicallyAllowedStatus );
137        $status->merge( $permissionStatus );
138
139        if ( $status->isOK() ) {
140            $this->forceAllowCreation( $title );
141        }
142
143        return $status;
144    }
145
146    /**
147     * @inheritDoc
148     */
149    public function forceAllowCreation( Title $title ) {
150        /*
151         * Tracks which titles are allowed so that when
152         * BoardContentHandler::canBeUsedOn is called for this title, it
153         * can verify this title was explicitly allowed.
154         */
155        $this->allowedPageNames[] = $title->getPrefixedDBkey();
156    }
157
158    /**
159     * Before creating a flow board, BoardContentHandler::canBeUsedOn will be
160     * called to verify it's ok to create it.
161     * That, in turn, will call this, which will check if the title we want to
162     * turn into a Flow board was allowed to create (with allowedPageNames)
163     *
164     * @param Title $title
165     * @param User $user
166     * @return bool
167     */
168    public function canBeUsedOn( Title $title, User $user ) {
169        // If the user has rights, mark the page as allowed
170        // For MovePage
171        $this->safeAllowCreation( $title, $user, /* $mustNotExist = */ true );
172
173        return // default content model already
174            MediaWikiServices::getInstance()->getSlotRoleRegistry()->getRoleHandler( SlotRecord::MAIN )
175                ->getDefaultModel( $title ) === CONTENT_MODEL_FLOW_BOARD ||
176
177            // explicitly allowed via safeAllowCreation()
178            in_array( $title->getPrefixedDBkey(), $this->allowedPageNames );
179    }
180
181    /**
182     * Gives a user object used to manage talk pages
183     *
184     * @return User User to manage talkpages
185     */
186    public function getTalkpageManager() {
187        if ( $this->talkPageManagerUser !== null ) {
188            return $this->talkPageManagerUser;
189        }
190
191        $user = User::newSystemUser( FLOW_TALK_PAGE_MANAGER_USER, [ 'steal' => true ] );
192
193        if ( ExtensionRegistry::getInstance()->isLoaded( 'CentralAuth' ) ) {
194            // Attach to CentralAuth if a global account already
195            // exists
196            $ca = CentralAuthUser::getPrimaryInstance( $user );
197            if ( $ca->exists() && !$ca->isAttached() ) {
198                $ca->attach( WikiMap::getCurrentWikiId(), 'admin' );
199            }
200        }
201
202        $groups = $this->userGroupManager->getUserGroups( $user );
203        foreach ( [ 'bot', 'flow-bot' ] as $group ) {
204            if ( !in_array( $group, $groups ) ) {
205                $this->userGroupManager->addUserToGroup( $user, $group );
206            }
207        }
208
209        $this->talkPageManagerUser = $user;
210        return $user;
211    }
212}