af803fdbbdebff892329c51b891f734f42950057
[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 # The talk page isn't in the regular link tables, so we need to update manually:
690 $talkns = $ns ^ 1; # talk -> normal; normal -> talk
691 $sql = "UPDATE cur set cur_touched='$now' WHERE cur_namespace=$talkns AND cur_title='" . wfStrencode( $ttl ) . "'";
692 wfQuery( $sql );
693
694 $this->showArticle( $text, wfMsg( "newarticle" ) );
695 }
696
697 function updateArticle( $text, $summary, $minor, $watchthis, $section = "")
698 {
699 global $wgOut, $wgUser, $wgLinkCache;
700 global $wgDBtransactions, $wgMwRedir;
701 $fname = "Article::updateArticle";
702
703 $this->loadLastEdit();
704
705 // insert updated section into old text if we have only edited part
706 // of the article
707 if ($section != "") {
708 $oldtext=$this->getContent();
709 if($section=="new") {
710 if($summary) $subject="== {$summary} ==\n\n";
711 $text=$oldtext."\n\n".$subject.$text;
712 } else {
713 $secs=preg_split("/(^=+.*?=+|^<h[1-6].*?>.*?<\/h[1-6].*?>)/mi",
714 $oldtext,-1,PREG_SPLIT_DELIM_CAPTURE);
715 $secs[$section*2]=$text."\n\n"; // replace with edited
716 if($section) { $secs[$section*2-1]=""; } // erase old headline
717 $text=join("",$secs);
718 }
719 }
720 if ( $this->mMinorEdit ) { $me1 = 1; } else { $me1 = 0; }
721 if ( $minor ) { $me2 = 1; } else { $me2 = 0; }
722 if ( preg_match( "/^((" . $wgMwRedir->getBaseRegex() . ")[^\\n]+)/i", $text, $m ) ) {
723 $redir = 1;
724 $text = $m[1] . "\n"; # Remove all content but redirect
725 }
726 else { $redir = 0; }
727
728 $text = $this->preSaveTransform( $text );
729
730 # Update article, but only if changed.
731
732 if( $wgDBtransactions ) {
733 $sql = "BEGIN";
734 wfQuery( $sql, DB_WRITE );
735 }
736 $oldtext = $this->getContent( true );
737
738 if ( 0 != strcmp( $text, $oldtext ) ) {
739 $this->mCountAdjustment = $this->isCountable( $text )
740 - $this->isCountable( $oldtext );
741
742 $now = wfTimestampNow();
743 $won = wfInvertTimestamp( $now );
744 $sql = "UPDATE cur SET cur_text='" . wfStrencode( $text ) .
745 "',cur_comment='" . wfStrencode( $summary ) .
746 "',cur_minor_edit={$me2}, cur_user=" . $wgUser->getID() .
747 ",cur_timestamp='{$now}',cur_user_text='" .
748 wfStrencode( $wgUser->getName() ) .
749 "',cur_is_redirect={$redir}, cur_is_new=0, cur_touched='{$now}', inverse_timestamp='{$won}' " .
750 "WHERE cur_id=" . $this->getID() .
751 " AND cur_timestamp='" . $this->getTimestamp() . "'";
752 $res = wfQuery( $sql, DB_WRITE, $fname );
753
754 if( wfAffectedRows() == 0 ) {
755 /* Belated edit conflict! Run away!! */
756 return false;
757 }
758
759 $sql = "INSERT INTO old (old_namespace,old_title,old_text," .
760 "old_comment,old_user,old_user_text,old_timestamp," .
761 "old_minor_edit,inverse_timestamp) VALUES (" .
762 $this->mTitle->getNamespace() . ", '" .
763 wfStrencode( $this->mTitle->getDBkey() ) . "', '" .
764 wfStrencode( $oldtext ) . "', '" .
765 wfStrencode( $this->getComment() ) . "', " .
766 $this->getUser() . ", '" .
767 wfStrencode( $this->getUserText() ) . "', '" .
768 $this->getTimestamp() . "', " . $me1 . ", '" .
769 wfInvertTimestamp( $this->getTimestamp() ) . "')";
770 $res = wfQuery( $sql, DB_WRITE, $fname );
771 $oldid = wfInsertID( $res );
772
773 $sql = "INSERT INTO recentchanges (rc_timestamp,rc_cur_time," .
774 "rc_namespace,rc_title,rc_new,rc_minor,rc_bot,rc_cur_id,rc_user," .
775 "rc_user_text,rc_comment,rc_this_oldid,rc_last_oldid) VALUES (" .
776 "'{$now}','{$now}'," . $this->mTitle->getNamespace() . ",'" .
777 wfStrencode( $this->mTitle->getDBkey() ) . "',0,{$me2}," .
778 ( $wgUser->isBot() ? 1 : 0 ) . "," .
779 $this->getID() . "," . $wgUser->getID() . ",'" .
780 wfStrencode( $wgUser->getName() ) . "','" .
781 wfStrencode( $summary ) . "',0,{$oldid})";
782 wfQuery( $sql, DB_WRITE, $fname );
783
784 $sql = "UPDATE recentchanges SET rc_this_oldid={$oldid} " .
785 "WHERE rc_namespace=" . $this->mTitle->getNamespace() . " AND " .
786 "rc_title='" . wfStrencode( $this->mTitle->getDBkey() ) . "' AND " .
787 "rc_timestamp='" . $this->getTimestamp() . "'";
788 wfQuery( $sql, DB_WRITE, $fname );
789
790 $sql = "UPDATE recentchanges SET rc_cur_time='{$now}' " .
791 "WHERE rc_cur_id=" . $this->getID();
792 wfQuery( $sql, DB_WRITE, $fname );
793
794 if ( $wgEnablePersistentLC ) {
795
796 // Purge link cache for this page
797 $pageid=$this->getID();
798 wfQuery("DELETE FROM linkscc WHERE lcc_pageid='{$pageid}'", DB_WRITE);
799
800 // This next query just makes sure stub colored links to this page
801 // are updated correctly (I think). If performance is more important
802 // than real-time updating of stub links, we really should skip
803 // this query.
804 wfQuery("DELETE linkscc FROM linkscc,links ".
805 "WHERE lcc_title=links.l_from AND l_to={$pageid}", DB_WRITE);
806 }
807
808 }
809 if( $wgDBtransactions ) {
810 $sql = "COMMIT";
811 wfQuery( $sql, DB_WRITE );
812 }
813
814 if ($watchthis) {
815 if (!$this->mTitle->userIsWatching()) $this->watch();
816 } else {
817 if ( $this->mTitle->userIsWatching() ) {
818 $this->unwatch();
819 }
820 }
821
822 $this->showArticle( $text, wfMsg( "updated" ) );
823 return true;
824 }
825
826 # After we've either updated or inserted the article, update
827 # the link tables and redirect to the new page.
828
829 function showArticle( $text, $subtitle )
830 {
831 global $wgOut, $wgUser, $wgLinkCache, $wgUseBetterLinksUpdate;
832 global $wgMwRedir;
833
834 $wgLinkCache = new LinkCache();
835
836 # Get old version of link table to allow incremental link updates
837 if ( $wgUseBetterLinksUpdate ) {
838 $wgLinkCache->preFill( $this->mTitle );
839 $wgLinkCache->clear();
840 }
841
842 # Now update the link cache by parsing the text
843 $wgOut = new OutputPage();
844 $wgOut->addWikiText( $text );
845
846 $this->editUpdates( $text );
847 if( $wgMwRedir->matchStart( $text ) )
848 $r = "redirect=no";
849 else
850 $r = "";
851 $wgOut->redirect( wfLocalUrl( $this->mTitle->getPrefixedURL(), $r ) );
852 }
853
854 # Add this page to my watchlist
855
856 function watch( $add = true )
857 {
858 global $wgUser, $wgOut, $wgLang;
859 global $wgDeferredUpdateList;
860
861 if ( 0 == $wgUser->getID() ) {
862 $wgOut->errorpage( "watchnologin", "watchnologintext" );
863 return;
864 }
865 if ( wfReadOnly() ) {
866 $wgOut->readOnlyPage();
867 return;
868 }
869 if( $add )
870 $wgUser->addWatch( $this->mTitle );
871 else
872 $wgUser->removeWatch( $this->mTitle );
873
874 $wgOut->setPagetitle( wfMsg( $add ? "addedwatch" : "removedwatch" ) );
875 $wgOut->setRobotpolicy( "noindex,follow" );
876
877 $sk = $wgUser->getSkin() ;
878 $link = $sk->makeKnownLink ( $this->mTitle->getPrefixedText() ) ;
879
880 if($add)
881 $text = wfMsg( "addedwatchtext", $link );
882 else
883 $text = wfMsg( "removedwatchtext", $link );
884 $wgOut->addHTML( $text );
885
886 $up = new UserUpdate();
887 array_push( $wgDeferredUpdateList, $up );
888
889 $wgOut->returnToMain( false );
890 }
891
892 function unwatch()
893 {
894 $this->watch( false );
895 }
896
897 # This shares a lot of issues (and code) with Recent Changes
898
899 function history()
900 {
901 global $wgUser, $wgOut, $wgLang, $offset, $limit;
902
903 # If page hasn't changed, client can cache this
904
905 $wgOut->checkLastModified( $this->getTimestamp() );
906 $fname = "Article::history";
907 wfProfileIn( $fname );
908
909 $wgOut->setPageTitle( $this->mTitle->getPRefixedText() );
910 $wgOut->setSubtitle( wfMsg( "revhistory" ) );
911 $wgOut->setArticleFlag( false );
912 $wgOut->setRobotpolicy( "noindex,nofollow" );
913
914 if( $this->mTitle->getArticleID() == 0 ) {
915 $wgOut->addHTML( wfMsg( "nohistory" ) );
916 wfProfileOut( $fname );
917 return;
918 }
919
920 $offset = (int)$offset;
921 $limit = (int)$limit;
922 if( $limit == 0 ) $limit = 50;
923 $namespace = $this->mTitle->getNamespace();
924 $title = $this->mTitle->getText();
925 $sql = "SELECT old_id,old_user," .
926 "old_comment,old_user_text,old_timestamp,old_minor_edit ".
927 "FROM old USE INDEX (name_title_timestamp) " .
928 "WHERE old_namespace={$namespace} AND " .
929 "old_title='" . wfStrencode( $this->mTitle->getDBkey() ) . "' " .
930 "ORDER BY inverse_timestamp LIMIT $offset, $limit";
931 $res = wfQuery( $sql, DB_READ, "Article::history" );
932
933 $revs = wfNumRows( $res );
934 if( $this->mTitle->getArticleID() == 0 ) {
935 $wgOut->addHTML( wfMsg( "nohistory" ) );
936 wfProfileOut( $fname );
937 return;
938 }
939
940 $sk = $wgUser->getSkin();
941 $numbar = wfViewPrevNext(
942 $offset, $limit,
943 $this->mTitle->getPrefixedText(),
944 "action=history" );
945 $s = $numbar;
946 $s .= $sk->beginHistoryList();
947
948 if($offset == 0 )
949 $s .= $sk->historyLine( $this->getTimestamp(), $this->getUser(),
950 $this->getUserText(), $namespace,
951 $title, 0, $this->getComment(),
952 ( $this->getMinorEdit() > 0 ) );
953
954 $revs = wfNumRows( $res );
955 while ( $line = wfFetchObject( $res ) ) {
956 $s .= $sk->historyLine( $line->old_timestamp, $line->old_user,
957 $line->old_user_text, $namespace,
958 $title, $line->old_id,
959 $line->old_comment, ( $line->old_minor_edit > 0 ) );
960 }
961 $s .= $sk->endHistoryList();
962 $s .= $numbar;
963 $wgOut->addHTML( $s );
964 wfProfileOut( $fname );
965 }
966
967 function protect( $limit = "sysop" )
968 {
969 global $wgUser, $wgOut;
970
971 if ( ! $wgUser->isSysop() ) {
972 $wgOut->sysopRequired();
973 return;
974 }
975 if ( wfReadOnly() ) {
976 $wgOut->readOnlyPage();
977 return;
978 }
979 $id = $this->mTitle->getArticleID();
980 if ( 0 == $id ) {
981 $wgOut->fatalEror( wfMsg( "badarticleerror" ) );
982 return;
983 }
984 $sql = "UPDATE cur SET cur_touched='" . wfTimestampNow() . "'," .
985 "cur_restrictions='{$limit}' WHERE cur_id={$id}";
986 wfQuery( $sql, DB_WRITE, "Article::protect" );
987
988 $log = new LogPage( wfMsg( "protectlogpage" ), wfMsg( "protectlogtext" ) );
989 if ( $limit === "" ) {
990 $log->addEntry( wfMsg( "unprotectedarticle", $wgTitle->getPrefixedText() ), "" );
991 } else {
992 $log->addEntry( wfMsg( "protectedarticle", $wgTitle->getPrefixedText() ), "" );
993 }
994 $wgOut->redirect( wfLocalUrl( $this->mTitle->getPrefixedURL() ) );
995 }
996
997 function unprotect()
998 {
999 return $this->protect( "" );
1000 }
1001
1002 function delete()
1003 {
1004 global $wgUser, $wgOut;
1005 global $wpConfirm, $wpReason, $image, $oldimage;
1006
1007 # This code desperately needs to be totally rewritten
1008
1009 if ( ( ! $wgUser->isSysop() ) ) {
1010 $wgOut->sysopRequired();
1011 return;
1012 }
1013 if ( wfReadOnly() ) {
1014 $wgOut->readOnlyPage();
1015 return;
1016 }
1017
1018 # Better double-check that it hasn't been deleted yet!
1019 $wgOut->setPagetitle( wfMsg( "confirmdelete" ) );
1020 if ( ( "" == trim( $this->mTitle->getText() ) )
1021 or ( $this->mTitle->getArticleId() == 0 ) ) {
1022 $wgOut->fatalError( wfMsg( "cannotdelete" ) );
1023 return;
1024 }
1025
1026 # determine whether this page has earlier revisions
1027 # and insert a warning if it does
1028 # we select the text because it might be useful below
1029 $sql="SELECT old_text FROM old WHERE old_namespace=0 and old_title='" . wfStrencode($this->mTitle->getPrefixedDBkey())."' ORDER BY inverse_timestamp LIMIT 1";
1030 $res=wfQuery($sql, DB_READ, $fname);
1031 if( ($old=wfFetchObject($res)) && !$wpConfirm ) {
1032 $skin=$wgUser->getSkin();
1033 $wgOut->addHTML("<B>".wfMsg("historywarning"));
1034 $wgOut->addHTML( $skin->historyLink() ."</B><P>");
1035 }
1036
1037 $sql="SELECT cur_text FROM cur WHERE cur_namespace=0 and cur_title='" . wfStrencode($this->mTitle->getPrefixedDBkey())."'";
1038 $res=wfQuery($sql, DB_READ, $fname);
1039 if( ($s=wfFetchObject($res))) {
1040
1041 # if this is a mini-text, we can paste part of it into the deletion reason
1042
1043 #if this is empty, an earlier revision may contain "useful" text
1044 if($s->cur_text!="") {
1045 $text=$s->cur_text;
1046 } else {
1047 if($old) {
1048 $text=$old->old_text;
1049 $blanked=1;
1050 }
1051
1052 }
1053
1054 $length=strlen($text);
1055
1056 # this should not happen, since it is not possible to store an empty, new
1057 # page. Let's insert a standard text in case it does, though
1058 if($length==0 && !$wpReason) { $wpReason=wfmsg("exblank");}
1059
1060
1061 if($length < 500 && !$wpReason) {
1062
1063 # comment field=255, let's grep the first 150 to have some user
1064 # space left
1065 $text=substr($text,0,150);
1066 # let's strip out newlines and HTML tags
1067 $text=preg_replace("/\"/","'",$text);
1068 $text=preg_replace("/\</","&lt;",$text);
1069 $text=preg_replace("/\>/","&gt;",$text);
1070 $text=preg_replace("/[\n\r]/","",$text);
1071 if(!$blanked) {
1072 $wpReason=wfMsg("excontent"). " '".$text;
1073 } else {
1074 $wpReason=wfMsg("exbeforeblank") . " '".$text;
1075 }
1076 if($length>150) { $wpReason .= "..."; } # we've only pasted part of the text
1077 $wpReason.="'";
1078 }
1079 }
1080
1081 return $this->confirmDelete();
1082 }
1083
1084 function confirmDelete( $par = "" )
1085 {
1086 global $wgOut;
1087
1088 $sub = htmlspecialchars( $this->mTitle->getPrefixedText() );
1089 $wgOut->setSubtitle( wfMsg( "deletesub", $sub ) );
1090 $wgOut->setRobotpolicy( "noindex,nofollow" );
1091 $wgOut->addWikiText( wfMsg( "confirmdeletetext" ) );
1092
1093 $t = $this->mTitle->getPrefixedURL();
1094
1095 $formaction = wfEscapeHTML( wfLocalUrl( $t, "action=delete" . $par ) );
1096 $confirm = wfMsg( "confirm" );
1097 $check = wfMsg( "confirmcheck" );
1098 $delcom = wfMsg( "deletecomment" );
1099
1100 $wgOut->addHTML( "
1101 <form id=\"deleteconfirm\" method=\"post\" action=\"{$formaction}\">
1102 <table border=0><tr><td align=right>
1103 {$delcom}:</td><td align=left>
1104 <input type=text size=60 name=\"wpReason\" value=\"{$wpReason}\">
1105 </td></tr><tr><td>&nbsp;</td></tr>
1106 <tr><td align=right>
1107 <input type=checkbox name=\"wpConfirm\" value='1' id=\"wpConfirm\">
1108 </td><td><label for=\"wpConfirm\">{$check}</label></td>
1109 </tr><tr><td>&nbsp;</td><td>
1110 <input type=submit name=\"wpConfirmB\" value=\"{$confirm}\">
1111 </td></tr></table></form>\n" );
1112
1113 $wgOut->returnToMain( false );
1114 }
1115
1116 function doDelete()
1117 {
1118 global $wgOut, $wgUser, $wgLang;
1119 global $wpReason;
1120 $fname = "Article::doDelete";
1121
1122 $this->doDeleteArticle( $this->mTitle );
1123 $deleted = $this->mTitle->getPrefixedText();
1124
1125 $wgOut->setPagetitle( wfMsg( "actioncomplete" ) );
1126 $wgOut->setRobotpolicy( "noindex,nofollow" );
1127
1128 $sk = $wgUser->getSkin();
1129 $loglink = $sk->makeKnownLink( $wgLang->getNsText(
1130 Namespace::getWikipedia() ) .
1131 ":" . wfMsg( "dellogpage" ), wfMsg( "deletionlog" ) );
1132
1133 $text = str_replace( "$1" , $deleted, wfMsg( "deletedtext" ) );
1134 $text = str_replace( "$2", $loglink, $text );
1135
1136 $wgOut->addHTML( "<p>" . $text );
1137 $wgOut->returnToMain( false );
1138 }
1139
1140 function doDeleteArticle( $title )
1141 {
1142 global $wgUser, $wgOut, $wgLang, $wpReason, $wgDeferredUpdateList;
1143
1144 $fname = "Article::doDeleteArticle";
1145 $ns = $title->getNamespace();
1146 $t = wfStrencode( $title->getDBkey() );
1147 $id = $title->getArticleID();
1148
1149 if ( "" == $t ) {
1150 $wgOut->fatalError( wfMsg( "cannotdelete" ) );
1151 return;
1152 }
1153
1154 $u = new SiteStatsUpdate( 0, 1, -$this->isCountable( $this->getContent( true ) ) );
1155 array_push( $wgDeferredUpdateList, $u );
1156
1157 # Move article and history to the "archive" table
1158 $sql = "INSERT INTO archive (ar_namespace,ar_title,ar_text," .
1159 "ar_comment,ar_user,ar_user_text,ar_timestamp,ar_minor_edit," .
1160 "ar_flags) SELECT cur_namespace,cur_title,cur_text,cur_comment," .
1161 "cur_user,cur_user_text,cur_timestamp,cur_minor_edit,0 FROM cur " .
1162 "WHERE cur_namespace={$ns} AND cur_title='{$t}'";
1163 wfQuery( $sql, DB_WRITE, $fname );
1164
1165 $sql = "INSERT INTO archive (ar_namespace,ar_title,ar_text," .
1166 "ar_comment,ar_user,ar_user_text,ar_timestamp,ar_minor_edit," .
1167 "ar_flags) SELECT old_namespace,old_title,old_text,old_comment," .
1168 "old_user,old_user_text,old_timestamp,old_minor_edit,old_flags " .
1169 "FROM old WHERE old_namespace={$ns} AND old_title='{$t}'";
1170 wfQuery( $sql, DB_WRITE, $fname );
1171
1172 # Now that it's safely backed up, delete it
1173
1174 $sql = "DELETE FROM cur WHERE cur_namespace={$ns} AND " .
1175 "cur_title='{$t}'";
1176 wfQuery( $sql, DB_WRITE, $fname );
1177
1178 $sql = "DELETE FROM old WHERE old_namespace={$ns} AND " .
1179 "old_title='{$t}'";
1180 wfQuery( $sql, DB_WRITE, $fname );
1181
1182 $sql = "DELETE FROM recentchanges WHERE rc_namespace={$ns} AND " .
1183 "rc_title='{$t}'";
1184 wfQuery( $sql, DB_WRITE, $fname );
1185
1186 # Finally, clean up the link tables
1187
1188 if ( 0 != $id ) {
1189
1190 $t = wfStrencode( $title->getPrefixedDBkey() );
1191
1192 if ( $wgEnablePersistentLC ) {
1193 // Purge related entries in links cache on delete,
1194 wfQuery("DELETE linkscc FROM linkscc,links ".
1195 "WHERE lcc_title=links.l_from AND l_to={$id}", DB_WRITE);
1196 wfQuery("DELETE FROM linkscc WHERE lcc_title='{$t}'", DB_WRITE);
1197 }
1198
1199 $sql = "SELECT l_from FROM links WHERE l_to={$id}";
1200 $res = wfQuery( $sql, DB_READ, $fname );
1201
1202 $sql = "INSERT INTO brokenlinks (bl_from,bl_to) VALUES ";
1203 $now = wfTimestampNow();
1204 $sql2 = "UPDATE cur SET cur_touched='{$now}' WHERE cur_id IN (";
1205 $first = true;
1206
1207 while ( $s = wfFetchObject( $res ) ) {
1208 $nt = Title::newFromDBkey( $s->l_from );
1209 $lid = $nt->getArticleID();
1210
1211 if ( ! $first ) { $sql .= ","; $sql2 .= ","; }
1212 $first = false;
1213 $sql .= "({$lid},'{$t}')";
1214 $sql2 .= "{$lid}";
1215 }
1216 $sql2 .= ")";
1217 if ( ! $first ) {
1218 wfQuery( $sql, DB_WRITE, $fname );
1219 wfQuery( $sql2, DB_WRITE, $fname );
1220 }
1221 wfFreeResult( $res );
1222
1223 $sql = "DELETE FROM links WHERE l_to={$id}";
1224 wfQuery( $sql, DB_WRITE, $fname );
1225
1226 $sql = "DELETE FROM links WHERE l_from='{$t}'";
1227 wfQuery( $sql, DB_WRITE, $fname );
1228
1229 $sql = "DELETE FROM imagelinks WHERE il_from='{$t}'";
1230 wfQuery( $sql, DB_WRITE, $fname );
1231
1232 $sql = "DELETE FROM brokenlinks WHERE bl_from={$id}";
1233 wfQuery( $sql, DB_WRITE, $fname );
1234 }
1235
1236 $log = new LogPage( wfMsg( "dellogpage" ), wfMsg( "dellogpagetext" ) );
1237 $art = $title->getPrefixedText();
1238 $wpReason = wfCleanQueryVar( $wpReason );
1239 $log->addEntry( str_replace( "$1", $art, wfMsg( "deletedarticle" ) ), $wpReason );
1240
1241 # Clear the cached article id so the interface doesn't act like we exist
1242 $this->mTitle->resetArticleID( 0 );
1243 $this->mTitle->mArticleID = 0;
1244 }
1245
1246 function rollback()
1247 {
1248 global $wgUser, $wgLang, $wgOut, $from;
1249
1250 if ( ! $wgUser->isSysop() ) {
1251 $wgOut->sysopRequired();
1252 return;
1253 }
1254
1255 # Replace all this user's current edits with the next one down
1256 $tt = wfStrencode( $this->mTitle->getDBKey() );
1257 $n = $this->mTitle->getNamespace();
1258
1259 # Get the last editor
1260 $sql = "SELECT cur_id,cur_user,cur_user_text,cur_comment FROM cur WHERE cur_title='{$tt}' AND cur_namespace={$n}";
1261 $res = wfQuery( $sql, DB_READ );
1262 if( ($x = wfNumRows( $res )) != 1 ) {
1263 # Something wrong
1264 $wgOut->addHTML( wfMsg( "notanarticle" ) );
1265 return;
1266 }
1267 $s = wfFetchObject( $res );
1268 $ut = wfStrencode( $s->cur_user_text );
1269 $uid = $s->cur_user;
1270 $pid = $s->cur_id;
1271
1272 $from = str_replace( '_', ' ', wfCleanQueryVar( $from ) );
1273 if( $from != $s->cur_user_text ) {
1274 $wgOut->setPageTitle(wfmsg("rollbackfailed"));
1275 $wgOut->addWikiText( wfMsg( "alreadyrolled",
1276 htmlspecialchars( $this->mTitle->getPrefixedText()),
1277 htmlspecialchars( $from ),
1278 htmlspecialchars( $s->cur_user_text ) ) );
1279 if($s->cur_comment != "") {
1280 $wgOut->addHTML(
1281 wfMsg("editcomment",
1282 htmlspecialchars( $s->cur_comment ) ) );
1283 }
1284 return;
1285 }
1286
1287 # Get the last edit not by this guy
1288 $sql = "SELECT old_text,old_user,old_user_text
1289 FROM old USE INDEX (name_title_timestamp)
1290 WHERE old_namespace={$n} AND old_title='{$tt}'
1291 AND (old_user <> {$uid} OR old_user_text <> '{$ut}')
1292 ORDER BY inverse_timestamp LIMIT 1";
1293 $res = wfQuery( $sql, DB_READ );
1294 if( wfNumRows( $res ) != 1 ) {
1295 # Something wrong
1296 $wgOut->setPageTitle(wfMsg("rollbackfailed"));
1297 $wgOut->addHTML( wfMsg( "cantrollback" ) );
1298 return;
1299 }
1300 $s = wfFetchObject( $res );
1301
1302 # Save it!
1303 $newcomment = str_replace( "$1", $s->old_user_text, wfMsg( "revertpage" ) );
1304 $wgOut->setPagetitle( wfMsg( "actioncomplete" ) );
1305 $wgOut->setRobotpolicy( "noindex,nofollow" );
1306 $wgOut->addHTML( "<h2>" . $newcomment . "</h2>\n<hr>\n" );
1307 $this->updateArticle( $s->old_text, $newcomment, 1, $this->mTitle->userIsWatching() );
1308
1309 $wgOut->returnToMain( false );
1310 }
1311
1312
1313 # Do standard deferred updates after page view
1314
1315 /* private */ function viewUpdates()
1316 {
1317 global $wgDeferredUpdateList;
1318
1319 if ( 0 != $this->getID() ) {
1320 global $wgDisableCounters;
1321 if( !$wgDisableCounters ) {
1322 $u = new ViewCountUpdate( $this->getID() );
1323 array_push( $wgDeferredUpdateList, $u );
1324 $u = new SiteStatsUpdate( 1, 0, 0 );
1325 array_push( $wgDeferredUpdateList, $u );
1326 }
1327 $u = new UserTalkUpdate( 0, $this->mTitle->getNamespace(),
1328 $this->mTitle->getDBkey() );
1329 array_push( $wgDeferredUpdateList, $u );
1330 }
1331 }
1332
1333 # Do standard deferred updates after page edit.
1334 # Every 1000th edit, prune the recent changes table.
1335
1336 /* private */ function editUpdates( $text )
1337 {
1338 global $wgDeferredUpdateList, $wgDBname, $wgMemc;
1339
1340 wfSeedRandom();
1341 if ( 0 == mt_rand( 0, 999 ) ) {
1342 $cutoff = wfUnix2Timestamp( time() - ( 7 * 86400 ) );
1343 $sql = "DELETE FROM recentchanges WHERE rc_timestamp < '{$cutoff}'";
1344 wfQuery( $sql, DB_WRITE );
1345 }
1346 $id = $this->getID();
1347 $title = $this->mTitle->getPrefixedDBkey();
1348 $adj = $this->mCountAdjustment;
1349
1350 if ( 0 != $id ) {
1351 $u = new LinksUpdate( $id, $title );
1352 array_push( $wgDeferredUpdateList, $u );
1353 $u = new SiteStatsUpdate( 0, 1, $adj );
1354 array_push( $wgDeferredUpdateList, $u );
1355 $u = new SearchUpdate( $id, $title, $text );
1356 array_push( $wgDeferredUpdateList, $u );
1357
1358 $u = new UserTalkUpdate( 1, $this->mTitle->getNamespace(),
1359 $this->mTitle->getDBkey() );
1360 array_push( $wgDeferredUpdateList, $u );
1361
1362 if ( $this->getNamespace == NS_MEDIAWIKI ) {
1363 $messageCache = $wgMemc->get( "$wgDBname:messages" );
1364 if (!$messageCache) {
1365 $messageCache = wfLoadAllMessages();
1366 }
1367 $messageCache[$title] = $text;
1368 $wgMemc->set( "$wgDBname:messages" );
1369 }
1370 }
1371 }
1372
1373 /* private */ function setOldSubtitle()
1374 {
1375 global $wgLang, $wgOut;
1376
1377 $td = $wgLang->timeanddate( $this->mTimestamp, true );
1378 $r = str_replace( "$1", "{$td}", wfMsg( "revisionasof" ) );
1379 $wgOut->setSubtitle( "({$r})" );
1380 }
1381
1382 function blockedIPpage()
1383 {
1384 global $wgOut, $wgUser, $wgLang;
1385
1386 $wgOut->setPageTitle( wfMsg( "blockedtitle" ) );
1387 $wgOut->setRobotpolicy( "noindex,nofollow" );
1388 $wgOut->setArticleFlag( false );
1389
1390 $id = $wgUser->blockedBy();
1391 $reason = $wgUser->blockedFor();
1392
1393 $name = User::whoIs( $id );
1394 $link = "[[" . $wgLang->getNsText( Namespace::getUser() ) .
1395 ":{$name}|{$name}]]";
1396
1397 $text = str_replace( "$1", $link, wfMsg( "blockedtext" ) );
1398 $text = str_replace( "$2", $reason, $text );
1399 $text = str_replace( "$3", getenv( "REMOTE_ADDR" ), $text );
1400 $wgOut->addWikiText( $text );
1401 $wgOut->returnToMain( false );
1402 }
1403
1404 # This function is called right before saving the wikitext,
1405 # so we can do things like signatures and links-in-context.
1406
1407 function preSaveTransform( $text )
1408 {
1409 $s = "";
1410 while ( "" != $text ) {
1411 $p = preg_split( "/<\\s*nowiki\\s*>/i", $text, 2 );
1412 $s .= $this->pstPass2( $p[0] );
1413
1414 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $text = ""; }
1415 else {
1416 $q = preg_split( "/<\\/\\s*nowiki\\s*>/i", $p[1], 2 );
1417 $s .= "<nowiki>{$q[0]}</nowiki>";
1418 $text = $q[1];
1419 }
1420 }
1421 return rtrim( $s );
1422 }
1423
1424 /* private */ function pstPass2( $text )
1425 {
1426 global $wgUser, $wgLang, $wgLocaltimezone;
1427
1428 # Signatures
1429 #
1430 $n = $wgUser->getName();
1431 $k = $wgUser->getOption( "nickname" );
1432 if ( "" == $k ) { $k = $n; }
1433 if(isset($wgLocaltimezone)) {
1434 $oldtz = getenv("TZ"); putenv("TZ=$wgLocaltimezone");
1435 }
1436 /* Note: this is an ugly timezone hack for the European wikis */
1437 $d = $wgLang->timeanddate( date( "YmdHis" ), false ) .
1438 " (" . date( "T" ) . ")";
1439 if(isset($wgLocaltimezone)) putenv("TZ=$oldtz");
1440
1441 $text = preg_replace( "/~~~~/", "[[" . $wgLang->getNsText(
1442 Namespace::getUser() ) . ":$n|$k]] $d", $text );
1443 $text = preg_replace( "/~~~/", "[[" . $wgLang->getNsText(
1444 Namespace::getUser() ) . ":$n|$k]]", $text );
1445
1446 # Context links: [[|name]] and [[name (context)|]]
1447 #
1448 $tc = "[&;%\\-,.\\(\\)' _0-9A-Za-z\\/:\\x80-\\xff]";
1449 $np = "[&;%\\-,.' _0-9A-Za-z\\/:\\x80-\\xff]"; # No parens
1450 $namespacechar = '[ _0-9A-Za-z\x80-\xff]'; # Namespaces can use non-ascii!
1451 $conpat = "/^({$np}+) \\(({$tc}+)\\)$/";
1452
1453 $p1 = "/\[\[({$np}+) \\(({$np}+)\\)\\|]]/"; # [[page (context)|]]
1454 $p2 = "/\[\[\\|({$tc}+)]]/"; # [[|page]]
1455 $p3 = "/\[\[($namespacechar+):({$np}+)\\|]]/"; # [[namespace:page|]]
1456 $p4 = "/\[\[($namespacechar+):({$np}+) \\(({$np}+)\\)\\|]]/";
1457 # [[ns:page (cont)|]]
1458 $context = "";
1459 $t = $this->mTitle->getText();
1460 if ( preg_match( $conpat, $t, $m ) ) {
1461 $context = $m[2];
1462 }
1463 $text = preg_replace( $p4, "[[\\1:\\2 (\\3)|\\2]]", $text );
1464 $text = preg_replace( $p1, "[[\\1 (\\2)|\\1]]", $text );
1465 $text = preg_replace( $p3, "[[\\1:\\2|\\2]]", $text );
1466
1467 if ( "" == $context ) {
1468 $text = preg_replace( $p2, "[[\\1]]", $text );
1469 } else {
1470 $text = preg_replace( $p2, "[[\\1 ({$context})|\\1]]", $text );
1471 }
1472
1473 # {{SUBST:xxx}} variables
1474 #
1475 $mw =& MagicWord::get( MAG_SUBST );
1476 $text = $mw->substituteCallback( $text, "wfReplaceSubstVar" );
1477
1478 return $text;
1479 }
1480
1481 /* Caching functions */
1482
1483 function tryFileCache() {
1484 if($this->isFileCacheable()) {
1485 $touched = $this->mTouched;
1486 if( strpos( $this->mContent, "{{" ) !== false ) {
1487 # Expire pages with variable replacements in an hour
1488 $expire = wfUnix2Timestamp( time() - 3600 );
1489 $touched = max( $expire, $touched );
1490 }
1491 $cache = new CacheManager( $this->mTitle );
1492 if($cache->isFileCacheGood( $touched )) {
1493 global $wgOut;
1494 wfDebug( " tryFileCache() - about to load\n" );
1495 $cache->loadFromFileCache();
1496 $wgOut->reportTime(); # For profiling
1497 exit;
1498 } else {
1499 wfDebug( " tryFileCache() - starting buffer\n" );
1500 if($cache->useGzip() && wfClientAcceptsGzip()) {
1501 /* For some reason, adding this header line over in
1502 CacheManager::saveToFileCache() fails on my test
1503 setup at home, though it works on the live install.
1504 Make double-sure... --brion */
1505 header( "Content-Encoding: gzip" );
1506 }
1507 ob_start( array(&$cache, 'saveToFileCache' ) );
1508 }
1509 } else {
1510 wfDebug( " tryFileCache() - not cacheable\n" );
1511 }
1512 }
1513
1514 function isFileCacheable() {
1515 global $wgUser, $wgUseFileCache, $wgShowIPinHeader;
1516 global $action, $oldid, $diff, $redirect, $printable;
1517 return $wgUseFileCache
1518 and (!$wgShowIPinHeader)
1519 and ($this->getID() != 0)
1520 and ($wgUser->getId() == 0)
1521 and (!$wgUser->getNewtalk())
1522 and ($this->mTitle->getNamespace != Namespace::getSpecial())
1523 and ($action == "view")
1524 and (!isset($oldid))
1525 and (!isset($diff))
1526 and (!isset($redirect))
1527 and (!isset($printable))
1528 and (!$this->mRedirectedFrom);
1529 }
1530
1531 function checkTouched() {
1532 $id = $this->getID();
1533 $sql = "SELECT cur_touched,cur_is_redirect FROM cur WHERE cur_id=$id";
1534 $res = wfQuery( $sql, DB_READ, "Article::checkTouched" );
1535 if( $s = wfFetchObject( $res ) ) {
1536 $this->mTouched = $s->cur_touched;
1537 return !$s->cur_is_redirect;
1538 } else {
1539 return false;
1540 }
1541 }
1542 }
1543
1544 function wfReplaceSubstVar( $matches ) {
1545 return wfMsg( $matches[1] );
1546 }
1547
1548 ?>