summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Scribunto/includes/common/ScribuntoContent.php
blob: ff2ec8e4a8d50fa784e8188cfc5bb6804aef91e8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<?php
/**
 * Scribunto Content Model
 *
 * @file
 * @ingroup Extensions
 * @ingroup Scribunto
 *
 * @author Brad Jorsch <bjorsch@wikimedia.org>
 */

/**
 * Represents the content of a Scribunto script page
 */
class ScribuntoContent extends TextContent {

	function __construct( $text ) {
		parent::__construct( $text, CONTENT_MODEL_SCRIBUNTO );
	}

	/**
	 * Checks whether the script is valid
	 *
	 * @param Title $title
	 * @return Status
	 */
	public function validate( Title $title ) {
		$engine = Scribunto::newDefaultEngine();
		$engine->setTitle( $title );
		return $engine->validate( $this->getNativeData(), $title->getPrefixedDBkey() );
	}

	public function prepareSave( WikiPage $page, $flags, $parentRevId, User $user ) {
		return $this->validate( $page->getTitle() );
	}

	/**
	 * Parse the Content object and generate a ParserOutput from the result.
	 *
	 * @param Title $title The page title to use as a context for rendering
	 * @param null|int $revId The revision being rendered (optional)
	 * @param null|ParserOptions $options Any parser options
	 * @param bool $generateHtml Whether to generate HTML (default: true).
	 * @param ParserOutput &$output ParserOutput representing the HTML form of the text.
	 * @return ParserOutput
	 */
	protected function fillParserOutput(
		Title $title, $revId, ParserOptions $options, $generateHtml, ParserOutput &$output
	) {
		global $wgParser;

		$text = $this->getNativeData();

		// Get documentation, if any
		$output = new ParserOutput();
		$doc = Scribunto::getDocPage( $title );
		if ( $doc ) {
			$msg = wfMessage(
				$doc->exists() ? 'scribunto-doc-page-show' : 'scribunto-doc-page-does-not-exist',
				$doc->getPrefixedText()
			)->inContentLanguage();

			if ( !$msg->isDisabled() ) {
				// We need the ParserOutput for categories and such, so we
				// can't use $msg->parse().
				$docViewLang = $doc->getPageViewLanguage();
				$dir = $docViewLang->getDir();

				// Code is forced to be ltr, but the documentation can be rtl.
				// Correct direction class is needed for correct formatting.
				// The possible classes are
				// mw-content-ltr or mw-content-rtl
				$dirClass = "mw-content-$dir";

				$docWikitext = Html::rawElement(
					'div',
					[
						'lang' => $docViewLang->getHtmlCode(),
						'dir' => $dir,
						'class' => $dirClass,
					],
					// Line breaks are needed so that wikitext would be
					// appropriately isolated for correct parsing. See Bug 60664.
					"\n" . $msg->plain() . "\n"
				);

				if ( !$options ) {
					// NOTE: use canonical options per default to produce cacheable output
					$options = ContentHandler::getForTitle( $doc )->makeParserOptions( 'canonical' );
				} else {
					if ( $options->getTargetLanguage() === null ) {
						$options->setTargetLanguage( $doc->getPageLanguage() );
					}
				}

				$output = $wgParser->parse( $docWikitext, $title, $options, true, true, $revId );
			}

			// Mark the doc page as a transclusion, so we get purged when it
			// changes.
			$output->addTemplate( $doc, $doc->getArticleID(), $doc->getLatestRevID() );
		}

		// Validate the script, and include an error message and tracking
		// category if it's invalid
		$status = $this->validate( $title );
		if ( !$status->isOK() ) {
			$output->setText( $output->getRawText() .
				Html::rawElement( 'div', [ 'class' => 'errorbox' ],
					$status->getHTML( 'scribunto-error-short', 'scribunto-error-long' )
				)
			);
			$output->addTrackingCategory( 'scribunto-module-with-errors-category', $title );
		}

		if ( !$generateHtml ) {
			// We don't need the actual HTML
			$output->setText( '' );
			return $output;
		}

		$engine = Scribunto::newDefaultEngine();
		$engine->setTitle( $title );
		if ( $this->highlight( $text, $output, $engine ) ) {
			return $output;
		}

		// No GeSHi, or GeSHi can't parse it, use plain <pre>
		$output->setText( $output->getRawText() .
			"<pre class='mw-code mw-script' dir='ltr'>\n" .
			htmlspecialchars( $text ) .
			"\n</pre>\n"
		);

		return $output;
	}

	/**
	 * Adds syntax highlighting to the output (or do not touch it and return false).
	 * @param string $text
	 * @param ParserOutput $output
	 * @param ScribuntoEngineBase $engine
	 * @return bool Success status
	 */
	protected function highlight( $text, ParserOutput $output, ScribuntoEngineBase $engine ) {
		global $wgScribuntoUseGeSHi;
		$language = $engine->getGeSHiLanguage();
		if ( $wgScribuntoUseGeSHi && class_exists( SyntaxHighlight::class ) && $language ) {
			$status = SyntaxHighlight::highlight( $text, $language );
			if ( $status->isGood() ) {
				// @todo replace addModuleStyles line with the appropriate call on
				// SyntaxHighlight once one is created
				$output->addModuleStyles( 'ext.pygments' );
				$output->setText( $output->getRawText() . $status->getValue() );
				return true;
			}
		}
		return false;
	}
}