Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 75
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
GrepPages
0.00% covered (danger)
0.00%
0 / 72
0.00% covered (danger)
0.00%
0 / 4
420
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 init
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
132
 findPages
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2// phpcs:disable MediaWiki.Files.ClassMatchesFilename.NotMatch
3use MediaWiki\Page\WikiPageFactory;
4use MediaWiki\Revision\RevisionRecord;
5use MediaWiki\Title\Title;
6use MediaWiki\WikiMap\WikiMap;
7use Wikimedia\Rdbms\IExpression;
8use Wikimedia\Rdbms\LikeValue;
9use Wikimedia\Rdbms\OrExpressionGroup;
10
11require_once __DIR__ . '/Maintenance.php';
12
13/**
14 * Search pages for a given regex
15 *
16 * @ingroup Maintenance
17 */
18class GrepPages extends Maintenance {
19    /** @var Language */
20    private $contLang;
21
22    /** @var WikiPageFactory */
23    private $wikiPageFactory;
24
25    public function __construct() {
26        parent::__construct();
27        $this->addDescription( 'Search the source text of pages for lines matching ' .
28            'a given regex, and print the lines.' );
29        $this->addOption( 'prefix',
30            'Title prefix. Can be specified more than once. ' .
31            'Use e.g. --prefix=Talk: to search an entire namespace.',
32            false, true, false, true );
33        $this->addOption( 'show-wiki', 'Add the wiki ID to the output' );
34        $this->addOption( 'pages-with-matches',
35            'Suppress normal output; instead print the title of each page ' .
36            'from which output would normally have been printed.',
37            false, false, 'l' );
38        $this->addArg( 'regex', 'The regex to search for' );
39    }
40
41    private function init() {
42        $services = $this->getServiceContainer();
43        $this->contLang = $services->getContentLanguage();
44        $this->wikiPageFactory = $services->getWikiPageFactory();
45    }
46
47    public function execute() {
48        $this->init();
49
50        $showWiki = $this->getOption( 'show-wiki' );
51        $wikiId = WikiMap::getCurrentWikiId();
52        $prefix = $this->getOption( 'prefix' );
53        $regex = $this->getArg( 0 );
54        $titleOnly = $this->hasOption( 'pages-with-matches' );
55
56        if ( ( $regex[0] ?? '' ) === '/' ) {
57            $delimRegex = $regex;
58        } else {
59            $delimRegex = '{' . $regex . '}';
60        }
61
62        foreach ( $this->findPages( $prefix ) as $page ) {
63            $content = $page->getContent( RevisionRecord::RAW );
64            $titleText = $page->getTitle()->getPrefixedDBkey();
65            if ( !$content ) {
66                $this->error( "Page has no content: $titleText" );
67                continue;
68            }
69            if ( !$content instanceof TextContent ) {
70                $this->error( "Page has a non-text content model: $titleText" );
71                continue;
72            }
73
74            $text = $content->getText();
75
76            if ( $titleOnly ) {
77                if ( preg_match( $delimRegex, $text ) ) {
78                    if ( $showWiki ) {
79                        echo "$wikiId\t$titleText\n";
80                    } else {
81                        echo "$titleText\n";
82                    }
83                }
84            } else {
85                foreach ( StringUtils::explode( "\n", $text ) as $lineNum => $line ) {
86                    $lineNum++;
87                    if ( preg_match( $delimRegex, $line ) ) {
88                        if ( $showWiki ) {
89                            echo "$wikiId\t$titleText:$lineNum:$line\n";
90                        } else {
91                            echo "$titleText:$lineNum:$line\n";
92                        }
93                    }
94                }
95            }
96        }
97    }
98
99    public function findPages( $prefixes = null ) {
100        $dbr = $this->getReplicaDB();
101        $orConds = [];
102        if ( $prefixes !== null ) {
103            foreach ( $prefixes as $prefix ) {
104                $colonPos = strpos( $prefix, ':' );
105                if ( $colonPos !== false ) {
106                    $ns = $this->contLang->getNsIndex( substr( $prefix, 0, $colonPos ) );
107                    $prefixDBkey = substr( $prefix, $colonPos + 1 );
108                } else {
109                    $ns = NS_MAIN;
110                    $prefixDBkey = $prefix;
111                }
112                $prefixExpr = $dbr->expr( 'page_namespace', '=', $ns );
113                if ( $prefixDBkey !== '' ) {
114                    $prefixExpr = $prefixExpr->and(
115                        'page_title',
116                        IExpression::LIKE,
117                        new LikeValue( $prefixDBkey, $dbr->anyString() )
118                    );
119                }
120                $orConds[] = $prefixExpr;
121            }
122        }
123        $res = $dbr->newSelectQueryBuilder()
124            ->queryInfo( WikiPage::getQueryInfo() )
125            ->where( $orConds ? new OrExpressionGroup( ...$orConds ) : [] )
126            ->caller( __METHOD__ )
127            ->fetchResultSet();
128        foreach ( $res as $row ) {
129            $title = Title::newFromRow( $row );
130            yield $this->wikiPageFactory->newFromTitle( $title );
131        }
132    }
133}
134
135$maintClass = GrepPages::class;
136require_once RUN_MAINTENANCE_IF_MAIN;