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