Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 66
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
Logic
0.00% covered (danger)
0.00%
0 / 66
0.00% covered (danger)
0.00%
0 / 3
506
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 setupForParse
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 1
182
 addSubpageHandlerToOutput
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2
3namespace MediaWiki\Extension\TemplateSandbox;
4
5use Content;
6use InvalidArgumentException;
7use MediaWiki\Linker\LinkTarget;
8use MediaWiki\MediaWikiServices;
9use MediaWiki\Output\OutputPage;
10use MediaWiki\Page\PageReference;
11use MediaWiki\Revision\MutableRevisionRecord;
12use MediaWiki\Revision\SlotRecord;
13use MediaWiki\Title\Title;
14use ParserOptions;
15use RequestContext;
16use Wikimedia\ScopedCallback;
17
18/**
19 * Business logic class for TemplateSandbox
20 */
21class Logic {
22    private static $counter = 0;
23
24    /** Prefixes to search for sandbox templates */
25    private $prefixes = [];
26
27    /** Title to replace with $content */
28    private $title = null;
29
30    /** Content to replace $title */
31    private $content = null;
32
33    /**
34     * @param string[] $prefixes Title prefixes to search for sandbox templates
35     * @param Title|null $title Title to replace with 'content'
36     * @param Content|null $content Content to use to replace 'title'
37     */
38    public function __construct( $prefixes, $title, $content ) {
39        if ( ( $title === null ) !== ( $content === null ) ) {
40            throw new InvalidArgumentException( '$title and $content must both be given or both be null' );
41        }
42
43        $this->prefixes = $prefixes;
44        $this->title = $title;
45        $this->content = $content;
46    }
47
48    /**
49     * Set up a ParserOptions for TemplateSandbox operation.
50     * @param ParserOptions $popt
51     * @return ScopedCallback to uninstall
52     */
53    public function setupForParse( ParserOptions $popt ) {
54        $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
55
56        $inHook = false;
57        $hookReset = $hookContainer->scopedRegister( 'TitleExists', function ( $title, &$exists ) use ( &$inHook ) {
58            if ( $exists || $inHook ) {
59                return;
60            }
61            $inHook = true;
62            $titleText = $title->getPrefixedText();
63            try {
64                if ( $this->title && $this->title->equals( $title ) ) {
65                    $exists = true;
66                    return;
67                }
68
69                foreach ( $this->prefixes as $prefix ) {
70                    $newtitle = Title::newFromText( $prefix . '/' . $titleText );
71                    if ( $newtitle instanceof Title && $newtitle->exists() ) {
72                        $exists = true;
73                        return;
74                    }
75                }
76            } finally {
77                $inHook = false;
78            }
79        } );
80
81        $oldCurrentRevisionRecordCallback = $popt->setCurrentRevisionRecordCallback(
82            function ( $title, $parser = false ) use ( &$oldCurrentRevisionRecordCallback ) {
83                if ( $this->title && $this->title->equals( $title ) ) {
84                    $user = RequestContext::getMain()->getUser();
85                    $revRecord = new MutableRevisionRecord( $title );
86                    $revRecord->setUser( $user );
87                    $revRecord->setContent(
88                        SlotRecord::MAIN,
89                        $this->content
90                    );
91                    $revRecord->setParentId( $title->getLatestRevID() );
92                    return $revRecord;
93                }
94
95                foreach ( $this->prefixes as $prefix ) {
96                    $newtitle = Title::newFromText( $prefix . '/' . $title->getPrefixedText() );
97                    if ( $newtitle instanceof Title && $newtitle->exists() ) {
98                        $title = $newtitle;
99                        break;
100                    }
101                }
102                return call_user_func( $oldCurrentRevisionRecordCallback, $title, $parser );
103            }
104        );
105
106        MediaWikiServices::getInstance()->getLinkCache()->clear();
107
108        return new ScopedCallback( static function () use ( $hookReset ) {
109            ScopedCallback::consume( $hookReset );
110            MediaWikiServices::getInstance()->getLinkCache()->clear();
111        } );
112    }
113
114    /**
115     * Add a handler for sandbox subpages to the OutputPage
116     * @param array $prefixes
117     * @param OutputPage $output
118     */
119    public static function addSubpageHandlerToOutput( array $prefixes, OutputPage $output ) {
120        $cache = [];
121        $output->addContentOverrideCallback( static function ( $title ) use ( $prefixes, &$cache ) {
122            /** @var PageReference|LinkTarget $title */
123            $formatter = MediaWikiServices::getInstance()->getTitleFormatter();
124            $titleText = $formatter->getPrefixedText( $title );
125            if ( array_key_exists( $titleText, $cache ) ) {
126                return $cache[$titleText];
127            }
128            foreach ( $prefixes as $prefix ) {
129                $newtitle = Title::newFromText( $prefix . '/' . $titleText );
130                if ( $newtitle instanceof Title && $newtitle->exists() ) {
131                    $rev = MediaWikiServices::getInstance()
132                        ->getRevisionLookup()
133                        ->getRevisionByTitle( $newtitle );
134                    $content = $rev ? $rev->getContent( SlotRecord::MAIN ) : null;
135                    if ( $content ) {
136                        $cache[$titleText] = $content;
137                        return $content;
138                    }
139                }
140            }
141            $cache[$titleText] = null;
142            return null;
143        } );
144    }
145
146}