Nov. branch merge. Various features backported from stable, various bug fixes.
[lhc/web/wiklou.git] / includes / Article.php
1 <?
2 # Class representing a Wikipedia article and history.
3 # See design.doc for an overview.
4
5 # Note: edit user interface and cache support functions have been
6 # moved to separate EditPage and CacheManager classes.
7
8 /* CHECK MERGE @@@
9 TEST THIS @@@
10
11 * s/\$wgTitle/\$this->mTitle/ performed, many replacements
12 * mTitle variable added to class
13 */
14
15 include_once( "CacheManager.php" );
16
17 class Article {
18 /* private */ var $mContent, $mContentLoaded;
19 /* private */ var $mUser, $mTimestamp, $mUserText;
20 /* private */ var $mCounter, $mComment, $mCountAdjustment;
21 /* private */ var $mMinorEdit, $mRedirectedFrom;
22 /* private */ var $mTouched, $mFileCache, $mTitle;
23
24 function Article( &$title ) {
25 $this->mTitle =& $title;
26 $this->clear();
27 }
28
29 /* private */ function clear()
30 {
31 $this->mContentLoaded = false;
32 $this->mUser = $this->mCounter = -1; # Not loaded
33 $this->mRedirectedFrom = $this->mUserText =
34 $this->mTimestamp = $this->mComment = $this->mFileCache = "";
35 $this->mCountAdjustment = 0;
36 $this->mTouched = "19700101000000";
37 }
38
39 # Note that getContent/loadContent may follow redirects if
40 # not told otherwise, and so may cause a change to mTitle.
41
42 function getContent( $noredir = false )
43 {
44 global $action,$section,$count; # From query string
45 $fname = "Article::getContent";
46 wfProfileIn( $fname );
47
48 if ( 0 == $this->getID() ) {
49 if ( "edit" == $action ) {
50 wfProfileOut( $fname );
51 return ""; # was "newarticletext", now moved above the box)
52 }
53 wfProfileOut( $fname );
54 return wfMsg( "noarticletext" );
55 } else {
56 $this->loadContent( $noredir );
57
58 if(
59 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
60 ( $this->mTitle->getNamespace() == Namespace::getTalk( Namespace::getUser()) ) &&
61 preg_match("/^\d{1,3}\.\d{1,3}.\d{1,3}\.\d{1,3}$/",$this->mTitle->getText()) &&
62 $action=="view"
63 )
64 {
65 wfProfileOut( $fname );
66 return $this->mContent . "\n" .wfMsg("anontalkpagetext"); }
67 else {
68 if($action=="edit") {
69 if($section!="") {
70 if($section=="new") {
71 wfProfileOut( $fname );
72 return "";
73 }
74
75 $secs=preg_split("/(^=+.*?=+|^<h[1-6].*?>.*?<\/h[1-6].*?>)/mi",
76 $this->mContent, -1,
77 PREG_SPLIT_DELIM_CAPTURE);
78 if($section==0) {
79 wfProfileOut( $fname );
80 return trim($secs[0]);
81 } else {
82 wfProfileOut( $fname );
83 return trim($secs[$section*2-1] . $secs[$section*2]);
84 }
85 }
86 }
87 wfProfileOut( $fname );
88 return $this->mContent;
89 }
90 }
91 }
92
93 function loadContent( $noredir = false )
94 {
95 global $wgOut, $wgMwRedir;
96 global $oldid, $redirect; # From query
97
98 if ( $this->mContentLoaded ) return;
99 $fname = "Article::loadContent";
100
101 # Pre-fill content with error message so that if something
102 # fails we'll have something telling us what we intended.
103
104 $t = $this->mTitle->getPrefixedText();
105 if ( isset( $oldid ) ) {
106 $oldid = IntVal( $oldid );
107 $t .= ",oldid={$oldid}";
108 }
109 if ( isset( $redirect ) ) {
110 $redirect = ($redirect == "no") ? "no" : "yes";
111 $t .= ",redirect={$redirect}";
112 }
113 $this->mContent = wfMsg( "missingarticle", $t );
114
115 if ( ! $oldid ) { # Retrieve current version
116 $id = $this->getID();
117 if ( 0 == $id ) return;
118
119 $sql = "SELECT " .
120 "cur_text,cur_timestamp,cur_user,cur_counter,cur_restrictions,cur_touched " .
121 "FROM cur WHERE cur_id={$id}";
122 wfDebug( "$sql\n" );
123 $res = wfQuery( $sql, DB_READ, $fname );
124 if ( 0 == wfNumRows( $res ) ) {
125 return;
126 }
127
128 $s = wfFetchObject( $res );
129 # If we got a redirect, follow it (unless we've been told
130 # not to by either the function parameter or the query
131 if ( ( "no" != $redirect ) && ( false == $noredir ) &&
132 ( $wgMwRedir->matchStart( $s->cur_text ) ) ) {
133 if ( preg_match( "/\\[\\[([^\\]\\|]+)[\\]\\|]/",
134 $s->cur_text, $m ) ) {
135 $rt = Title::newFromText( $m[1] );
136
137 # Gotta hand redirects to special pages differently:
138 # Fill the HTTP response "Location" header and ignore
139 # the rest of the page we're on.
140
141 if ( $rt->getInterwiki() != "" ) {
142 $wgOut->redirect( $rt->getFullURL() ) ;
143 return;
144 }
145 if ( $rt->getNamespace() == Namespace::getSpecial() ) {
146 $wgOut->redirect( wfLocalUrl(
147 $rt->getPrefixedURL() ) );
148 return;
149 }
150 $rid = $rt->getArticleID();
151 if ( 0 != $rid ) {
152 $sql = "SELECT cur_text,cur_timestamp,cur_user," .
153 "cur_counter,cur_touched FROM cur WHERE cur_id={$rid}";
154 $res = wfQuery( $sql, DB_READ, $fname );
155
156 if ( 0 != wfNumRows( $res ) ) {
157 $this->mRedirectedFrom = $this->mTitle->getPrefixedText();
158 $this->mTitle = $rt;
159 $s = wfFetchObject( $res );
160 }
161 }
162 }
163 }
164
165 $this->mContent = $s->cur_text;
166 $this->mUser = $s->cur_user;
167 $this->mCounter = $s->cur_counter;
168 $this->mTimestamp = $s->cur_timestamp;
169 $this->mTouched = $s->cur_touched;
170 $this->mTitle->mRestrictions = explode( ",", trim( $s->cur_restrictions ) );
171 $this->mTitle->mRestrictionsLoaded = true;
172 wfFreeResult( $res );
173 } else { # oldid set, retrieve historical version
174 $sql = "SELECT old_text,old_timestamp,old_user FROM old " .
175 "WHERE old_id={$oldid}";
176 $res = wfQuery( $sql, DB_READ, $fname );
177 if ( 0 == wfNumRows( $res ) ) { return; }
178
179 $s = wfFetchObject( $res );
180 $this->mContent = $s->old_text;
181 $this->mUser = $s->old_user;
182 $this->mCounter = 0;
183 $this->mTimestamp = $s->old_timestamp;
184 wfFreeResult( $res );
185 }
186 $this->mContentLoaded = true;
187 }
188
189 function getID() { return $this->mTitle->getArticleID(); }
190
191 function getCount()
192 {
193 if ( -1 == $this->mCounter ) {
194 $id = $this->getID();
195 $this->mCounter = wfGetSQL( "cur", "cur_counter", "cur_id={$id}" );
196 }
197 return $this->mCounter;
198 }
199
200 # Would the given text make this article a "good" article (i.e.,
201 # suitable for including in the article count)?
202
203 function isCountable( $text )
204 {
205 global $wgUseCommaCount, $wgMwRedir;
206
207 if ( 0 != $this->mTitle->getNamespace() ) { return 0; }
208 if ( $wgMwRedir->matchStart( $text ) ) { return 0; }
209 $token = ($wgUseCommaCount ? "," : "[[" );
210 if ( false === strstr( $text, $token ) ) { return 0; }
211 return 1;
212 }
213
214 # Load the field related to the last edit time of the article.
215 # This isn't necessary for all uses, so it's only done if needed.
216
217 /* private */ function loadLastEdit()
218 {
219 global $wgOut;
220 if ( -1 != $this->mUser ) return;
221
222 $sql = "SELECT cur_user,cur_user_text,cur_timestamp," .
223 "cur_comment,cur_minor_edit FROM cur WHERE " .
224 "cur_id=" . $this->getID();
225 $res = wfQuery( $sql, DB_READ, "Article::loadLastEdit" );
226
227 if ( wfNumRows( $res ) > 0 ) {
228 $s = wfFetchObject( $res );
229 $this->mUser = $s->cur_user;
230 $this->mUserText = $s->cur_user_text;
231 $this->mTimestamp = $s->cur_timestamp;
232 $this->mComment = $s->cur_comment;
233 $this->mMinorEdit = $s->cur_minor_edit;
234 }
235 }
236
237 function getTimestamp()
238 {
239 $this->loadLastEdit();
240 return $this->mTimestamp;
241 }
242
243 function getUser()
244 {
245 $this->loadLastEdit();
246 return $this->mUser;
247 }
248
249 function getUserText()
250 {
251 $this->loadLastEdit();
252 return $this->mUserText;
253 }
254
255 function getComment()
256 {
257 $this->loadLastEdit();
258 return $this->mComment;
259 }
260
261 function getMinorEdit()
262 {
263 $this->loadLastEdit();
264 return $this->mMinorEdit;
265 }
266
267 # This is the default action of the script: just view the page of
268 # the given title.
269
270 function view()
271 {
272 global $wgUser, $wgOut, $wgLang;
273 global $oldid, $diff; # From query
274 global $wgLinkCache, $IP;
275 $fname = "Article::view";
276 wfProfileIn( $fname );
277
278 $wgOut->setArticleFlag( true );
279 $wgOut->setRobotpolicy( "index,follow" );
280
281 # If we got diff and oldid in the query, we want to see a
282 # diff page instead of the article.
283
284 if ( isset( $diff ) ) {
285 include_once( "$IP/DifferenceEngine.php" );
286 $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
287 $de = new DifferenceEngine( $oldid, $diff );
288 $de->showDiffPage();
289 wfProfileOut( $fname );
290 return;
291 }
292
293 if ( !isset( $oldid ) ) {
294 if( $this->checkTouched() ) {
295 $wgOut->checkLastModified( $this->mTouched );
296 $this->tryFileCache();
297 }
298 }
299
300 $text = $this->getContent(); # May change mTitle
301 $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
302 $wgOut->setHTMLTitle( $this->mTitle->getPrefixedText() .
303 " - " . wfMsg( "wikititlesuffix" ) );
304
305 # We're looking at an old revision
306
307 if ( $oldid ) {
308 $this->setOldSubtitle();
309 $wgOut->setRobotpolicy( "noindex,follow" );
310 }
311 if ( "" != $this->mRedirectedFrom ) {
312 $sk = $wgUser->getSkin();
313 $redir = $sk->makeKnownLink( $this->mRedirectedFrom, "",
314 "redirect=no" );
315 $s = str_replace( "$1", $redir, wfMsg( "redirectedfrom" ) );
316 $wgOut->setSubtitle( $s );
317 }
318 $wgOut->checkLastModified( $this->mTouched );
319 $this->tryFileCache();
320 $wgLinkCache->preFill( $this->mTitle );
321 $wgOut->addWikiText( $text );
322
323 $this->viewUpdates();
324 wfProfileOut( $fname );
325 }
326
327 # This is the function that gets called for "action=edit".
328
329 function edit()
330 {
331 global $wgOut, $wgUser;
332 global $wpTextbox1, $wpSummary, $wpSave, $wpPreview;
333 global $wpMinoredit, $wpEdittime, $wpTextbox2;
334
335 $fields = array( "wpTextbox1", "wpSummary", "wpTextbox2" );
336 wfCleanFormFields( $fields );
337
338 if ( ! $this->mTitle->userCanEdit() ) {
339 $wgOut->readOnlyPage( $this->getContent(), true );
340 return;
341 }
342 if ( $wgUser->isBlocked() ) {
343 $this->blockedIPpage();
344 return;
345 }
346 if ( wfReadOnly() ) {
347 if( isset( $wpSave ) or isset( $wpPreview ) ) {
348 $this->editForm( "preview" );
349 } else {
350 $wgOut->readOnlyPage( $this->getContent() );
351 }
352 return;
353 }
354 if ( $_SERVER['REQUEST_METHOD'] != "POST" ) unset( $wpSave );
355 if ( isset( $wpSave ) ) {
356 $this->editForm( "save" );
357 } else if ( isset( $wpPreview ) ) {
358 $this->editForm( "preview" );
359 } else { # First time through
360 $this->editForm( "initial" );
361 }
362 }
363
364 # Since there is only one text field on the edit form,
365 # pressing <enter> will cause the form to be submitted, but
366 # the submit button value won't appear in the query, so we
367 # Fake it here before going back to edit(). This is kind of
368 # ugly, but it helps some old URLs to still work.
369
370 function submit()
371 {
372 global $wpSave, $wpPreview;
373 if ( ! isset( $wpPreview ) ) { $wpSave = 1; }
374
375 $this->edit();
376 }
377
378 # The edit form is self-submitting, so that when things like
379 # preview and edit conflicts occur, we get the same form back
380 # with the extra stuff added. Only when the final submission
381 # is made and all is well do we actually save and redirect to
382 # the newly-edited page.
383
384 function editForm( $formtype )
385 {
386 global $wgOut, $wgUser;
387 global $wpTextbox1, $wpSummary, $wpWatchthis;
388 global $wpSave, $wpPreview;
389 global $wpMinoredit, $wpEdittime, $wpTextbox2, $wpSection;
390 global $oldid, $redirect, $section;
391 global $wgLang;
392
393 if(isset($wpSection)) { $section=$wpSection; } else { $wpSection=$section; }
394
395 $sk = $wgUser->getSkin();
396 $isConflict = false;
397 $wpTextbox1 = rtrim ( $wpTextbox1 ) ; # To avoid text getting longer on each preview
398
399 if(!$this->mTitle->getArticleID()) { # new article
400
401 $wgOut->addWikiText(wfmsg("newarticletext"));
402
403 }
404
405 # Attempt submission here. This will check for edit conflicts,
406 # and redundantly check for locked database, blocked IPs, etc.
407 # that edit() already checked just in case someone tries to sneak
408 # in the back door with a hand-edited submission URL.
409
410 if ( "save" == $formtype ) {
411 if ( $wgUser->isBlocked() ) {
412 $this->blockedIPpage();
413 return;
414 }
415 if ( wfReadOnly() ) {
416 $wgOut->readOnlyPage();
417 return;
418 }
419 # If article is new, insert it.
420
421 $aid = $this->mTitle->getArticleID();
422 if ( 0 == $aid ) {
423 # we need to strip Windoze linebreaks because some browsers
424 # append them and the string comparison fails
425 if ( ( "" == $wpTextbox1 ) ||
426 ( wfMsg( "newarticletext" ) == rtrim( preg_replace("/\r/","",$wpTextbox1) ) ) ) {
427 $wgOut->redirect( wfLocalUrl(
428 $this->mTitle->getPrefixedURL() ) );
429 return;
430 }
431 $this->mCountAdjustment = $this->isCountable( $wpTextbox1 );
432 $this->insertNewArticle( $wpTextbox1, $wpSummary, $wpMinoredit, $wpWatchthis );
433 return;
434 }
435 # Article exists. Check for edit conflict.
436 # Don't check for conflict when appending a comment - this should always work
437
438 $this->clear(); # Force reload of dates, etc.
439 if ( $section!="new" && ( $this->getTimestamp() != $wpEdittime ) ) {
440 $isConflict = true;
441 }
442 $u = $wgUser->getID();
443
444 # Supress edit conflict with self
445
446 if ( ( 0 != $u ) && ( $this->getUser() == $u ) ) {
447 $isConflict = false;
448 } else {
449 # switch from section editing to normal editing in edit conflict
450 if($isConflict) {
451 $section="";$wpSection="";
452 }
453
454 }
455 if ( ! $isConflict ) {
456 # All's well: update the article here
457 if($this->updateArticle( $wpTextbox1, $wpSummary, $wpMinoredit, $wpWatchthis, $wpSection ))
458 return;
459 else
460 $isConflict = true;
461 }
462 }
463 # First time through: get contents, set time for conflict
464 # checking, etc.
465
466 if ( "initial" == $formtype ) {
467 $wpEdittime = $this->getTimestamp();
468 $wpTextbox1 = $this->getContent(true);
469 $wpSummary = "";
470 }
471 $wgOut->setRobotpolicy( "noindex,nofollow" );
472 $wgOut->setArticleFlag( false );
473
474 if ( $isConflict ) {
475 $s = str_replace( "$1", $this->mTitle->getPrefixedText(),
476 wfMsg( "editconflict" ) );
477 $wgOut->setPageTitle( $s );
478 $wgOut->addHTML( wfMsg( "explainconflict" ) );
479
480 $wpTextbox2 = $wpTextbox1;
481 $wpTextbox1 = $this->getContent(true);
482 $wpEdittime = $this->getTimestamp();
483 } else {
484 $s = str_replace( "$1", $this->mTitle->getPrefixedText(),
485 wfMsg( "editing" ) );
486
487 if($section!="") {
488 if($section=="new") {
489 $s.=wfMsg("commentedit");
490 } else {
491 $s.=wfMsg("sectionedit");
492 }
493 }
494 $wgOut->setPageTitle( $s );
495 if ( $oldid ) {
496 $this->setOldSubtitle();
497 $wgOut->addHTML( wfMsg( "editingold" ) );
498 }
499 }
500
501 if( wfReadOnly() ) {
502 $wgOut->addHTML( "<strong>" .
503 wfMsg( "readonlywarning" ) .
504 "</strong>" );
505 }
506 if( $this->mTitle->isProtected() ) {
507 $wgOut->addHTML( "<strong>" . wfMsg( "protectedpagewarning" ) .
508 "</strong><br />\n" );
509 }
510
511 $kblength = (int)(strlen( $wpTextbox1 ) / 1024);
512 if( $kblength > 29 ) {
513 $wgOut->addHTML( "<strong>" .
514 str_replace( '$1', $kblength , wfMsg( "longpagewarning" ) )
515 . "</strong>" );
516 }
517
518 $rows = $wgUser->getOption( "rows" );
519 $cols = $wgUser->getOption( "cols" );
520
521 $ew = $wgUser->getOption( "editwidth" );
522 if ( $ew ) $ew = " style=\"width:100%\"";
523 else $ew = "" ;
524
525 $q = "action=submit";
526 if ( "no" == $redirect ) { $q .= "&redirect=no"; }
527 $action = wfEscapeHTML( wfLocalUrl( $this->mTitle->getPrefixedURL(), $q ) );
528
529 $summary = wfMsg( "summary" );
530 $subject = wfMsg("subject");
531 $minor = wfMsg( "minoredit" );
532 $watchthis = wfMsg ("watchthis");
533 $save = wfMsg( "savearticle" );
534 $prev = wfMsg( "showpreview" );
535
536 $cancel = $sk->makeKnownLink( $this->mTitle->getPrefixedURL(),
537 wfMsg( "cancel" ) );
538 $edithelp = $sk->makeKnownLink( wfMsg( "edithelppage" ),
539 wfMsg( "edithelp" ) );
540 $copywarn = str_replace( "$1", $sk->makeKnownLink(
541 wfMsg( "copyrightpage" ) ), wfMsg( "copyrightwarning" ) );
542
543 $wpTextbox1 = wfEscapeHTML( $wpTextbox1 );
544 $wpTextbox2 = wfEscapeHTML( $wpTextbox2 );
545 $wpSummary = wfEscapeHTML( $wpSummary );
546
547 // activate checkboxes if user wants them to be always active
548 if (!$wpPreview && $wgUser->getOption("watchdefault")) $wpWatchthis=1;
549 if (!$wpPreview && $wgUser->getOption("minordefault")) $wpMinoredit=1;
550
551 // activate checkbox also if user is already watching the page,
552 // require wpWatchthis to be unset so that second condition is not
553 // checked unnecessarily
554 if (!$wpWatchthis && !$wpPreview && $this->mTitle->userIsWatching()) $wpWatchthis=1;
555
556 if ( 0 != $wgUser->getID() ) {
557 $checkboxhtml=
558 "<input tabindex=3 type=checkbox value=1 name='wpMinoredit'".($wpMinoredit?" checked":"").">{$minor}".
559 "<input tabindex=4 type=checkbox name='wpWatchthis'".($wpWatchthis?" checked":"").">{$watchthis}<br>";
560
561 } else {
562 $checkboxhtml="";
563 }
564
565
566 if ( "preview" == $formtype) {
567
568 $previewhead="<h2>" . wfMsg( "preview" ) . "</h2>\n<p><large><center><font color=\"#cc0000\">" .
569 wfMsg( "note" ) . wfMsg( "previewnote" ) . "</font></center></large><P>\n";
570 if ( $isConflict ) {
571 $previewhead.="<h2>" . wfMsg( "previewconflict" ) .
572 "</h2>\n";
573 }
574 $previewtext = wfUnescapeHTML( $wpTextbox1 );
575
576 if($wgUser->getOption("previewontop")) {
577 $wgOut->addHTML($previewhead);
578 $wgOut->addWikiText( $this->preSaveTransform( $previewtext ) ."\n\n");
579 }
580 $wgOut->addHTML( "<br clear=\"all\" />\n" );
581 }
582
583 # if this is a comment, show a subject line at the top, which is also the edit summary.
584 # Otherwise, show a summary field at the bottom
585 if($section=="new") {
586
587 $commentsubject="{$subject}: <input tabindex=1 type=text value=\"{$wpSummary}\" name=\"wpSummary\" maxlength=200 size=60><br>";
588 } else {
589
590 $editsummary="{$summary}: <input tabindex=3 type=text value=\"{$wpSummary}\" name=\"wpSummary\" maxlength=200 size=60><br>";
591 }
592
593 $wgOut->addHTML( "
594 <form id=\"editform\" name=\"editform\" method=\"post\" action=\"$action\"
595 enctype=\"application/x-www-form-urlencoded\">
596 {$commentsubject}
597 <textarea tabindex=2 name=\"wpTextbox1\" rows={$rows}
598 cols={$cols}{$ew} wrap=\"virtual\">" .
599 $wgLang->recodeForEdit( $wpTextbox1 ) .
600 "
601 </textarea>
602 <br>{$editsummary}
603 {$checkboxhtml}
604 <input tabindex=5 type=submit value=\"{$save}\" name=\"wpSave\">
605 <input tabindex=6 type=submit value=\"{$prev}\" name=\"wpPreview\">
606 <em>{$cancel}</em> | <em>{$edithelp}</em>
607 <br><br>{$copywarn}
608 <input type=hidden value=\"{$section}\" name=\"wpSection\">
609 <input type=hidden value=\"{$wpEdittime}\" name=\"wpEdittime\">\n" );
610
611 if ( $isConflict ) {
612 $wgOut->addHTML( "<h2>" . wfMsg( "yourdiff" ) . "</h2>\n" );
613 DifferenceEngine::showDiff( $wpTextbox2, $wpTextbox1,
614 wfMsg( "yourtext" ), wfMsg( "storedversion" ) );
615
616 $wgOut->addHTML( "<h2>" . wfMsg( "yourtext" ) . "</h2>
617 <textarea tabindex=6 name=\"wpTextbox2\" rows={$rows} cols={$cols} wrap=virtual>"
618 . $wgLang->recodeForEdit( $wpTextbox2 ) .
619 "
620 </textarea>" );
621 }
622 $wgOut->addHTML( "</form>\n" );
623 if($formtype =="preview" && !$wgUser->getOption("previewontop")) {
624 $wgOut->addHTML($previewhead);
625 $wgOut->addWikiText( $this->preSaveTransform( $previewtext ) );
626 }
627 }
628
629 # Theoretically we could defer these whole insert and update
630 # functions for after display, but that's taking a big leap
631 # of faith, and we want to be able to report database
632 # errors at some point.
633
634 /* private */ function insertNewArticle( $text, $summary, $isminor, $watchthis )
635 {
636 global $wgOut, $wgUser, $wgLinkCache, $wgMwRedir;
637 global $wgEnablePersistentLC;
638
639 $fname = "Article::insertNewArticle";
640
641 $ns = $this->mTitle->getNamespace();
642 $ttl = $this->mTitle->getDBkey();
643 $text = $this->preSaveTransform( $text );
644 if ( $wgMwRedir->matchStart( $text ) ) { $redir = 1; }
645 else { $redir = 0; }
646
647 $now = wfTimestampNow();
648 $won = wfInvertTimestamp( $now );
649 wfSeedRandom();
650 $rand = number_format( mt_rand() / mt_getrandmax(), 12, ".", "" );
651 $sql = "INSERT INTO cur (cur_namespace,cur_title,cur_text," .
652 "cur_comment,cur_user,cur_timestamp,cur_minor_edit,cur_counter," .
653 "cur_restrictions,cur_user_text,cur_is_redirect," .
654 "cur_is_new,cur_random,cur_touched,inverse_timestamp) VALUES ({$ns},'" . wfStrencode( $ttl ) . "', '" .
655 wfStrencode( $text ) . "', '" .
656 wfStrencode( $summary ) . "', '" .
657 $wgUser->getID() . "', '{$now}', " .
658 ( $isminor ? 1 : 0 ) . ", 0, '', '" .
659 wfStrencode( $wgUser->getName() ) . "', $redir, 1, $rand, '{$now}', '{$won}')";
660 $res = wfQuery( $sql, DB_WRITE, $fname );
661
662 $newid = wfInsertId();
663 $this->mTitle->resetArticleID( $newid );
664
665 if ( $wgEnablePersistentLC ) {
666 // Purge related entries in links cache on new page, to heal broken links
667 $ptitle = wfStrencode( $ttl );
668 wfQuery("DELETE linkscc FROM linkscc,brokenlinks ".
669 "WHERE lcc_pageid=bl_from AND bl_to='{$ptitle}'", DB_WRITE);
670 }
671
672 $sql = "INSERT INTO recentchanges (rc_timestamp,rc_cur_time," .
673 "rc_namespace,rc_title,rc_new,rc_minor,rc_cur_id,rc_user," .
674 "rc_user_text,rc_comment,rc_this_oldid,rc_last_oldid,rc_bot) VALUES (" .
675 "'{$now}','{$now}',{$ns},'" . wfStrencode( $ttl ) . "',1," .
676 ( $isminor ? 1 : 0 ) . ",{$newid}," . $wgUser->getID() . ",'" .
677 wfStrencode( $wgUser->getName() ) . "','" .
678 wfStrencode( $summary ) . "',0,0," .
679 ( $wgUser->isBot() ? 1 : 0 ) . ")";
680 wfQuery( $sql, DB_WRITE, $fname );
681 if ($watchthis) {
682 if(!$this->mTitle->userIsWatching()) $this->watch();
683 } else {
684 if ( $this->mTitle->userIsWatching() ) {
685 $this->unwatch();
686 }
687 }
688
689 $this->showArticle( $text, wfMsg( "newarticle" ) );
690 }
691
692 function updateArticle( $text, $summary, $minor, $watchthis, $section = "")
693 {
694 global $wgOut, $wgUser, $wgLinkCache;
695 global $wgDBtransactions, $wgMwRedir;
696 $fname = "Article::updateArticle";
697
698 $this->loadLastEdit();
699
700 // insert updated section into old text if we have only edited part
701 // of the article
702 if ($section != "") {
703 $oldtext=$this->getContent();
704 if($section=="new") {
705 if($summary) $subject="== {$summary} ==\n\n";
706 $text=$oldtext."\n\n".$subject.$text;
707 } else {
708 $secs=preg_split("/(^=+.*?=+|^<h[1-6].*?>.*?<\/h[1-6].*?>)/mi",
709 $oldtext,-1,PREG_SPLIT_DELIM_CAPTURE);
710 $secs[$section*2]=$text."\n\n"; // replace with edited
711 if($section) { $secs[$section*2-1]=""; } // erase old headline
712 $text=join("",$secs);
713 }
714 }
715 if ( $this->mMinorEdit ) { $me1 = 1; } else { $me1 = 0; }
716 if ( $minor ) { $me2 = 1; } else { $me2 = 0; }
717 if ( preg_match( "/^((" . $wgMwRedir->getBaseRegex() . ")[^\\n]+)/i", $text, $m ) ) {
718 $redir = 1;
719 $text = $m[1] . "\n"; # Remove all content but redirect
720 }
721 else { $redir = 0; }
722
723 $text = $this->preSaveTransform( $text );
724
725 # Update article, but only if changed.
726
727 if( $wgDBtransactions ) {
728 $sql = "BEGIN";
729 wfQuery( $sql, DB_WRITE );
730 }
731 $oldtext = $this->getContent( true );
732
733 if ( 0 != strcmp( $text, $oldtext ) ) {
734 $this->mCountAdjustment = $this->isCountable( $text )
735 - $this->isCountable( $oldtext );
736
737 $now = wfTimestampNow();
738 $won = wfInvertTimestamp( $now );
739 $sql = "UPDATE cur SET cur_text='" . wfStrencode( $text ) .
740 "',cur_comment='" . wfStrencode( $summary ) .
741 "',cur_minor_edit={$me2}, cur_user=" . $wgUser->getID() .
742 ",cur_timestamp='{$now}',cur_user_text='" .
743 wfStrencode( $wgUser->getName() ) .
744 "',cur_is_redirect={$redir}, cur_is_new=0, cur_touched='{$now}', inverse_timestamp='{$won}' " .
745 "WHERE cur_id=" . $this->getID() .
746 " AND cur_timestamp='" . $this->getTimestamp() . "'";
747 $res = wfQuery( $sql, DB_WRITE, $fname );
748
749 if( wfAffectedRows() == 0 ) {
750 /* Belated edit conflict! Run away!! */
751 return false;
752 }
753
754 $sql = "INSERT INTO old (old_namespace,old_title,old_text," .
755 "old_comment,old_user,old_user_text,old_timestamp," .
756 "old_minor_edit,inverse_timestamp) VALUES (" .
757 $this->mTitle->getNamespace() . ", '" .
758 wfStrencode( $this->mTitle->getDBkey() ) . "', '" .
759 wfStrencode( $oldtext ) . "', '" .
760 wfStrencode( $this->getComment() ) . "', " .
761 $this->getUser() . ", '" .
762 wfStrencode( $this->getUserText() ) . "', '" .
763 $this->getTimestamp() . "', " . $me1 . ", '" .
764 wfInvertTimestamp( $this->getTimestamp() ) . "')";
765 $res = wfQuery( $sql, DB_WRITE, $fname );
766 $oldid = wfInsertID( $res );
767
768 $sql = "INSERT INTO recentchanges (rc_timestamp,rc_cur_time," .
769 "rc_namespace,rc_title,rc_new,rc_minor,rc_bot,rc_cur_id,rc_user," .
770 "rc_user_text,rc_comment,rc_this_oldid,rc_last_oldid) VALUES (" .
771 "'{$now}','{$now}'," . $this->mTitle->getNamespace() . ",'" .
772 wfStrencode( $this->mTitle->getDBkey() ) . "',0,{$me2}," .
773 ( $wgUser->isBot() ? 1 : 0 ) . "," .
774 $this->getID() . "," . $wgUser->getID() . ",'" .
775 wfStrencode( $wgUser->getName() ) . "','" .
776 wfStrencode( $summary ) . "',0,{$oldid})";
777 wfQuery( $sql, DB_WRITE, $fname );
778
779 $sql = "UPDATE recentchanges SET rc_this_oldid={$oldid} " .
780 "WHERE rc_namespace=" . $this->mTitle->getNamespace() . " AND " .
781 "rc_title='" . wfStrencode( $this->mTitle->getDBkey() ) . "' AND " .
782 "rc_timestamp='" . $this->getTimestamp() . "'";
783 wfQuery( $sql, DB_WRITE, $fname );
784
785 $sql = "UPDATE recentchanges SET rc_cur_time='{$now}' " .
786 "WHERE rc_cur_id=" . $this->getID();
787 wfQuery( $sql, DB_WRITE, $fname );
788
789 // Purge related entries in link cache when a page change
790 // (probably just affects anything when article changes stub state)
791 $pageid=$this->getID();
792 wfQuery("DELETE linkscc FROM linkscc,links ".
793 "WHERE lcc_title=links.l_from AND l_to={$pageid}", DB_WRITE);
794
795 }
796 if( $wgDBtransactions ) {
797 $sql = "COMMIT";
798 wfQuery( $sql, DB_WRITE );
799 }
800
801 if ($watchthis) {
802 if (!$this->mTitle->userIsWatching()) $this->watch();
803 } else {
804 if ( $this->mTitle->userIsWatching() ) {
805 $this->unwatch();
806 }
807 }
808
809 $this->showArticle( $text, wfMsg( "updated" ) );
810 return true;
811 }
812
813 # After we've either updated or inserted the article, update
814 # the link tables and redirect to the new page.
815
816 function showArticle( $text, $subtitle )
817 {
818 global $wgOut, $wgUser, $wgLinkCache, $wgUseBetterLinksUpdate;
819 global $wgMwRedir;
820
821 $wgLinkCache = new LinkCache();
822
823 # Get old version of link table to allow incremental link updates
824 if ( $wgUseBetterLinksUpdate ) {
825 $wgLinkCache->preFill( $this->mTitle );
826 $wgLinkCache->clear();
827 }
828
829 # Now update the link cache by parsing the text
830 $wgOut = new OutputPage();
831 $wgOut->addWikiText( $text );
832
833 $this->editUpdates( $text );
834 if( $wgMwRedir->matchStart( $text ) )
835 $r = "redirect=no";
836 else
837 $r = "";
838 $wgOut->redirect( wfLocalUrl( $this->mTitle->getPrefixedURL(), $r ) );
839 }
840
841 # Add this page to my watchlist
842
843 function watch( $add = true )
844 {
845 global $wgUser, $wgOut, $wgLang;
846 global $wgDeferredUpdateList;
847
848 if ( 0 == $wgUser->getID() ) {
849 $wgOut->errorpage( "watchnologin", "watchnologintext" );
850 return;
851 }
852 if ( wfReadOnly() ) {
853 $wgOut->readOnlyPage();
854 return;
855 }
856 if( $add )
857 $wgUser->addWatch( $this->mTitle );
858 else
859 $wgUser->removeWatch( $this->mTitle );
860
861 $wgOut->setPagetitle( wfMsg( $add ? "addedwatch" : "removedwatch" ) );
862 $wgOut->setRobotpolicy( "noindex,follow" );
863
864 $sk = $wgUser->getSkin() ;
865 $link = $sk->makeKnownLink ( $this->mTitle->getPrefixedText() ) ;
866
867 if($add)
868 $text = wfMsg( "addedwatchtext", $link );
869 else
870 $text = wfMsg( "removedwatchtext", $link );
871 $wgOut->addHTML( $text );
872
873 $up = new UserUpdate();
874 array_push( $wgDeferredUpdateList, $up );
875
876 $wgOut->returnToMain( false );
877 }
878
879 function unwatch()
880 {
881 $this->watch( false );
882 }
883
884 # This shares a lot of issues (and code) with Recent Changes
885
886 function history()
887 {
888 global $wgUser, $wgOut, $wgLang, $offset, $limit;
889
890 # If page hasn't changed, client can cache this
891
892 $wgOut->checkLastModified( $this->getTimestamp() );
893 $fname = "Article::history";
894 wfProfileIn( $fname );
895
896 $wgOut->setPageTitle( $this->mTitle->getPRefixedText() );
897 $wgOut->setSubtitle( wfMsg( "revhistory" ) );
898 $wgOut->setArticleFlag( false );
899 $wgOut->setRobotpolicy( "noindex,nofollow" );
900
901 if( $this->mTitle->getArticleID() == 0 ) {
902 $wgOut->addHTML( wfMsg( "nohistory" ) );
903 wfProfileOut( $fname );
904 return;
905 }
906
907 $offset = (int)$offset;
908 $limit = (int)$limit;
909 if( $limit == 0 ) $limit = 50;
910 $namespace = $this->mTitle->getNamespace();
911 $title = $this->mTitle->getText();
912 $sql = "SELECT old_id,old_user," .
913 "old_comment,old_user_text,old_timestamp,old_minor_edit ".
914 "FROM old USE INDEX (name_title_timestamp) " .
915 "WHERE old_namespace={$namespace} AND " .
916 "old_title='" . wfStrencode( $this->mTitle->getDBkey() ) . "' " .
917 "ORDER BY inverse_timestamp LIMIT $offset, $limit";
918 $res = wfQuery( $sql, DB_READ, "Article::history" );
919
920 $revs = wfNumRows( $res );
921 if( $this->mTitle->getArticleID() == 0 ) {
922 $wgOut->addHTML( wfMsg( "nohistory" ) );
923 wfProfileOut( $fname );
924 return;
925 }
926
927 $sk = $wgUser->getSkin();
928 $numbar = wfViewPrevNext(
929 $offset, $limit,
930 $this->mTitle->getPrefixedText(),
931 "action=history" );
932 $s = $numbar;
933 $s .= $sk->beginHistoryList();
934
935 if($offset == 0 )
936 $s .= $sk->historyLine( $this->getTimestamp(), $this->getUser(),
937 $this->getUserText(), $namespace,
938 $title, 0, $this->getComment(),
939 ( $this->getMinorEdit() > 0 ) );
940
941 $revs = wfNumRows( $res );
942 while ( $line = wfFetchObject( $res ) ) {
943 $s .= $sk->historyLine( $line->old_timestamp, $line->old_user,
944 $line->old_user_text, $namespace,
945 $title, $line->old_id,
946 $line->old_comment, ( $line->old_minor_edit > 0 ) );
947 }
948 $s .= $sk->endHistoryList();
949 $s .= $numbar;
950 $wgOut->addHTML( $s );
951 wfProfileOut( $fname );
952 }
953
954 function protect( $limit = "sysop" )
955 {
956 global $wgUser, $wgOut;
957
958 if ( ! $wgUser->isSysop() ) {
959 $wgOut->sysopRequired();
960 return;
961 }
962 if ( wfReadOnly() ) {
963 $wgOut->readOnlyPage();
964 return;
965 }
966 $id = $this->mTitle->getArticleID();
967 if ( 0 == $id ) {
968 $wgOut->fatalEror( wfMsg( "badarticleerror" ) );
969 return;
970 }
971 $sql = "UPDATE cur SET cur_touched='" . wfTimestampNow() . "'," .
972 "cur_restrictions='{$limit}' WHERE cur_id={$id}";
973 wfQuery( $sql, DB_WRITE, "Article::protect" );
974
975 $wgOut->redirect( wfLocalUrl( $this->mTitle->getPrefixedURL() ) );
976 }
977
978 function unprotect()
979 {
980 return $this->protect( "" );
981 }
982
983 function delete()
984 {
985 global $wgUser, $wgOut;
986 global $wpConfirm, $wpReason, $image, $oldimage;
987
988 # This code desperately needs to be totally rewritten
989
990 if ( ( ! $wgUser->isSysop() ) ) {
991 $wgOut->sysopRequired();
992 return;
993 }
994 if ( wfReadOnly() ) {
995 $wgOut->readOnlyPage();
996 return;
997 }
998
999 # Better double-check that it hasn't been deleted yet!
1000 $wgOut->setPagetitle( wfMsg( "confirmdelete" ) );
1001 if ( ( "" == trim( $this->mTitle->getText() ) )
1002 or ( $this->mTitle->getArticleId() == 0 ) ) {
1003 $wgOut->fatalError( wfMsg( "cannotdelete" ) );
1004 return;
1005 }
1006
1007 # determine whether this page has earlier revisions
1008 # and insert a warning if it does
1009 # we select the text because it might be useful below
1010 $sql="SELECT old_text FROM old WHERE old_namespace=0 and old_title='" . wfStrencode($this->mTitle->getPrefixedDBkey())."' ORDER BY inverse_timestamp LIMIT 1";
1011 $res=wfQuery($sql, DB_READ, $fname);
1012 if( ($old=wfFetchObject($res)) && !$wpConfirm ) {
1013 $skin=$wgUser->getSkin();
1014 $wgOut->addHTML("<B>".wfMsg("historywarning"));
1015 $wgOut->addHTML( $skin->historyLink() ."</B><P>");
1016 }
1017
1018 $sql="SELECT cur_text FROM cur WHERE cur_namespace=0 and cur_title='" . wfStrencode($this->mTitle->getPrefixedDBkey())."'";
1019 $res=wfQuery($sql, DB_READ, $fname);
1020 if( ($s=wfFetchObject($res))) {
1021
1022 # if this is a mini-text, we can paste part of it into the deletion reason
1023
1024 #if this is empty, an earlier revision may contain "useful" text
1025 if($s->cur_text!="") {
1026 $text=$s->cur_text;
1027 } else {
1028 if($old) {
1029 $text=$old->old_text;
1030 $blanked=1;
1031 }
1032
1033 }
1034
1035 $length=strlen($text);
1036
1037 # this should not happen, since it is not possible to store an empty, new
1038 # page. Let's insert a standard text in case it does, though
1039 if($length==0 && !$wpReason) { $wpReason=wfmsg("exblank");}
1040
1041
1042 if($length < 500 && !$wpReason) {
1043
1044 # comment field=255, let's grep the first 150 to have some user
1045 # space left
1046 $text=substr($text,0,150);
1047 # let's strip out newlines and HTML tags
1048 $text=preg_replace("/\"/","'",$text);
1049 $text=preg_replace("/\</","&lt;",$text);
1050 $text=preg_replace("/\>/","&gt;",$text);
1051 $text=preg_replace("/[\n\r]/","",$text);
1052 if(!$blanked) {
1053 $wpReason=wfMsg("excontent"). " '".$text;
1054 } else {
1055 $wpReason=wfMsg("exbeforeblank") . " '".$text;
1056 }
1057 if($length>150) { $wpReason .= "..."; } # we've only pasted part of the text
1058 $wpReason.="'";
1059 }
1060 }
1061
1062 return $this->confirmDelete();
1063 }
1064
1065 function confirmDelete( $par = "" )
1066 {
1067 global $wgOut;
1068
1069 $sub = htmlspecialchars( $this->mTitle->getPrefixedText() );
1070 $wgOut->setSubtitle( wfMsg( "deletesub", $sub ) );
1071 $wgOut->setRobotpolicy( "noindex,nofollow" );
1072 $wgOut->addWikiText( wfMsg( "confirmdeletetext" ) );
1073
1074 $t = $this->mTitle->getPrefixedURL();
1075
1076 $formaction = wfEscapeHTML( wfLocalUrl( $t, "action=delete" . $par ) );
1077 $confirm = wfMsg( "confirm" );
1078 $check = wfMsg( "confirmcheck" );
1079 $delcom = wfMsg( "deletecomment" );
1080
1081 $wgOut->addHTML( "
1082 <form id=\"deleteconfirm\" method=\"post\" action=\"{$formaction}\">
1083 <table border=0><tr><td align=right>
1084 {$delcom}:</td><td align=left>
1085 <input type=text size=60 name=\"wpReason\" value=\"{$wpReason}\">
1086 </td></tr><tr><td>&nbsp;</td></tr>
1087 <tr><td align=right>
1088 <input type=checkbox name=\"wpConfirm\" value='1' id=\"wpConfirm\">
1089 </td><td><label for=\"wpConfirm\">{$check}</label></td>
1090 </tr><tr><td>&nbsp;</td><td>
1091 <input type=submit name=\"wpConfirmB\" value=\"{$confirm}\">
1092 </td></tr></table></form>\n" );
1093
1094 $wgOut->returnToMain( false );
1095 }
1096
1097 function doDelete()
1098 {
1099 global $wgOut, $wgUser, $wgLang;
1100 global $wpReason;
1101 $fname = "Article::doDelete";
1102
1103 $this->doDeleteArticle( $this->mTitle );
1104 $deleted = $this->mTitle->getPrefixedText();
1105
1106 $wgOut->setPagetitle( wfMsg( "actioncomplete" ) );
1107 $wgOut->setRobotpolicy( "noindex,nofollow" );
1108
1109 $sk = $wgUser->getSkin();
1110 $loglink = $sk->makeKnownLink( $wgLang->getNsText(
1111 Namespace::getWikipedia() ) .
1112 ":" . wfMsg( "dellogpage" ), wfMsg( "deletionlog" ) );
1113
1114 $text = str_replace( "$1" , $deleted, wfMsg( "deletedtext" ) );
1115 $text = str_replace( "$2", $loglink, $text );
1116
1117 $wgOut->addHTML( "<p>" . $text );
1118 $wgOut->returnToMain( false );
1119 }
1120
1121 function doDeleteArticle( $title )
1122 {
1123 global $wgUser, $wgOut, $wgLang, $wpReason, $wgDeferredUpdateList;
1124
1125 $fname = "Article::doDeleteArticle";
1126 $ns = $title->getNamespace();
1127 $t = wfStrencode( $title->getDBkey() );
1128 $id = $title->getArticleID();
1129
1130 if ( "" == $t ) {
1131 $wgOut->fatalError( wfMsg( "cannotdelete" ) );
1132 return;
1133 }
1134
1135 $u = new SiteStatsUpdate( 0, 1, -$this->isCountable( $this->getContent( true ) ) );
1136 array_push( $wgDeferredUpdateList, $u );
1137
1138 # Move article and history to the "archive" table
1139 $sql = "INSERT INTO archive (ar_namespace,ar_title,ar_text," .
1140 "ar_comment,ar_user,ar_user_text,ar_timestamp,ar_minor_edit," .
1141 "ar_flags) SELECT cur_namespace,cur_title,cur_text,cur_comment," .
1142 "cur_user,cur_user_text,cur_timestamp,cur_minor_edit,0 FROM cur " .
1143 "WHERE cur_namespace={$ns} AND cur_title='{$t}'";
1144 wfQuery( $sql, DB_WRITE, $fname );
1145
1146 $sql = "INSERT INTO archive (ar_namespace,ar_title,ar_text," .
1147 "ar_comment,ar_user,ar_user_text,ar_timestamp,ar_minor_edit," .
1148 "ar_flags) SELECT old_namespace,old_title,old_text,old_comment," .
1149 "old_user,old_user_text,old_timestamp,old_minor_edit,old_flags " .
1150 "FROM old WHERE old_namespace={$ns} AND old_title='{$t}'";
1151 wfQuery( $sql, DB_WRITE, $fname );
1152
1153 # Now that it's safely backed up, delete it
1154
1155 $sql = "DELETE FROM cur WHERE cur_namespace={$ns} AND " .
1156 "cur_title='{$t}'";
1157 wfQuery( $sql, DB_WRITE, $fname );
1158
1159 $sql = "DELETE FROM old WHERE old_namespace={$ns} AND " .
1160 "old_title='{$t}'";
1161 wfQuery( $sql, DB_WRITE, $fname );
1162
1163 $sql = "DELETE FROM recentchanges WHERE rc_namespace={$ns} AND " .
1164 "rc_title='{$t}'";
1165 wfQuery( $sql, DB_WRITE, $fname );
1166
1167 # Finally, clean up the link tables
1168
1169 if ( 0 != $id ) {
1170
1171 // Purge related entries in links cache on delete,
1172 wfQuery("DELETE linkscc FROM linkscc,links ".
1173 "WHERE lcc_title=links.l_from AND l_to={$id}", DB_WRITE);
1174 wfQuery("DELETE FROM linkscc WHERE lcc_title='{$t}'", DB_WRITE);
1175
1176 $t = wfStrencode( $title->getPrefixedDBkey() );
1177 $sql = "SELECT l_from FROM links WHERE l_to={$id}";
1178 $res = wfQuery( $sql, DB_READ, $fname );
1179
1180 $sql = "INSERT INTO brokenlinks (bl_from,bl_to) VALUES ";
1181 $now = wfTimestampNow();
1182 $sql2 = "UPDATE cur SET cur_touched='{$now}' WHERE cur_id IN (";
1183 $first = true;
1184
1185 while ( $s = wfFetchObject( $res ) ) {
1186 $nt = Title::newFromDBkey( $s->l_from );
1187 $lid = $nt->getArticleID();
1188
1189 if ( ! $first ) { $sql .= ","; $sql2 .= ","; }
1190 $first = false;
1191 $sql .= "({$lid},'{$t}')";
1192 $sql2 .= "{$lid}";
1193 }
1194 $sql2 .= ")";
1195 if ( ! $first ) {
1196 wfQuery( $sql, DB_WRITE, $fname );
1197 wfQuery( $sql2, DB_WRITE, $fname );
1198 }
1199 wfFreeResult( $res );
1200
1201 $sql = "DELETE FROM links WHERE l_to={$id}";
1202 wfQuery( $sql, DB_WRITE, $fname );
1203
1204 $sql = "DELETE FROM links WHERE l_from='{$t}'";
1205 wfQuery( $sql, DB_WRITE, $fname );
1206
1207 $sql = "DELETE FROM imagelinks WHERE il_from='{$t}'";
1208 wfQuery( $sql, DB_WRITE, $fname );
1209
1210 $sql = "DELETE FROM brokenlinks WHERE bl_from={$id}";
1211 wfQuery( $sql, DB_WRITE, $fname );
1212 }
1213
1214 $log = new LogPage( wfMsg( "dellogpage" ), wfMsg( "dellogpagetext" ) );
1215 $art = $title->getPrefixedText();
1216 $wpReason = wfCleanQueryVar( $wpReason );
1217 $log->addEntry( str_replace( "$1", $art, wfMsg( "deletedarticle" ) ), $wpReason );
1218
1219 # Clear the cached article id so the interface doesn't act like we exist
1220 $this->mTitle->resetArticleID( 0 );
1221 $this->mTitle->mArticleID = 0;
1222 }
1223
1224 function rollback()
1225 {
1226 global $wgUser, $wgLang, $wgOut, $from;
1227
1228 if ( ! $wgUser->isSysop() ) {
1229 $wgOut->sysopRequired();
1230 return;
1231 }
1232
1233 # Replace all this user's current edits with the next one down
1234 $tt = wfStrencode( $this->mTitle->getDBKey() );
1235 $n = $this->mTitle->getNamespace();
1236
1237 # Get the last editor
1238 $sql = "SELECT cur_id,cur_user,cur_user_text,cur_comment FROM cur WHERE cur_title='{$tt}' AND cur_namespace={$n}";
1239 $res = wfQuery( $sql, DB_READ );
1240 if( ($x = wfNumRows( $res )) != 1 ) {
1241 # Something wrong
1242 $wgOut->addHTML( wfMsg( "notanarticle" ) );
1243 return;
1244 }
1245 $s = wfFetchObject( $res );
1246 $ut = wfStrencode( $s->cur_user_text );
1247 $uid = $s->cur_user;
1248 $pid = $s->cur_id;
1249
1250 $from = str_replace( '_', ' ', wfCleanQueryVar( $from ) );
1251 if( $from != $s->cur_user_text ) {
1252 $wgOut->setPageTitle(wfmsg("rollbackfailed"));
1253 $wgOut->addWikiText( wfMsg( "alreadyrolled",
1254 htmlspecialchars( $this->mTitle->getPrefixedText()),
1255 htmlspecialchars( $from ),
1256 htmlspecialchars( $s->cur_user_text ) ) );
1257 if($s->cur_comment != "") {
1258 $wgOut->addHTML(
1259 wfMsg("editcomment",
1260 htmlspecialchars( $s->cur_comment ) ) );
1261 }
1262 return;
1263 }
1264
1265 # Get the last edit not by this guy
1266 $sql = "SELECT old_text,old_user,old_user_text
1267 FROM old USE INDEX (name_title_timestamp)
1268 WHERE old_namespace={$n} AND old_title='{$tt}'
1269 AND (old_user <> {$uid} OR old_user_text <> '{$ut}')
1270 ORDER BY inverse_timestamp LIMIT 1";
1271 $res = wfQuery( $sql, DB_READ );
1272 if( wfNumRows( $res ) != 1 ) {
1273 # Something wrong
1274 $wgOut->setPageTitle(wfMsg("rollbackfailed"));
1275 $wgOut->addHTML( wfMsg( "cantrollback" ) );
1276 return;
1277 }
1278 $s = wfFetchObject( $res );
1279
1280 # Save it!
1281 $newcomment = str_replace( "$1", $s->old_user_text, wfMsg( "revertpage" ) );
1282 $wgOut->setPagetitle( wfMsg( "actioncomplete" ) );
1283 $wgOut->setRobotpolicy( "noindex,nofollow" );
1284 $wgOut->addHTML( "<h2>" . $newcomment . "</h2>\n<hr>\n" );
1285 $this->updateArticle( $s->old_text, $newcomment, 1, $this->mTitle->userIsWatching() );
1286
1287 $wgOut->returnToMain( false );
1288 }
1289
1290
1291 # Do standard deferred updates after page view
1292
1293 /* private */ function viewUpdates()
1294 {
1295 global $wgDeferredUpdateList;
1296
1297 if ( 0 != $this->getID() ) {
1298 global $wgDisableCounters;
1299 if( !$wgDisableCounters ) {
1300 $u = new ViewCountUpdate( $this->getID() );
1301 array_push( $wgDeferredUpdateList, $u );
1302 $u = new SiteStatsUpdate( 1, 0, 0 );
1303 array_push( $wgDeferredUpdateList, $u );
1304 }
1305 $u = new UserTalkUpdate( 0, $this->mTitle->getNamespace(),
1306 $this->mTitle->getDBkey() );
1307 array_push( $wgDeferredUpdateList, $u );
1308 }
1309 }
1310
1311 # Do standard deferred updates after page edit.
1312 # Every 1000th edit, prune the recent changes table.
1313
1314 /* private */ function editUpdates( $text )
1315 {
1316 global $wgDeferredUpdateList, $wgDBname, $wgMemc;
1317
1318 wfSeedRandom();
1319 if ( 0 == mt_rand( 0, 999 ) ) {
1320 $cutoff = wfUnix2Timestamp( time() - ( 7 * 86400 ) );
1321 $sql = "DELETE FROM recentchanges WHERE rc_timestamp < '{$cutoff}'";
1322 wfQuery( $sql, DB_WRITE );
1323 }
1324 $id = $this->getID();
1325 $title = $this->mTitle->getPrefixedDBkey();
1326 $adj = $this->mCountAdjustment;
1327
1328 if ( 0 != $id ) {
1329 $u = new LinksUpdate( $id, $title );
1330 array_push( $wgDeferredUpdateList, $u );
1331 $u = new SiteStatsUpdate( 0, 1, $adj );
1332 array_push( $wgDeferredUpdateList, $u );
1333 $u = new SearchUpdate( $id, $title, $text );
1334 array_push( $wgDeferredUpdateList, $u );
1335
1336 $u = new UserTalkUpdate( 1, $this->mTitle->getNamespace(),
1337 $this->mTitle->getDBkey() );
1338 array_push( $wgDeferredUpdateList, $u );
1339
1340 if ( $this->getNamespace == NS_MEDIAWIKI ) {
1341 $messageCache = $wgMemc->get( "$wgDBname:messages" );
1342 if (!$messageCache) {
1343 $messageCache = wfLoadAllMessages();
1344 }
1345 $messageCache[$title] = $text;
1346 $wgMemc->set( "$wgDBname:messages" );
1347 }
1348 }
1349 }
1350
1351 /* private */ function setOldSubtitle()
1352 {
1353 global $wgLang, $wgOut;
1354
1355 $td = $wgLang->timeanddate( $this->mTimestamp, true );
1356 $r = str_replace( "$1", "{$td}", wfMsg( "revisionasof" ) );
1357 $wgOut->setSubtitle( "({$r})" );
1358 }
1359
1360 function blockedIPpage()
1361 {
1362 global $wgOut, $wgUser, $wgLang;
1363
1364 $wgOut->setPageTitle( wfMsg( "blockedtitle" ) );
1365 $wgOut->setRobotpolicy( "noindex,nofollow" );
1366 $wgOut->setArticleFlag( false );
1367
1368 $id = $wgUser->blockedBy();
1369 $reason = $wgUser->blockedFor();
1370
1371 $name = User::whoIs( $id );
1372 $link = "[[" . $wgLang->getNsText( Namespace::getUser() ) .
1373 ":{$name}|{$name}]]";
1374
1375 $text = str_replace( "$1", $link, wfMsg( "blockedtext" ) );
1376 $text = str_replace( "$2", $reason, $text );
1377 $text = str_replace( "$3", getenv( "REMOTE_ADDR" ), $text );
1378 $wgOut->addWikiText( $text );
1379 $wgOut->returnToMain( false );
1380 }
1381
1382 # This function is called right before saving the wikitext,
1383 # so we can do things like signatures and links-in-context.
1384
1385 function preSaveTransform( $text )
1386 {
1387 $s = "";
1388 while ( "" != $text ) {
1389 $p = preg_split( "/<\\s*nowiki\\s*>/i", $text, 2 );
1390 $s .= $this->pstPass2( $p[0] );
1391
1392 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $text = ""; }
1393 else {
1394 $q = preg_split( "/<\\/\\s*nowiki\\s*>/i", $p[1], 2 );
1395 $s .= "<nowiki>{$q[0]}</nowiki>";
1396 $text = $q[1];
1397 }
1398 }
1399 return rtrim( $s );
1400 }
1401
1402 /* private */ function pstPass2( $text )
1403 {
1404 global $wgUser, $wgLang, $wgLocaltimezone;
1405
1406 # Signatures
1407 #
1408 $n = $wgUser->getName();
1409 $k = $wgUser->getOption( "nickname" );
1410 if ( "" == $k ) { $k = $n; }
1411 if(isset($wgLocaltimezone)) {
1412 $oldtz = getenv("TZ"); putenv("TZ=$wgLocaltimezone");
1413 }
1414 /* Note: this is an ugly timezone hack for the European wikis */
1415 $d = $wgLang->timeanddate( date( "YmdHis" ), false ) .
1416 " (" . date( "T" ) . ")";
1417 if(isset($wgLocaltimezone)) putenv("TZ=$oldtz");
1418
1419 $text = preg_replace( "/~~~~/", "[[" . $wgLang->getNsText(
1420 Namespace::getUser() ) . ":$n|$k]] $d", $text );
1421 $text = preg_replace( "/~~~/", "[[" . $wgLang->getNsText(
1422 Namespace::getUser() ) . ":$n|$k]]", $text );
1423
1424 # Context links: [[|name]] and [[name (context)|]]
1425 #
1426 $tc = "[&;%\\-,.\\(\\)' _0-9A-Za-z\\/:\\x80-\\xff]";
1427 $np = "[&;%\\-,.' _0-9A-Za-z\\/:\\x80-\\xff]"; # No parens
1428 $conpat = "/^({$np}+) \\(({$tc}+)\\)$/";
1429
1430 $p1 = "/\[\[({$np}+) \\(({$np}+)\\)\\|]]/"; # [[page (context)|]]
1431 $p2 = "/\[\[\\|({$tc}+)]]/"; # [[|page]]
1432 $p3 = "/\[\[([A-Za-z _]+):({$np}+)\\|]]/"; # [[namespace:page|]]
1433 $p4 = "/\[\[([A-Aa-z _]+):({$np}+) \\(({$np}+)\\)\\|]]/";
1434 # [[ns:page (cont)|]]
1435 $context = "";
1436 $t = $this->mTitle->getText();
1437 if ( preg_match( $conpat, $t, $m ) ) {
1438 $context = $m[2];
1439 }
1440 $text = preg_replace( $p4, "[[\\1:\\2 (\\3)|\\2]]", $text );
1441 $text = preg_replace( $p1, "[[\\1 (\\2)|\\1]]", $text );
1442 $text = preg_replace( $p3, "[[\\1:\\2|\\2]]", $text );
1443
1444 if ( "" == $context ) {
1445 $text = preg_replace( $p2, "[[\\1]]", $text );
1446 } else {
1447 $text = preg_replace( $p2, "[[\\1 ({$context})|\\1]]", $text );
1448 }
1449
1450 # {{SUBST:xxx}} variables
1451 #
1452 $mw =& MagicWord::get( MAG_SUBST );
1453 $text = $mw->substituteCallback( $text, "wfReplaceSubstVar" );
1454
1455 return $text;
1456 }
1457
1458 /* Caching functions */
1459
1460 function tryFileCache() {
1461 if($this->isFileCacheable()) {
1462 $touched = $this->mTouched;
1463 if( strpos( $this->mContent, "{{" ) !== false ) {
1464 # Expire pages with variable replacements in an hour
1465 $expire = wfUnix2Timestamp( time() - 3600 );
1466 $touched = max( $expire, $touched );
1467 }
1468 $cache = new CacheManager( $this->mTitle );
1469 if($cache->isFileCacheGood( $touched )) {
1470 global $wgOut;
1471 wfDebug( " tryFileCache() - about to load\n" );
1472 $cache->loadFromFileCache();
1473 $wgOut->reportTime(); # For profiling
1474 exit;
1475 } else {
1476 wfDebug( " tryFileCache() - starting buffer\n" );
1477 if($cache->useGzip() && wfClientAcceptsGzip()) {
1478 /* For some reason, adding this header line over in
1479 CacheManager::saveToFileCache() fails on my test
1480 setup at home, though it works on the live install.
1481 Make double-sure... --brion */
1482 header( "Content-Encoding: gzip" );
1483 }
1484 ob_start( array(&$cache, 'saveToFileCache' ) );
1485 }
1486 } else {
1487 wfDebug( " tryFileCache() - not cacheable\n" );
1488 }
1489 }
1490
1491 function isFileCacheable() {
1492 global $wgUser, $wgUseFileCache, $wgShowIPinHeader;
1493 global $action, $oldid, $diff, $redirect, $printable;
1494 return $wgUseFileCache
1495 and (!$wgShowIPinHeader)
1496 and ($this->getID() != 0)
1497 and ($wgUser->getId() == 0)
1498 and (!$wgUser->getNewtalk())
1499 and ($this->mTitle->getNamespace != Namespace::getSpecial())
1500 and ($action == "view")
1501 and (!isset($oldid))
1502 and (!isset($diff))
1503 and (!isset($redirect))
1504 and (!isset($printable))
1505 and (!$this->mRedirectedFrom);
1506 }
1507
1508 function checkTouched() {
1509 $id = $this->getID();
1510 $sql = "SELECT cur_touched,cur_is_redirect FROM cur WHERE cur_id=$id";
1511 $res = wfQuery( $sql, DB_READ, "Article::checkTouched" );
1512 if( $s = wfFetchObject( $res ) ) {
1513 $this->mTouched = $s->cur_touched;
1514 return !$s->cur_is_redirect;
1515 } else {
1516 return false;
1517 }
1518 }
1519 }
1520
1521 function wfReplaceSubstVar( $matches ) {
1522 return wfMsg( $matches[1] );
1523 }
1524
1525 ?>