Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
19.64% |
11 / 56 |
|
37.50% |
3 / 8 |
CRAP | |
0.00% |
0 / 1 |
| TalkpageManager | |
19.64% |
11 / 56 |
|
37.50% |
3 / 8 |
297.49 | |
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% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
| 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\Page\WikiPage; |
| 11 | use MediaWiki\Registration\ExtensionRegistry; |
| 12 | use MediaWiki\Revision\SlotRecord; |
| 13 | use MediaWiki\Status\Status; |
| 14 | use MediaWiki\Title\Title; |
| 15 | use MediaWiki\User\User; |
| 16 | use MediaWiki\User\UserGroupManager; |
| 17 | use MediaWiki\WikiMap\WikiMap; |
| 18 | use Wikimedia\Rdbms\IDBAccessObject; |
| 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 | 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 | } |