Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 111 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
ChangeWikiConfig | |
0.00% |
0 / 105 |
|
0.00% |
0 / 7 |
600 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 38 |
|
0.00% |
0 / 1 |
2 | |||
initServices | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
initConfigWriter | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
execute | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
30 | |||
saveChange | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
72 | |||
rawPageTitleEquals | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
touchConfigPage | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments\Maintenance; |
4 | |
5 | use GrowthExperiments\Config\WikiPageConfigWriter; |
6 | use GrowthExperiments\Config\WikiPageConfigWriterFactory; |
7 | use GrowthExperiments\GrowthExperimentsServices; |
8 | use InvalidArgumentException; |
9 | use MediaWiki\Json\FormatJson; |
10 | use MediaWiki\Maintenance\Maintenance; |
11 | use MediaWiki\Status\Status; |
12 | use MediaWiki\Title\TitleFactory; |
13 | use MediaWiki\User\User; |
14 | |
15 | $IP = getenv( 'MW_INSTALL_PATH' ); |
16 | if ( $IP === false ) { |
17 | $IP = __DIR__ . '/../../..'; |
18 | } |
19 | require_once "$IP/maintenance/Maintenance.php"; |
20 | |
21 | class ChangeWikiConfig extends Maintenance { |
22 | /** @var TitleFactory */ |
23 | private $titleFactory; |
24 | |
25 | /** @var WikiPageConfigWriterFactory */ |
26 | private $wikiPageConfigWriterFactory; |
27 | |
28 | public function __construct() { |
29 | parent::__construct(); |
30 | $this->requireExtension( 'GrowthExperiments' ); |
31 | $this->addDescription( 'Update a config key in on-wiki config' ); |
32 | |
33 | $this->addOption( |
34 | 'json', |
35 | 'If true, input value will be treated as JSON, not as string' |
36 | ); |
37 | $this->addOption( |
38 | 'page', |
39 | 'Page that will be changed (defaults to GEWikiConfigPageTitle)', |
40 | false, |
41 | true |
42 | ); |
43 | $this->addOption( |
44 | 'summary', |
45 | 'Edit summary to use', |
46 | false, |
47 | true |
48 | ); |
49 | $this->addOption( |
50 | 'touch', |
51 | 'Make a null edit to the page (useful to reflect config serialization changes); ' |
52 | . 'not supported for all config pages' |
53 | ); |
54 | |
55 | $this->addArg( |
56 | 'key', |
57 | 'Config key that is updated (use . to separate keys in a multidimensional array)', |
58 | false |
59 | ); |
60 | $this->addArg( |
61 | 'value', |
62 | 'New value of the config key', |
63 | false |
64 | ); |
65 | $this->addOption( |
66 | 'create-only', |
67 | 'Create the field if it doesn\'t exist but do not overwrite it if it does' |
68 | ); |
69 | } |
70 | |
71 | private function initServices() { |
72 | $services = $this->getServiceContainer(); |
73 | |
74 | $this->wikiPageConfigWriterFactory = GrowthExperimentsServices::wrap( $services ) |
75 | ->getWikiPageConfigWriterFactory(); |
76 | $this->titleFactory = $services->getTitleFactory(); |
77 | } |
78 | |
79 | private function initConfigWriter(): WikiPageConfigWriter { |
80 | $rawConfigPage = $this->getOption( |
81 | 'page', |
82 | $this->getConfig()->get( 'GEWikiConfigPageTitle' ) |
83 | ); |
84 | $configPage = $this->titleFactory->newFromText( $rawConfigPage ); |
85 | if ( $configPage === null ) { |
86 | $this->fatalError( "$rawConfigPage is not a valid title." ); |
87 | } |
88 | |
89 | try { |
90 | '@phan-var \MediaWiki\Linker\LinkTarget $configPage'; |
91 | return $this->wikiPageConfigWriterFactory->newWikiPageConfigWriter( |
92 | $configPage |
93 | ); |
94 | } catch ( InvalidArgumentException $e ) { |
95 | $this->fatalError( "$rawConfigPage is not a supported config page" ); |
96 | } |
97 | } |
98 | |
99 | /** |
100 | * @inheritDoc |
101 | */ |
102 | public function execute() { |
103 | $this->initServices(); |
104 | |
105 | $touch = $this->hasOption( 'touch' ); |
106 | $key = $this->getArg( 0 ); |
107 | $value = $this->getArg( 1 ); |
108 | |
109 | if ( !$touch && ( $key === null || $value === null ) ) { |
110 | $this->fatalError( 'Key and value are required when --touch is not used.' ); |
111 | } |
112 | |
113 | if ( !$touch ) { |
114 | $this->saveChange( $key, $value ); |
115 | } else { |
116 | $this->touchConfigPage(); |
117 | } |
118 | } |
119 | |
120 | /** |
121 | * Make a change to the config page |
122 | * |
123 | * @param mixed $key |
124 | * @param mixed $value |
125 | * @return void |
126 | */ |
127 | private function saveChange( $key, $value ) { |
128 | $configWriter = $this->initConfigWriter(); |
129 | |
130 | if ( strpos( $key, '.' ) !== false ) { |
131 | $key = explode( '.', $key ); |
132 | } |
133 | |
134 | if ( $this->hasOption( 'json' ) ) { |
135 | $status = FormatJson::parse( $value, FormatJson::FORCE_ASSOC ); |
136 | if ( !$status->isGood() ) { |
137 | $this->fatalError( |
138 | "Unable to decode JSON to use with $key: $value. Error from FormatJson::parse: " . |
139 | $status->getWikiText( false, false, 'en' ) |
140 | ); |
141 | } |
142 | $value = $status->getValue(); |
143 | } |
144 | try { |
145 | if ( $this->hasOption( 'create-only' ) |
146 | && $configWriter->variableExists( $key ) |
147 | ) { |
148 | return; |
149 | } |
150 | $configWriter->setVariable( $key, $value ); |
151 | } catch ( InvalidArgumentException $e ) { |
152 | $this->fatalError( $e->getMessage() ); |
153 | } |
154 | |
155 | $status = $configWriter->save( $this->getOption( 'summary', '' ) ); |
156 | if ( !$status->isOK() ) { |
157 | $this->fatalError( $status->getWikiText() ); |
158 | } |
159 | |
160 | $this->output( "Saved!\n" ); |
161 | } |
162 | |
163 | /** |
164 | * @param string $rawPageA Raw page title |
165 | * @param string $rawPageB Raw page title |
166 | * @return bool |
167 | */ |
168 | private function rawPageTitleEquals( string $rawPageA, string $rawPageB ): bool { |
169 | $pageA = $this->titleFactory->newFromText( $rawPageA ); |
170 | $pageB = $this->titleFactory->newFromText( $rawPageB ); |
171 | if ( $pageA === null || $pageB === null ) { |
172 | return false; |
173 | } |
174 | return $pageA->equals( $pageB ); |
175 | } |
176 | |
177 | /** |
178 | * If supported, make a no-op change |
179 | */ |
180 | private function touchConfigPage() { |
181 | $rawConfigPage = $this->getOption( |
182 | 'page', |
183 | $this->getConfig()->get( 'GEWikiConfigPageTitle' ) |
184 | ); |
185 | |
186 | if ( $this->rawPageTitleEquals( $rawConfigPage, $this->getConfig()->get( 'GEStructuredMentorList' ) ) ) { |
187 | $statusValue = GrowthExperimentsServices::wrap( $this->getServiceContainer() ) |
188 | ->getMentorWriter() |
189 | ->touchList( |
190 | User::newSystemUser( 'Maintenance script', [ 'steal' => true ] ), |
191 | $this->getOption( 'summary', '' ) |
192 | ); |
193 | if ( !$statusValue->isOK() ) { |
194 | $this->fatalError( Status::wrap( $statusValue )->getWikiText() ); |
195 | } |
196 | $this->output( "Saved!\n" ); |
197 | } else { |
198 | $this->fatalError( '--touch is not supported for ' . $rawConfigPage ); |
199 | } |
200 | } |
201 | } |
202 | |
203 | $maintClass = ChangeWikiConfig::class; |
204 | require_once RUN_MAINTENANCE_IF_MAIN; |