Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
43 / 43
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
DefaultPageProperties
100.00% covered (success)
100.00%
43 / 43
100.00% covered (success)
100.00%
6 / 6
12
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 initialize
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
4
 redirectTargetField
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 finishInitializeBatch
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 finalize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 loadCreateTimestamp
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace CirrusSearch\BuildDocument;
4
5use CirrusSearch\Search\CirrusIndexField;
6use CirrusSearch\Util;
7use Elastica\Document;
8use MediaWiki\Linker\LinkTarget;
9use MediaWiki\Page\WikiPage;
10use MediaWiki\Revision\RevisionRecord;
11use MediaWiki\Revision\SlotRecord;
12use MediaWiki\Title\Title;
13use MediaWiki\Title\TitleFormatter;
14use MediaWiki\Utils\MWTimestamp;
15use MediaWiki\WikiMap\WikiMap;
16use Wikimedia\Rdbms\IReadableDatabase;
17
18/**
19 * Default properties attached to all page documents.
20 */
21class DefaultPageProperties implements PagePropertyBuilder {
22    /** @var IReadableDatabase Wiki database to query additional page properties from. */
23    private $db;
24
25    /** @var TitleFormatter Formats namespace redirect-target text. */
26    private $titleFormatter;
27
28    /**
29     * @param IReadableDatabase $db Wiki database to query additional page properties from.
30     * @param TitleFormatter $titleFormatter Formats namespace and redirect-target text.
31     */
32    public function __construct( IReadableDatabase $db, TitleFormatter $titleFormatter ) {
33        $this->db = $db;
34        $this->titleFormatter = $titleFormatter;
35    }
36
37    /**
38     * {@inheritDoc}
39     *
40     * @param Document $doc The document to be populated
41     * @param WikiPage $page The page to scope operation to
42     */
43    public function initialize( Document $doc, WikiPage $page, RevisionRecord $revision, bool $isRedirect ): void {
44        $title = $page->getTitle();
45        $doc->set( 'wiki', WikiMap::getCurrentWikiId() );
46        $doc->set( 'page_id', $page->getId() );
47        $doc->set( 'namespace',
48            $title->getNamespace() );
49        $doc->set( 'namespace_text',
50            Util::getNamespaceText( $title, $this->titleFormatter ) );
51        $doc->set( 'title', $title->getText() );
52        $doc->set( 'timestamp',
53            wfTimestamp( TS_ISO_8601, $revision->getTimestamp() ) );
54        $createTs = $this->loadCreateTimestamp( $page->getId() );
55        if ( $createTs ) {
56            $doc->set( 'create_timestamp', $createTs );
57        }
58
59        // we always set redirect_target and pair that with the super_detect_noop
60        // equals handler to ensure the entire map is replaced at once, clearing
61        // any stale sub-field data
62        if ( $isRedirect ) {
63            $doc->set( 'page_type', 'redirect' );
64            $content = $revision->getContent( SlotRecord::MAIN );
65            $target = $content ? $content->getRedirectTarget() : null;
66            $doc->set( 'redirect_target', $this->redirectTargetField( $target ) );
67        } else {
68            $doc->set( 'page_type', 'primary' );
69            $doc->set( 'redirect_target', null );
70        }
71        CirrusIndexField::addNoopHandler( $doc, 'redirect_target', 'equals' );
72    }
73
74    /**
75     * Derive the redirect_target field from a redirect's resolved target.
76     *
77     * Pure mapping of the LinkTarget returned by Content::getRedirectTarget() onto
78     * the stored object. Returns null for a malformed redirect (no resolvable
79     * target); every resolvable target (same-wiki existing or broken, Special:,
80     * interwiki) yields an object.
81     *
82     * @param LinkTarget|null $target The redirect's resolved target, or null when malformed
83     * @return array|null
84     */
85    public function redirectTargetField( ?LinkTarget $target ): ?array {
86        if ( $target === null ) {
87            return null;
88        }
89        return [
90            // namespace may be negative (Special: = -1, Media: = -2)
91            'namespace' => $target->getNamespace(),
92            // getText() (namespace-less) so it is directly comparable to a document's
93            // top-level title field
94            'title' => $target->getText(),
95            // portion after the #, empty string otherwise
96            'fragment' => $target->getFragment(),
97            // empty for same-wiki targets
98            'interwiki' => $target->getInterwiki(),
99            // full normalized target string, including any interwiki/namespace prefix
100            'link' => $this->titleFormatter->getFullText( $target ),
101        ];
102    }
103
104    /**
105     * {@inheritDoc}
106     */
107    public function finishInitializeBatch(): void {
108        // NOOP
109    }
110
111    /**
112     * {@inheritDoc}
113     */
114    public function finalize( Document $doc, Title $title, RevisionRecord $revision ): void {
115        // NOOP
116    }
117
118    /**
119     * Timestamp the oldest revision of this page was created.
120     */
121    private function loadCreateTimestamp( int $pageId ): ?string {
122        $createTs = $this->db->newSelectQueryBuilder()
123            ->select( 'MIN(rev_timestamp)' )
124            ->from( 'revision' )
125            ->where( [ 'rev_page' => $pageId ] )
126            ->caller( __METHOD__ )
127            ->fetchField();
128        if ( $createTs ) {
129            $createTs = MWTimestamp::convert( TS_ISO_8601, $createTs );
130        }
131        return $createTs ?: null;
132    }
133}