Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
93.88% |
46 / 49 |
|
33.33% |
1 / 3 |
CRAP | |
0.00% |
0 / 1 |
Writer | |
93.88% |
46 / 49 |
|
33.33% |
1 / 3 |
11.03 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
save | |
96.55% |
28 / 29 |
|
0.00% |
0 / 1 |
7 | |||
runEditFilterMergedContentHook | |
88.24% |
15 / 17 |
|
0.00% |
0 / 1 |
3.01 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\CommunityConfiguration\Store\WikiPage; |
4 | |
5 | use Content; |
6 | use MediaWiki\CommentStore\CommentStoreComment; |
7 | use MediaWiki\Content\JsonContent; |
8 | use MediaWiki\Context\DerivativeContext; |
9 | use MediaWiki\Context\RequestContext; |
10 | use MediaWiki\HookContainer\HookContainer; |
11 | use MediaWiki\HookContainer\HookRunner; |
12 | use MediaWiki\Json\FormatJson; |
13 | use MediaWiki\Page\PageIdentity; |
14 | use MediaWiki\Page\WikiPageFactory; |
15 | use MediaWiki\Permissions\Authority; |
16 | use MediaWiki\Permissions\UltimateAuthority; |
17 | use MediaWiki\Revision\SlotRecord; |
18 | use MediaWiki\Status\Status; |
19 | use MediaWiki\Title\Title; |
20 | use MediaWiki\User\UserFactory; |
21 | use RecentChange; |
22 | |
23 | class Writer { |
24 | |
25 | private WikiPageFactory $wikiPageFactory; |
26 | private UserFactory $userFactory; |
27 | private HookContainer $hookContainer; |
28 | |
29 | /** |
30 | * @param WikiPageFactory $wikiPageFactory |
31 | * @param UserFactory $userFactory |
32 | * @param HookContainer $hookContainer |
33 | */ |
34 | public function __construct( |
35 | WikiPageFactory $wikiPageFactory, |
36 | UserFactory $userFactory, |
37 | HookContainer $hookContainer |
38 | ) { |
39 | $this->wikiPageFactory = $wikiPageFactory; |
40 | $this->userFactory = $userFactory; |
41 | $this->hookContainer = $hookContainer; |
42 | } |
43 | |
44 | /** |
45 | * Save a new version to the configuration page |
46 | * |
47 | * No permission changes or validation is performed. |
48 | * |
49 | * @param PageIdentity $configPage |
50 | * @param mixed $newConfig |
51 | * @param Authority $performer |
52 | * @param string $summary |
53 | * @param bool $minor |
54 | * @param array|string $tags Tag(s) to apply (defaults to none) |
55 | * @return Status |
56 | */ |
57 | public function save( |
58 | PageIdentity $configPage, |
59 | $newConfig, |
60 | Authority $performer, |
61 | string $summary = '', |
62 | bool $minor = false, |
63 | $tags = [] |
64 | ): Status { |
65 | // REVIEW: Should this validate $configPage is an acceptable target? |
66 | |
67 | // Sort config alphabetically |
68 | $configSorted = (array)$newConfig; |
69 | ksort( $configSorted ); |
70 | $status = Status::newGood(); |
71 | $content = new JsonContent( FormatJson::encode( (object)$configSorted ) ); |
72 | |
73 | $page = $this->wikiPageFactory->newFromTitle( $configPage ); |
74 | |
75 | // Give AbuseFilter et al. a chance to block the edit (T346235) |
76 | // Do not run when UltimateAuthority is used (from e.g. maintenance scripts), as in those |
77 | // cases, we want the edit to succeed regardless of permissions. |
78 | if ( !$performer instanceof UltimateAuthority ) { |
79 | $status->merge( $this->runEditFilterMergedContentHook( |
80 | $performer, |
81 | $page->getTitle(), |
82 | $content, |
83 | $summary, |
84 | $minor |
85 | ) ); |
86 | } |
87 | |
88 | if ( !$status->isOK() ) { |
89 | return $status; |
90 | } |
91 | |
92 | $updater = $page->newPageUpdater( $performer ); |
93 | if ( is_string( $tags ) ) { |
94 | $updater->addTag( $tags ); |
95 | } elseif ( is_array( $tags ) ) { |
96 | $updater->addTags( $tags ); |
97 | } |
98 | $updater->setContent( SlotRecord::MAIN, $content ); |
99 | |
100 | if ( $performer->isAllowed( 'autopatrol' ) ) { |
101 | $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED ); |
102 | } |
103 | |
104 | $updater->saveRevision( |
105 | CommentStoreComment::newUnsavedComment( $summary ), |
106 | $minor ? EDIT_MINOR : 0 |
107 | ); |
108 | $status->merge( $updater->getStatus() ); |
109 | |
110 | return $status; |
111 | } |
112 | |
113 | /** |
114 | * Run the EditFilterMergedContentHook |
115 | * |
116 | * @param Authority $performer |
117 | * @param Title $title |
118 | * @param Content $content |
119 | * @param string $summary |
120 | * @param bool $minor |
121 | * @return Status |
122 | */ |
123 | private function runEditFilterMergedContentHook( |
124 | Authority $performer, |
125 | Title $title, |
126 | Content $content, |
127 | string $summary, |
128 | bool $minor |
129 | ): Status { |
130 | $performerUser = $this->userFactory->newFromAuthority( $performer ); |
131 | |
132 | // Ensure context has right values for title and performer, which are available to the |
133 | // config writer. Use the global context for the rest. |
134 | $derivativeContext = new DerivativeContext( RequestContext::getMain() ); |
135 | $derivativeContext->setUser( $performerUser ); |
136 | $derivativeContext->setTitle( $title ); |
137 | |
138 | $status = new Status(); |
139 | $hookRunner = new HookRunner( $this->hookContainer ); |
140 | if ( !$hookRunner->onEditFilterMergedContent( |
141 | $derivativeContext, |
142 | $content, |
143 | $status, |
144 | $summary, |
145 | $performerUser, |
146 | $minor |
147 | ) ) { |
148 | if ( $status->isGood() ) { |
149 | $status->fatal( 'hookaborted' ); |
150 | } |
151 | } |
152 | return $status; |
153 | } |
154 | } |