Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
21.05% |
12 / 57 |
|
37.50% |
3 / 8 |
CRAP | |
0.00% |
0 / 1 |
TalkpageManager | |
21.05% |
12 / 57 |
|
37.50% |
3 / 8 |
307.42 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
ensureFlowRevision | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
20 | |||
checkIfCreationIsPossible | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
checkIfUserHasPermission | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
safeAllowCreation | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
forceAllowCreation | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
canBeUsedOn | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getTalkpageManager | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
56 |
1 | <?php |
2 | |
3 | namespace Flow; |
4 | |
5 | use Flow\Content\BoardContent; |
6 | use Flow\Exception\InvalidInputException; |
7 | use Flow\Model\Workflow; |
8 | use MediaWiki\Extension\CentralAuth\User\CentralAuthUser; |
9 | use MediaWiki\MediaWikiServices; |
10 | use MediaWiki\Registration\ExtensionRegistry; |
11 | use MediaWiki\Revision\SlotRecord; |
12 | use MediaWiki\Status\Status; |
13 | use MediaWiki\Title\Title; |
14 | use MediaWiki\User\User; |
15 | use MediaWiki\User\UserGroupManager; |
16 | use MediaWiki\WikiMap\WikiMap; |
17 | use Wikimedia\Rdbms\IDBAccessObject; |
18 | use WikiPage; |
19 | |
20 | class 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 | } |