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