messing with replaceSection()
[lhc/web/wiklou.git] / includes / Content.php
1 <?php
2
3 /**
4 * A content object represents page content, e.g. the text to show on a page.
5 * Content objects have no knowledge about how they relate to Wiki pages.
6 * Content objects are imutable.
7 *
8 */
9 abstract class Content {
10
11 public function __construct( $modelName = null ) { #FIXME: really need revId? annoying! #FIXME: really $title? or just when parsing, every time?
12 $this->mModelName = $modelName;
13 }
14
15 public function getModelName() {
16 return $this->mModelName;
17 }
18
19 public abstract function getSearchText( );
20
21 public abstract function getWikitextForTransclusion( );
22
23 /**
24 * Returns native represenation of the data. Interpretation depends on the data model used,
25 * as given by getDataModel().
26 *
27 */
28 public abstract function getRawData( );
29
30 public abstract function getParserOutput( Title $title = null, $revId = null, ParserOptions $options = NULL );
31
32 public function getRedirectChain() {
33 return null;
34 }
35
36 /**
37 * Returns the section with the given id.
38 *
39 * The default implementation returns null.
40 *
41 * @param String $sectionId the section's id
42 * @return Content|Boolean|null the section, or false if no such section exist, or null if sections are not supported
43 */
44 public function getSection( $sectionId ) {
45 return null;
46 }
47
48 /**
49 * Replaces a section of the content.
50 *
51 * @param $section empty/null/false or a section number (0, 1, 2, T1, T2...), or "new"
52 * @param $with Content: new content of the section
53 * @param $sectionTitle String: new section's subject, only if $section is 'new'
54 * @return string Complete article text, or null if error
55 */
56 public function replaceSection( $section, Content $with, $sectionTitle = '' ) {
57 return $this;
58 }
59
60 #TODO: implement specialized ParserOutput for Wikidata model
61 #TODO: provide addToParserOutput fule Multipart... somehow.
62
63 # TODO: EditPage::mergeChanges( Content $a, Content $b )
64 # TODO: Wikipage::isCountable(Content $a)
65 # TODO: Title::newFromRedirectRecurse( $this->getRawText() );
66
67 # TODO: isCacheable( )
68 # TODO: getSize( )
69
70 # TODO: WikiPage::getUndoText( Revision $undo, Revision $undoafter = null )
71 # TODO: WikiPage::getAutosummary( $oldtext, $text, $flags )
72
73 # TODO: EditPage::getPreloadedText( $preload ) // $wgParser->getPreloadText
74
75
76 # TODO: tie into API to provide contentModel for Revisions
77 # TODO: tie into API to provide serialized version and contentFormat for Revisions
78 # TODO: tie into API edit interface
79
80 }
81
82 /**
83 * Content object implementation for representing flat text. The
84 */
85 abstract class TextContent extends Content {
86 public function __construct( $text, $modelName = null ) {
87 parent::__construct($modelName);
88
89 $this->mText = $text;
90 }
91
92 /**
93 * Returns the text represented by this Content object, as a string.
94 *
95 * @return String the raw text
96 */
97 public function getRawData( ) {
98 $text = $this->mText;
99 return $text;
100 }
101
102 /**
103 * Returns the text represented by this Content object, as a string.
104 *
105 * @return String the raw text
106 */
107 public function getSearchText( ) { #FIXME: use!
108 return $this->getRawData();
109 }
110
111 /**
112 * Returns the text represented by this Content object, as a string.
113 *
114 * @return String the raw text
115 */
116 public function getWikitextForTransclusion( ) { #FIXME: use!
117 return $this->getRawData();
118 }
119
120 /**
121 * Returns a generic ParserOutput object, wrapping the HTML returned by getHtml().
122 *
123 * @return ParserOutput representing the HTML form of the text
124 */
125 public function getParserOutput( Title $title = null, $revId = null, ParserOptions $options = null ) {
126 # generic implementation, relying on $this->getHtml()
127
128 $html = $this->getHtml( $options );
129 $po = new ParserOutput( $html );
130
131 if ( $this->mTitle ) $po->setTitleText( $this->mTitle->getText() );
132
133 #TODO: cache settings, etc?
134
135 return $po;
136 }
137
138 protected abstract function getHtml( );
139
140 }
141
142 class WikitextContent extends TextContent {
143 public function __construct( $text ) {
144 parent::__construct($text, CONTENT_MODEL_WIKITEXT);
145
146 $this->mDefaultParserOptions = null; #TODO: use per-class static member?!
147 }
148
149 protected function getHtml( ) {
150 throw new MWException( "getHtml() not implemented for wikitext. Use getParserOutput()->getText()." );
151 }
152
153 public function getDefaultParserOptions() {
154 global $wgUser, $wgContLang;
155
156 if ( !$this->mDefaultParserOptions ) { #TODO: use per-class static member?!
157 $this->mDefaultParserOptions = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
158 }
159
160 return $this->mDefaultParserOptions;
161 }
162
163 /**
164 * Returns a ParserOutput object reesulting from parsing the content's text using $wgParser
165 *
166 * @return ParserOutput representing the HTML form of the text
167 */
168 public function getParserOutput( Title $title = null, $revId = null, ParserOptions $options = null ) {
169 global $wgParser;
170
171 if ( !$options ) {
172 $options = $this->getDefaultParserOptions();
173 }
174
175 $po = $wgParser->parse( $this->mText, $this->getTitle(), $options, true, true, $this->mRevId );
176
177 return $po;
178 }
179
180 /**
181 * Returns the section with the given id.
182 *
183 * @param String $sectionId the section's id
184 * @return Content|false|null the section, or false if no such section exist, or null if sections are not supported
185 */
186 public function getSection( $section ) {
187 global $wgParser;
188
189 $text = $this->getRawData();
190 $sect = $wgParser->getSection( $text, $section, false );
191
192 return new WikitextContent( $sect );
193 }
194
195 /**
196 * Replaces a section in the wikitext
197 *
198 * @param $section empty/null/false or a section number (0, 1, 2, T1, T2...), or "new"
199 * @param $with Content: new content of the section
200 * @param $sectionTitle String: new section's subject, only if $section is 'new'
201 * @return string Complete article text, or null if error
202 */
203 public function replaceSection( $section, Content $with, $sectionTitle = '' ) {
204 global $wgParser;
205
206 wfProfileIn( __METHOD__ );
207
208 $myModelName = $this->getModelName();
209 $sectionModelName = $with->getModelName();
210
211 if ( $sectionModelName != $myModelName ) {
212 throw new MWException( "Incompatible content model for section: document uses $myModelName, section uses $sectionModelName." );
213 }
214
215 $oldtext = $this->getRawData();
216 $text = $with->getRawData();
217
218 if ( $section == 'new' ) {
219 # Inserting a new section
220 $subject = $sectionTitle ? wfMsgForContent( 'newsectionheaderdefaultlevel', $sectionTitle ) . "\n\n" : '';
221 if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) {
222 $text = strlen( trim( $oldtext ) ) > 0
223 ? "{$oldtext}\n\n{$subject}{$text}"
224 : "{$subject}{$text}";
225 }
226 } else {
227 # Replacing an existing section; roll out the big guns
228 global $wgParser;
229
230 $text = $wgParser->replaceSection( $oldtext, $section, $text );
231 }
232
233 $newContent = new WikitextContent( $text );
234
235 wfProfileOut( __METHOD__ );
236 return $newContent;
237 }
238
239 public function getRedirectChain() {
240 $text = $this->getRawData();
241 return Title::newFromRedirectArray( $text );
242 }
243
244 }
245
246 class MessageContent extends TextContent {
247 public function __construct( $msg_key, $params = null, $options = null ) {
248 parent::__construct(null, CONTENT_MODEL_WIKITEXT);
249
250 $this->mMessageKey = $msg_key;
251
252 $this->mParameters = $params;
253
254 if ( !$options ) $options = array();
255 $this->mOptions = $options;
256
257 $this->mHtmlOptions = null;
258 }
259
260 /**
261 * Returns the message as rendered HTML, using the options supplied to the constructor plus "parse".
262 */
263 protected function getHtml( ) {
264 $opt = array_merge( $this->mOptions, array('parse') );
265
266 return wfMsgExt( $this->mMessageKey, $this->mParameters, $opt );
267 }
268
269
270 /**
271 * Returns the message as raw text, using the options supplied to the constructor minus "parse" and "parseinline".
272 */
273 public function getRawData( ) {
274 $opt = array_diff( $this->mOptions, array('parse', 'parseinline') );
275
276 return wfMsgExt( $this->mMessageKey, $this->mParameters, $opt );
277 }
278
279 }
280
281
282 class JavaScriptContent extends TextContent {
283 public function __construct( $text ) {
284 parent::__construct($text, CONTENT_MODEL_JAVASCRIPT);
285 }
286
287 protected function getHtml( ) {
288 $html = "";
289 $html .= "<pre class=\"mw-code mw-js\" dir=\"ltr\">\n";
290 $html .= htmlspecialchars( $this->getRawData() );
291 $html .= "\n</pre>\n";
292
293 return $html;
294 }
295
296 }
297
298 class CssContent extends TextContent {
299 public function __construct( $text ) {
300 parent::__construct($text, CONTENT_MODEL_CSS);
301 }
302
303 protected function getHtml( ) {
304 $html = "";
305 $html .= "<pre class=\"mw-code mw-css\" dir=\"ltr\">\n";
306 $html .= htmlspecialchars( $this->getRawData() );
307 $html .= "\n</pre>\n";
308
309 return $html;
310 }
311 }
312
313 #FUTURE: special type for redirects?!
314 #FUTURE: MultipartMultipart < WikipageContent (Main + Links + X)
315 #FUTURE: LinksContent < LanguageLinksContent, CategoriesContent
316 #EXAMPLE: CoordinatesContent
317 #EXAMPLE: WikidataContent