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