Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 96 |
|
0.00% |
0 / 2 |
CRAP | |
0.00% |
0 / 1 |
FlowFixInconsistentBoards | |
0.00% |
0 / 90 |
|
0.00% |
0 / 2 |
342 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 81 |
|
0.00% |
0 / 1 |
306 |
1 | <?php |
2 | |
3 | namespace Flow\Maintenance; |
4 | |
5 | use BatchRowIterator; |
6 | use Flow\BoardMover; |
7 | use Flow\Container; |
8 | use Flow\Content\BoardContent; |
9 | use Flow\Data\ManagerGroup; |
10 | use Flow\DbFactory; |
11 | use Flow\Exception\UnknownWorkflowIdException; |
12 | use Flow\WorkflowLoaderFactory; |
13 | use Maintenance; |
14 | use MediaWiki\Revision\RevisionRecord; |
15 | use MediaWiki\Revision\SlotRecord; |
16 | use MediaWiki\Title\Title; |
17 | use MediaWiki\WikiMap\WikiMap; |
18 | |
19 | $IP = getenv( 'MW_INSTALL_PATH' ); |
20 | if ( $IP === false ) { |
21 | $IP = __DIR__ . '/../../..'; |
22 | } |
23 | |
24 | require_once "$IP/maintenance/Maintenance.php"; |
25 | |
26 | /** |
27 | * Changes Flow boards and their topics to be associated with their current title, based on the JSON content |
28 | * Fixes inconsistent bugs like T138310. |
29 | * |
30 | * There is a dry run available. |
31 | * |
32 | * @ingroup Maintenance |
33 | */ |
34 | class FlowFixInconsistentBoards extends Maintenance { |
35 | /** |
36 | * @var DbFactory |
37 | */ |
38 | protected $dbFactory; |
39 | |
40 | /** |
41 | * @var WorkflowLoaderFactory |
42 | */ |
43 | protected $workflowLoaderFactory; |
44 | |
45 | /** |
46 | * @var BoardMover |
47 | */ |
48 | protected $boardMover; |
49 | |
50 | /** |
51 | * @var ManagerGroup |
52 | */ |
53 | protected $storage; |
54 | |
55 | public function __construct() { |
56 | parent::__construct(); |
57 | |
58 | $this->addDescription( 'Changes Flow boards and their topics to be associated with their ' . |
59 | 'current title, based on the JSON content. Must be run separately for each affected wiki.' ); |
60 | |
61 | $this->addOption( 'dry-run', 'Only prints the board names, without changing anything.' ); |
62 | $this->addOption( 'namespaceName', 'Name of namespace to check, otherwise all', false, true ); |
63 | $this->addOption( 'limit', 'Limit of inconsistent pages to identify (and fix if not a dry ' . |
64 | 'run). Defaults to no limit', false, true ); |
65 | |
66 | $this->setBatchSize( 300 ); |
67 | |
68 | $this->requireExtension( 'Flow' ); |
69 | } |
70 | |
71 | /** |
72 | * @return false|void |
73 | */ |
74 | public function execute() { |
75 | global $wgLang; |
76 | |
77 | $this->dbFactory = Container::get( 'db.factory' ); |
78 | $this->workflowLoaderFactory = Container::get( 'factory.loader.workflow' ); |
79 | $this->boardMover = Container::get( 'board_mover' ); |
80 | $this->storage = Container::get( 'storage' ); |
81 | |
82 | $dryRun = $this->hasOption( 'dry-run' ); |
83 | |
84 | $limit = $this->getOption( 'limit' ); |
85 | |
86 | $wikiDbw = $this->dbFactory->getWikiDB( DB_PRIMARY ); |
87 | |
88 | $iterator = new BatchRowIterator( $wikiDbw, 'page', 'page_id', $this->getBatchSize() ); |
89 | $iterator->setFetchColumns( [ 'page_namespace', 'page_title', 'page_latest' ] ); |
90 | $iterator->addConditions( [ |
91 | 'page_content_model' => CONTENT_MODEL_FLOW_BOARD, |
92 | ] ); |
93 | $iterator->setCaller( __METHOD__ ); |
94 | |
95 | if ( $this->hasOption( 'namespaceName' ) ) { |
96 | $namespaceName = $this->getOption( 'namespaceName' ); |
97 | $namespaceId = $wgLang->getNsIndex( $namespaceName ); |
98 | |
99 | if ( !$namespaceId ) { |
100 | $this->error( "'$namespaceName' is not a valid namespace name" ); |
101 | return false; |
102 | } |
103 | |
104 | if ( $namespaceId == NS_TOPIC ) { |
105 | $this->error( 'This script can not be run on the Flow topic namespace' ); |
106 | return false; |
107 | } |
108 | |
109 | $iterator->addConditions( [ |
110 | 'page_namespace' => $namespaceId, |
111 | ] ); |
112 | } else { |
113 | $iterator->addConditions( [ |
114 | $wikiDbw->expr( 'page_namespace', '!=', NS_TOPIC ), |
115 | ] ); |
116 | } |
117 | |
118 | $checkedCount = 0; |
119 | $inconsistentCount = 0; |
120 | |
121 | // Not all of $inconsistentCount are fixable by the current script. |
122 | $fixableInconsistentCount = 0; |
123 | |
124 | foreach ( $iterator as $rows ) { |
125 | foreach ( $rows as $row ) { |
126 | $checkedCount++; |
127 | $coreTitle = Title::makeTitle( $row->page_namespace, $row->page_title ); |
128 | $revision = $this->getServiceContainer()->getRevisionLookup()->getRevisionById( $row->page_latest ); |
129 | $content = $revision->getContent( SlotRecord::MAIN, RevisionRecord::RAW ); |
130 | if ( !$content instanceof BoardContent ) { |
131 | $actualClass = get_debug_type( $content ); |
132 | $this->error( "ERROR: '$coreTitle' content is a '$actualClass', but should be '" |
133 | . BoardContent::class . "'." ); |
134 | continue; |
135 | } |
136 | $workflowId = $content->getWorkflowId(); |
137 | if ( $workflowId === null ) { |
138 | // See T153320. If the workflow exists, it could |
139 | // be looked up by title/page ID and the JSON could |
140 | // be fixed with an edit. |
141 | // Otherwise, the core revision has to be deleted. This |
142 | // script does not do either of these things. |
143 | $this->error( "ERROR: '$coreTitle' JSON content does not have a valid workflow ID." ); |
144 | continue; |
145 | } |
146 | |
147 | $workflowIdAlphadecimal = $workflowId->getAlphadecimal(); |
148 | |
149 | try { |
150 | $workflow = $this->workflowLoaderFactory->loadWorkflowById( false, $workflowId ); |
151 | } catch ( UnknownWorkflowIdException $ex ) { |
152 | // This is a different error (a core page refers to |
153 | // a non-existent workflow), which this script can not fix. |
154 | $this->error( "ERROR: '$coreTitle' refers to workflow ID " . |
155 | "'$workflowIdAlphadecimal', which could not be found." ); |
156 | continue; |
157 | } |
158 | |
159 | if ( !$workflow->matchesTitle( $coreTitle ) ) { |
160 | $pageId = (int)$row->page_id; |
161 | |
162 | $workflowTitle = $workflow->getOwnerTitle(); |
163 | $this->output( "INCONSISTENT: Core title for '$workflowIdAlphadecimal' is " . |
164 | "'$coreTitle', but Flow title is '$workflowTitle'\n" ); |
165 | |
166 | $inconsistentCount++; |
167 | |
168 | // Sanity check, or this will fail in BoardMover |
169 | $workflowByPageId = $this->storage->find( 'Workflow', [ |
170 | 'workflow_wiki' => WikiMap::getCurrentWikiId(), |
171 | 'workflow_page_id' => $pageId, |
172 | ] ); |
173 | |
174 | if ( !$workflowByPageId ) { |
175 | $this->error( "ERROR: '$coreTitle' has page ID '$pageId', but no workflow " . |
176 | "is linked to this page ID" ); |
177 | continue; |
178 | } |
179 | |
180 | if ( !$dryRun ) { |
181 | $this->boardMover->move( $pageId, $coreTitle ); |
182 | $this->boardMover->commit(); |
183 | $this->output( "FIXED: Updated '$workflowIdAlphadecimal' to match core " . |
184 | "title, '$coreTitle'\n" ); |
185 | } |
186 | |
187 | $fixableInconsistentCount++; |
188 | |
189 | if ( $limit !== null && $fixableInconsistentCount >= $limit ) { |
190 | break; |
191 | } |
192 | } |
193 | } |
194 | |
195 | $action = $dryRun ? 'identified as fixable' : 'fixed'; |
196 | $this->output( "\nChecked a total of $checkedCount Flow boards. Of those, " . |
197 | "$inconsistentCount boards had an inconsistent title; $fixableInconsistentCount " . |
198 | "were $action.\n" ); |
199 | if ( $limit !== null && $fixableInconsistentCount >= $limit ) { |
200 | break; |
201 | } |
202 | } |
203 | } |
204 | } |
205 | |
206 | $maintClass = FlowFixInconsistentBoards::class; |
207 | require_once RUN_MAINTENANCE_IF_MAIN; |