addDescription( <<addOption( 'mode', '"add" empty categories with description pages, "remove" empty categories ' . 'without description pages, or "both"', false, true ); $this->addOption( 'begin', 'Only do categories whose names are alphabetically after the provided name', false, true ); $this->addOption( 'throttle', 'Wait this many milliseconds after each batch. Default: 0', false, true ); } protected function getUpdateKey() { return 'cleanup empty categories'; } protected function doDBUpdates() { $mode = $this->getOption( 'mode', 'both' ); $begin = $this->getOption( 'begin', '' ); $throttle = $this->getOption( 'throttle', 0 ); if ( !in_array( $mode, [ 'add', 'remove', 'both' ] ) ) { $this->output( "--mode must be 'add', 'remove', or 'both'.\n" ); return false; } $dbw = $this->getPrimaryDB(); $throttle = intval( $throttle ); if ( $mode === 'add' || $mode === 'both' ) { if ( $begin !== '' ) { $where = [ $dbw->expr( 'page_title', '>', $begin ) ]; } else { $where = []; } $this->output( "Adding empty categories with description pages...\n" ); while ( true ) { # Find which category to update $rows = $dbw->newSelectQueryBuilder() ->select( 'page_title' ) ->from( 'page' ) ->leftJoin( 'category', null, 'page_title = cat_title' ) ->where( $where ) ->andWhere( [ 'page_namespace' => NS_CATEGORY, 'cat_title' => null ] ) ->orderBy( 'page_title' ) ->limit( $this->getBatchSize() ) ->caller( __METHOD__ )->fetchResultSet(); if ( !$rows || $rows->numRows() <= 0 ) { break; } foreach ( $rows as $row ) { $name = $row->page_title; $where = [ $dbw->expr( 'page_title', '>', $name ) ]; # Use the row to update the category count $cat = Category::newFromName( $name ); if ( !is_object( $cat ) ) { $this->output( "The category named $name is not valid?!\n" ); } else { $cat->refreshCounts(); } } // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $rows has at at least one item $this->output( "--mode=$mode --begin=$name\n" ); $this->waitForReplication(); usleep( $throttle * 1000 ); } $begin = ''; } if ( $mode === 'remove' || $mode === 'both' ) { if ( $begin !== '' ) { $where = [ $dbw->expr( 'cat_title', '>', $begin ) ]; } else { $where = []; } $this->output( "Removing empty categories without description pages...\n" ); while ( true ) { # Find which category to update $rows = $dbw->newSelectQueryBuilder() ->select( 'cat_title' ) ->from( 'category' ) ->leftJoin( 'page', null, [ 'page_namespace' => NS_CATEGORY, 'page_title = cat_title' ] ) ->where( $where ) ->andWhere( [ 'page_title' => null, 'cat_pages' => 0 ] ) ->orderBy( 'cat_title' ) ->limit( $this->getBatchSize() ) ->caller( __METHOD__ )->fetchResultSet(); if ( !$rows || $rows->numRows() <= 0 ) { break; } foreach ( $rows as $row ) { $name = $row->cat_title; $where = [ $dbw->expr( 'cat_title', '>', $name ) ]; # Use the row to update the category count $cat = Category::newFromName( $name ); if ( !is_object( $cat ) ) { $this->output( "The category named $name is not valid?!\n" ); } else { $cat->refreshCounts(); } } // @phan-suppress-next-line PhanPossiblyUndeclaredVariable rows contains at least one item $this->output( "--mode=remove --begin=$name\n" ); $this->waitForReplication(); usleep( $throttle * 1000 ); } } $this->output( "Category cleanup complete.\n" ); return true; } } // @codeCoverageIgnoreStart $maintClass = CleanupEmptyCategories::class; require_once RUN_MAINTENANCE_IF_MAIN; // @codeCoverageIgnoreEnd