Add two new parameters for editing new pages:
[lhc/web/wiklou.git] / includes / Article.php
1 <?php
2 /**
3 * File for articles
4 * @package MediaWiki
5 */
6
7 /**
8 * Need the CacheManager to be loaded
9 */
10 require_once( 'CacheManager.php' );
11 require_once( 'Revision.php' );
12
13 $wgArticleCurContentFields = false;
14 $wgArticleOldContentFields = false;
15
16 /**
17 * Class representing a MediaWiki article and history.
18 *
19 * See design.txt for an overview.
20 * Note: edit user interface and cache support functions have been
21 * moved to separate EditPage and CacheManager classes.
22 *
23 * @package MediaWiki
24 */
25 class Article {
26 /**#@+
27 * @access private
28 */
29 var $mContent, $mContentLoaded;
30 var $mUser, $mTimestamp, $mUserText;
31 var $mCounter, $mComment, $mGoodAdjustment, $mTotalAdjustment;
32 var $mMinorEdit, $mRedirectedFrom;
33 var $mTouched, $mFileCache, $mTitle;
34 var $mId, $mTable;
35 var $mForUpdate;
36 var $mOldId;
37 var $mRevIdFetched;
38 var $mRevision;
39 /**#@-*/
40
41 /**
42 * Constructor and clear the article
43 * @param mixed &$title
44 */
45 function Article( &$title ) {
46 $this->mTitle =& $title;
47 $this->clear();
48 }
49
50 /**
51 * get the title object of the article
52 * @public
53 */
54 function getTitle() {
55 return $this->mTitle;
56 }
57
58 /**
59 * Clear the object
60 * @private
61 */
62 function clear() {
63 $this->mDataLoaded = false;
64 $this->mContentLoaded = false;
65
66 $this->mCurID = $this->mUser = $this->mCounter = -1; # Not loaded
67 $this->mRedirectedFrom = $this->mUserText =
68 $this->mTimestamp = $this->mComment = $this->mFileCache = '';
69 $this->mGoodAdjustment = $this->mTotalAdjustment = 0;
70 $this->mTouched = '19700101000000';
71 $this->mForUpdate = false;
72 $this->mIsRedirect = false;
73 $this->mRevIdFetched = 0;
74 }
75
76 /**
77 * Note that getContent/loadContent may follow redirects if
78 * not told otherwise, and so may cause a change to mTitle.
79 *
80 * @param $noredir
81 * @return Return the text of this revision
82 */
83 function getContent( $noredir ) {
84 global $wgRequest, $wgUser;
85
86 # Get variables from query string :P
87 $action = $wgRequest->getText( 'action', 'view' );
88 $section = $wgRequest->getText( 'section' );
89 $preload = $wgRequest->getText( 'preload' );
90 $newpagetext = $wgRequest->getText('newpagetext');
91
92 $fname = 'Article::getContent';
93 wfProfileIn( $fname );
94
95 if ( 0 == $this->getID() ) {
96 if ( 'edit' == $action ) {
97 wfProfileOut( $fname );
98 # Should we put something in the textarea?
99 # if &preload=Pagename is set, we try to get
100 # the revision text and put it in.
101 if($preload) {
102 $preloadTitle=Title::newFromText($preload);
103 if($preloadTitle->userCanRead()) {
104 $rev=Revision::newFromTitle($preloadTitle);
105 if($rev) {
106 return $rev->getText();
107 }
108 }
109 }
110 # Don't preload anything.
111 # We used to put MediaWiki:Newarticletext here.
112 # This is now shown above the edit box instead.
113 return '';
114 }
115 wfProfileOut( $fname );
116
117 return wfMsg( 'noarticletext' );
118 } else {
119 $this->loadContent( $noredir );
120 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
121 if ( $this->mTitle->getNamespace() == NS_USER_TALK &&
122 $wgUser->isIP($this->mTitle->getText()) &&
123 $action=='view'
124 ) {
125 wfProfileOut( $fname );
126 return $this->mContent . "\n" .wfMsg('anontalkpagetext');
127 } else {
128 if($action=='edit') {
129 if($section!='') {
130 if($section=='new') {
131 wfProfileOut( $fname );
132 return '';
133 }
134
135 # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML
136 # comments to be stripped as well)
137 $rv=$this->getSection($this->mContent,$section);
138 wfProfileOut( $fname );
139 return $rv;
140 }
141 }
142 wfProfileOut( $fname );
143 return $this->mContent;
144 }
145 }
146 }
147
148 /**
149 * This function returns the text of a section, specified by a number ($section).
150 * A section is text under a heading like == Heading == or <h1>Heading</h1>, or
151 * the first section before any such heading (section 0).
152 *
153 * If a section contains subsections, these are also returned.
154 *
155 * @param string $text text to look in
156 * @param integer $section section number
157 * @return string text of the requested section
158 */
159 function getSection($text,$section) {
160
161 # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML
162 # comments to be stripped as well)
163 $striparray=array();
164 $parser=new Parser();
165 $parser->mOutputType=OT_WIKI;
166 $striptext=$parser->strip($text, $striparray, true);
167
168 # now that we can be sure that no pseudo-sections are in the source,
169 # split it up by section
170 $secs =
171 preg_split(
172 '/(^=+.+?=+|^<h[1-6].*?' . '>.*?<\/h[1-6].*?' . '>)(?!\S)/mi',
173 $striptext, -1,
174 PREG_SPLIT_DELIM_CAPTURE);
175 if($section==0) {
176 $rv=$secs[0];
177 } else {
178 $headline=$secs[$section*2-1];
179 preg_match( '/^(=+).+?=+|^<h([1-6]).*?' . '>.*?<\/h[1-6].*?' . '>(?!\S)/mi',$headline,$matches);
180 $hlevel=$matches[1];
181
182 # translate wiki heading into level
183 if(strpos($hlevel,'=')!==false) {
184 $hlevel=strlen($hlevel);
185 }
186
187 $rv=$headline. $secs[$section*2];
188 $count=$section+1;
189
190 $break=false;
191 while(!empty($secs[$count*2-1]) && !$break) {
192
193 $subheadline=$secs[$count*2-1];
194 preg_match( '/^(=+).+?=+|^<h([1-6]).*?' . '>.*?<\/h[1-6].*?' . '>(?!\S)/mi',$subheadline,$matches);
195 $subhlevel=$matches[1];
196 if(strpos($subhlevel,'=')!==false) {
197 $subhlevel=strlen($subhlevel);
198 }
199 if($subhlevel > $hlevel) {
200 $rv.=$subheadline.$secs[$count*2];
201 }
202 if($subhlevel <= $hlevel) {
203 $break=true;
204 }
205 $count++;
206
207 }
208 }
209 # reinsert stripped tags
210 $rv=$parser->unstrip($rv,$striparray);
211 $rv=$parser->unstripNoWiki($rv,$striparray);
212 $rv=trim($rv);
213 return $rv;
214
215 }
216
217 /**
218 * Return an array of the columns of the "cur"-table
219 */
220 function getContentFields() {
221 return $wgArticleContentFields = array(
222 'old_text','old_flags',
223 'rev_timestamp','rev_user', 'rev_user_text', 'rev_comment','page_counter',
224 'page_namespace', 'page_title', 'page_restrictions','page_touched','page_is_redirect' );
225 }
226
227 /**
228 * Return the oldid of the article that is to be shown.
229 * For requests with a "direction", this is not the oldid of the
230 * query
231 */
232 function getOldID() {
233 global $wgRequest, $wgOut;
234 static $lastid;
235
236 if ( isset( $lastid ) ) {
237 return $lastid;
238 }
239 # Query variables :P
240 $oldid = $wgRequest->getVal( 'oldid' );
241 if ( isset( $oldid ) ) {
242 $oldid = IntVal( $oldid );
243 if ( $wgRequest->getVal( 'direction' ) == 'next' ) {
244 $nextid = $this->mTitle->getNextRevisionID( $oldid );
245 if ( $nextid ) {
246 $oldid = $nextid;
247 } else {
248 $wgOut->redirect( $this->mTitle->getFullURL( 'redirect=no' ) );
249 }
250 } elseif ( $wgRequest->getVal( 'direction' ) == 'prev' ) {
251 $previd = $this->mTitle->getPreviousRevisionID( $oldid );
252 if ( $previd ) {
253 $oldid = $previd;
254 } else {
255 # TODO
256 }
257 }
258 $lastid = $oldid;
259 }
260 return @$oldid; # "@" to be able to return "unset" without PHP complaining
261 }
262
263
264 /**
265 * Load the revision (including cur_text) into this object
266 */
267 function loadContent( $noredir = false ) {
268 global $wgOut, $wgRequest;
269
270 if ( $this->mContentLoaded ) return;
271
272 # Query variables :P
273 $oldid = $this->getOldID();
274 $redirect = $wgRequest->getVal( 'redirect' );
275
276 $fname = 'Article::loadContent';
277
278 # Pre-fill content with error message so that if something
279 # fails we'll have something telling us what we intended.
280
281 $t = $this->mTitle->getPrefixedText();
282
283 $noredir = $noredir || ($wgRequest->getVal( 'redirect' ) == 'no')
284 || $wgRequest->getCheck( 'rdfrom' );
285 $this->mOldId = $oldid;
286 $this->fetchContent( $oldid, $noredir, true );
287 }
288
289
290 /**
291 * Fetch a page record with the given conditions
292 * @param Database $dbr
293 * @param array $conditions
294 * @access private
295 */
296 function pageData( &$dbr, $conditions ) {
297 return $dbr->selectRow( 'page',
298 array(
299 'page_id',
300 'page_namespace',
301 'page_title',
302 'page_restrictions',
303 'page_counter',
304 'page_is_redirect',
305 'page_is_new',
306 'page_random',
307 'page_touched',
308 'page_latest',
309 'page_len' ),
310 $conditions,
311 'Article::pageData' );
312 }
313
314 function pageDataFromTitle( &$dbr, $title ) {
315 return $this->pageData( $dbr, array(
316 'page_namespace' => $title->getNamespace(),
317 'page_title' => $title->getDBkey() ) );
318 }
319
320 function pageDataFromId( &$dbr, $id ) {
321 return $this->pageData( $dbr, array(
322 'page_id' => IntVal( $id ) ) );
323 }
324
325 /**
326 * Set the general counter, title etc data loaded from
327 * some source.
328 *
329 * @param object $data
330 * @access private
331 */
332 function loadPageData( $data ) {
333 $this->mTitle->loadRestrictions( $data->page_restrictions );
334 $this->mTitle->mRestrictionsLoaded = true;
335
336 $this->mCounter = $data->page_counter;
337 $this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
338 $this->mIsRedirect = $data->page_is_redirect;
339 $this->mLatest = $data->page_latest;
340
341 $this->mDataLoaded = true;
342 }
343
344 /**
345 * Get text of an article from database
346 * @param int $oldid 0 for whatever the latest revision is
347 * @param bool $noredir Set to false to follow redirects
348 * @param bool $globalTitle Set to true to change the global $wgTitle object when following redirects or other unexpected title changes
349 * @return string
350 */
351 function fetchContent( $oldid = 0, $noredir = true, $globalTitle = false ) {
352 if ( $this->mContentLoaded ) {
353 return $this->mContent;
354 }
355 $dbr =& $this->getDB();
356 $fname = 'Article::fetchContent';
357
358 # Pre-fill content with error message so that if something
359 # fails we'll have something telling us what we intended.
360 $t = $this->mTitle->getPrefixedText();
361 if( $oldid ) {
362 $t .= ',oldid='.$oldid;
363 }
364 if( isset( $redirect ) ) {
365 $redirect = ($redirect == 'no') ? 'no' : 'yes';
366 $t .= ',redirect='.$redirect;
367 }
368 $this->mContent = wfMsg( 'missingarticle', $t );
369
370 if( $oldid ) {
371 $revision = Revision::newFromId( $oldid );
372 if( is_null( $revision ) ) {
373 wfDebug( "$fname failed to retrieve specified revision, id $oldid\n" );
374 return false;
375 }
376 $data = $this->pageDataFromId( $dbr, $revision->getPage() );
377 if( !$data ) {
378 wfDebug( "$fname failed to get page data linked to revision id $oldid\n" );
379 return false;
380 }
381 $this->mTitle = Title::makeTitle( $data->page_namespace, $data->page_title );
382 $this->loadPageData( $data );
383 } else {
384 if( !$this->mDataLoaded ) {
385 $data = $this->pageDataFromTitle( $dbr, $this->mTitle );
386 if( !$data ) {
387 wfDebug( "$fname failed to find page data for title " . $this->mTitle->getPrefixedText() . "\n" );
388 return false;
389 }
390 $this->loadPageData( $data );
391 }
392 $revision = Revision::newFromId( $this->mLatest );
393 if( is_null( $revision ) ) {
394 wfDebug( "$fname failed to retrieve current page, rev_id $data->page_latest\n" );
395 return false;
396 }
397 }
398
399 # If we got a redirect, follow it (unless we've been told
400 # not to by either the function parameter or the query
401 if ( !$oldid && !$noredir ) {
402 $rt = Title::newFromRedirect( $revision->getText() );
403 # process if title object is valid and not special:userlogout
404 if ( $rt && ! ( $rt->getNamespace() == NS_SPECIAL && $rt->getText() == 'Userlogout' ) ) {
405 # Gotta hand redirects to special pages differently:
406 # Fill the HTTP response "Location" header and ignore
407 # the rest of the page we're on.
408 global $wgDisableHardRedirects;
409 if( $globalTitle && !$wgDisableHardRedirects ) {
410 global $wgOut;
411 if ( $rt->getInterwiki() != '' && $rt->isLocal() ) {
412 $source = $this->mTitle->getFullURL( 'redirect=no' );
413 $wgOut->redirect( $rt->getFullURL( 'rdfrom=' . urlencode( $source ) ) ) ;
414 return false;
415 }
416 if ( $rt->getNamespace() == NS_SPECIAL ) {
417 $wgOut->redirect( $rt->getFullURL() );
418 return false;
419 }
420 }
421 $redirData = $this->pageDataFromTitle( $dbr, $rt );
422 if( $redirData ) {
423 $redirRev = Revision::newFromId( $redirData->page_latest );
424 if( !is_null( $redirRev ) ) {
425 $this->mRedirectedFrom = $this->mTitle->getPrefixedText();
426 $this->mTitle = $rt;
427 $data = $redirData;
428 $this->loadPageData( $data );
429 $revision = $redirRev;
430 }
431 }
432 }
433 }
434
435 # if the title's different from expected, update...
436 if( $globalTitle ) {
437 global $wgTitle;
438 if( !$this->mTitle->equals( $wgTitle ) ) {
439 $wgTitle = $this->mTitle;
440 }
441 }
442
443 # Back to the business at hand...
444 $this->mContent = $revision->getText();
445
446 $this->mUser = $revision->getUser();
447 $this->mUserText = $revision->getUserText();
448 $this->mComment = $revision->getComment();
449 $this->mTimestamp = wfTimestamp( TS_MW, $revision->getTimestamp() );
450
451 $this->mRevIdFetched = $revision->getID();
452 $this->mContentLoaded = true;
453 $this->mRevision =& $revision;
454
455 return $this->mContent;
456 }
457
458 /**
459 * Gets the article text without using so many damn globals
460 * Returns false on error
461 *
462 * @param integer $oldid
463 */
464 function getContentWithoutUsingSoManyDamnGlobals( $oldid = 0, $noredir = false ) {
465 return $this->fetchContent( $oldid, $noredir, false );
466 }
467
468 /**
469 * Read/write accessor to select FOR UPDATE
470 */
471 function forUpdate( $x = NULL ) {
472 return wfSetVar( $this->mForUpdate, $x );
473 }
474
475 /**
476 * Get the database which should be used for reads
477 */
478 function &getDB() {
479 #if ( $this->mForUpdate ) {
480 return wfGetDB( DB_MASTER );
481 #} else {
482 # return wfGetDB( DB_SLAVE );
483 #}
484 }
485
486 /**
487 * Get options for all SELECT statements
488 * Can pass an option array, to which the class-wide options will be appended
489 */
490 function getSelectOptions( $options = '' ) {
491 if ( $this->mForUpdate ) {
492 if ( is_array( $options ) ) {
493 $options[] = 'FOR UPDATE';
494 } else {
495 $options = 'FOR UPDATE';
496 }
497 }
498 return $options;
499 }
500
501 /**
502 * Return the Article ID
503 */
504 function getID() {
505 if( $this->mTitle ) {
506 return $this->mTitle->getArticleID();
507 } else {
508 return 0;
509 }
510 }
511
512 /**
513 * Returns true if this article exists in the database.
514 * @return bool
515 */
516 function exists() {
517 return $this->getId() != 0;
518 }
519
520 /**
521 * Get the view count for this article
522 */
523 function getCount() {
524 if ( -1 == $this->mCounter ) {
525 $id = $this->getID();
526 $dbr =& $this->getDB();
527 $this->mCounter = $dbr->selectField( 'page', 'page_counter', array( 'page_id' => $id ),
528 'Article::getCount', $this->getSelectOptions() );
529 }
530 return $this->mCounter;
531 }
532
533 /**
534 * Would the given text make this article a "good" article (i.e.,
535 * suitable for including in the article count)?
536 * @param string $text Text to analyze
537 * @return integer 1 if it can be counted else 0
538 */
539 function isCountable( $text ) {
540 global $wgUseCommaCount;
541
542 if ( NS_MAIN != $this->mTitle->getNamespace() ) { return 0; }
543 if ( $this->isRedirect( $text ) ) { return 0; }
544 $token = ($wgUseCommaCount ? ',' : '[[' );
545 if ( false === strstr( $text, $token ) ) { return 0; }
546 return 1;
547 }
548
549 /**
550 * Tests if the article text represents a redirect
551 */
552 function isRedirect( $text = false ) {
553 if ( $text === false ) {
554 $this->loadContent();
555 $titleObj = Title::newFromRedirect( $this->fetchContent() );
556 } else {
557 $titleObj = Title::newFromRedirect( $text );
558 }
559 return $titleObj !== NULL;
560 }
561
562 /**
563 * Returns true if the currently-referenced revision is the current edit
564 * to this page (and it exists).
565 * @return bool
566 */
567 function isCurrent() {
568 return $this->exists() &&
569 isset( $this->mRevision ) &&
570 $this->mRevision->isCurrent();
571 }
572
573 /**
574 * Loads everything except the text
575 * This isn't necessary for all uses, so it's only done if needed.
576 * @private
577 */
578 function loadLastEdit() {
579 global $wgOut;
580
581 if ( -1 != $this->mUser )
582 return;
583
584 # New or non-existent articles have no user information
585 $id = $this->getID();
586 if ( 0 == $id ) return;
587
588 $this->mLastRevision = Revision::loadFromPageId( $this->getDB(), $id );
589 if( !is_null( $this->mLastRevision ) ) {
590 $this->mUser = $this->mLastRevision->getUser();
591 $this->mUserText = $this->mLastRevision->getUserText();
592 $this->mTimestamp = $this->mLastRevision->getTimestamp();
593 $this->mComment = $this->mLastRevision->getComment();
594 $this->mMinorEdit = $this->mLastRevision->isMinor();
595 }
596 }
597
598 function getTimestamp() {
599 $this->loadLastEdit();
600 return $this->mTimestamp;
601 }
602
603 function getUser() {
604 $this->loadLastEdit();
605 return $this->mUser;
606 }
607
608 function getUserText() {
609 $this->loadLastEdit();
610 return $this->mUserText;
611 }
612
613 function getComment() {
614 $this->loadLastEdit();
615 return $this->mComment;
616 }
617
618 function getMinorEdit() {
619 $this->loadLastEdit();
620 return $this->mMinorEdit;
621 }
622
623 function getRevIdFetched() {
624 $this->loadLastEdit();
625 return $this->mRevIdFetched;
626 }
627
628 function getContributors($limit = 0, $offset = 0) {
629 $fname = 'Article::getContributors';
630
631 # XXX: this is expensive; cache this info somewhere.
632
633 $title = $this->mTitle;
634 $contribs = array();
635 $dbr =& $this->getDB();
636 $revTable = $dbr->tableName( 'revision' );
637 $userTable = $dbr->tableName( 'user' );
638 $encDBkey = $dbr->addQuotes( $title->getDBkey() );
639 $ns = $title->getNamespace();
640 $user = $this->getUser();
641 $pageId = $this->getId();
642
643 $sql = "SELECT rev_user, rev_user_text, user_real_name, MAX(rev_timestamp) as timestamp
644 FROM $revTable LEFT JOIN $userTable ON rev_user = user_id
645 WHERE rev_page = $pageId
646 AND rev_user != $user
647 GROUP BY rev_user, rev_user_text, user_real_name
648 ORDER BY timestamp DESC";
649
650 if ($limit > 0) { $sql .= ' LIMIT '.$limit; }
651 $sql .= ' '. $this->getSelectOptions();
652
653 $res = $dbr->query($sql, $fname);
654
655 while ( $line = $dbr->fetchObject( $res ) ) {
656 $contribs[] = array($line->rev_user, $line->rev_user_text, $line->user_real_name);
657 }
658
659 $dbr->freeResult($res);
660 return $contribs;
661 }
662
663 /**
664 * This is the default action of the script: just view the page of
665 * the given title.
666 */
667 function view() {
668 global $wgUser, $wgOut, $wgRequest, $wgOnlySysopsCanPatrol, $wgLang;
669 global $wgLinkCache, $IP, $wgEnableParserCache, $wgStylePath, $wgUseRCPatrol;
670 global $wgEnotif, $wgParser, $wgParserCache;
671 $sk = $wgUser->getSkin();
672
673 $fname = 'Article::view';
674 wfProfileIn( $fname );
675 # Get variables from query string
676 $oldid = $this->getOldID();
677 $diff = $wgRequest->getVal( 'diff' );
678 $rcid = $wgRequest->getVal( 'rcid' );
679 $rdfrom = $wgRequest->getVal( 'rdfrom' );
680
681 $wgOut->setArticleFlag( true );
682 $wgOut->setRobotpolicy( 'index,follow' );
683 # If we got diff and oldid in the query, we want to see a
684 # diff page instead of the article.
685
686 if ( !is_null( $diff ) ) {
687 require_once( 'DifferenceEngine.php' );
688 $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
689
690 $de = new DifferenceEngine( $oldid, $diff, $rcid );
691 // DifferenceEngine directly fetched the revision:
692 $this->mRevIdFetched = $de->mNewid;
693 $de->showDiffPage();
694
695 if( $diff == 0 ) {
696 # Run view updates for current revision only
697 $this->viewUpdates();
698 }
699 wfProfileOut( $fname );
700 return;
701 }
702
703 if ( empty( $oldid ) && $this->checkTouched() ) {
704 $wgOut->setETag($wgParserCache->getETag($this, $wgUser));
705
706 if( $wgOut->checkLastModified( $this->mTouched ) ){
707 wfProfileOut( $fname );
708 return;
709 } else if ( $this->tryFileCache() ) {
710 # tell wgOut that output is taken care of
711 $wgOut->disable();
712 $this->viewUpdates();
713 wfProfileOut( $fname );
714 return;
715 }
716 }
717 # Should the parser cache be used?
718 $pcache = $wgEnableParserCache &&
719 intval( $wgUser->getOption( 'stubthreshold' ) ) == 0 &&
720 $this->exists() &&
721 empty( $oldid );
722 wfDebug( 'Article::view using parser cache: ' . ($pcache ? 'yes' : 'no' ) . "\n" );
723
724 $outputDone = false;
725 if ( $pcache ) {
726 if ( $wgOut->tryParserCache( $this, $wgUser ) ) {
727 $outputDone = true;
728 }
729 }
730 if ( !$outputDone ) {
731 $text = $this->getContent( false ); # May change mTitle by following a redirect
732
733 # Another whitelist check in case oldid or redirects are altering the title
734 if ( !$this->mTitle->userCanRead() ) {
735 $wgOut->loginToUse();
736 $wgOut->output();
737 exit;
738 }
739
740 # We're looking at an old revision
741
742 if ( !empty( $oldid ) ) {
743 $this->setOldSubtitle( isset($this->mOldId) ? $this->mOldId : $oldid );
744 $wgOut->setRobotpolicy( 'noindex,follow' );
745 }
746 if ( '' != $this->mRedirectedFrom ) {
747 $sk = $wgUser->getSkin();
748 $redir = $sk->makeKnownLink( $this->mRedirectedFrom, '',
749 'redirect=no' );
750 $s = wfMsg( 'redirectedfrom', $redir );
751 $wgOut->setSubtitle( $s );
752
753 # Can't cache redirects
754 $pcache = false;
755 } elseif ( !empty( $rdfrom ) ) {
756 global $wgRedirectSources;
757 if( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
758 $sk = $wgUser->getSkin();
759 $redir = $sk->makeExternalLink( $rdfrom, $rdfrom );
760 $s = wfMsg( 'redirectedfrom', $redir );
761 $wgOut->setSubtitle( $s );
762 }
763 }
764
765 # wrap user css and user js in pre and don't parse
766 # XXX: use $this->mTitle->usCssJsSubpage() when php is fixed/ a workaround is found
767 if (
768 $this->mTitle->getNamespace() == NS_USER &&
769 preg_match('/\\/[\\w]+\\.(css|js)$/', $this->mTitle->getDBkey())
770 ) {
771 $wgOut->addWikiText( wfMsg('clearyourcache'));
772 $wgOut->addHTML( '<pre>'.htmlspecialchars($this->mContent)."\n</pre>" );
773 } else if ( $rt = Title::newFromRedirect( $text ) ) {
774 # Display redirect
775 $imageUrl = $wgStylePath.'/common/images/redirect.png';
776 $targetUrl = $rt->escapeLocalURL();
777 $titleText = htmlspecialchars( $rt->getPrefixedText() );
778 $link = $sk->makeLinkObj( $rt );
779
780 $wgOut->addHTML( '<img valign="center" src="'.$imageUrl.'" alt="#REDIRECT" />' .
781 '<span class="redirectText">'.$link.'</span>' );
782
783 $parseout = $wgParser->parse($text, $this->mTitle, ParserOptions::newFromUser($wgUser));
784 $catlinks = $parseout->getCategoryLinks();
785 $wgOut->addCategoryLinks($catlinks);
786 $skin = $wgUser->getSkin();
787 } else if ( $pcache ) {
788 # Display content and save to parser cache
789 $wgOut->addPrimaryWikiText( $text, $this );
790 } else {
791 # Display content, don't attempt to save to parser cache
792
793 # Don't show section-edit links on old revisions... this way lies madness.
794 if( !$this->isCurrent() ) {
795 $oldEditSectionSetting = $wgOut->mParserOptions->setEditSection( false );
796 }
797 $wgOut->addWikiText( $text );
798
799 if( !$this->isCurrent() ) {
800 $wgOut->mParserOptions->setEditSection( $oldEditSectionSetting );
801 }
802 }
803 }
804 /* title may have been set from the cache */
805 $t = $wgOut->getPageTitle();
806 if( empty( $t ) ) {
807 $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
808 }
809
810 # If we have been passed an &rcid= parameter, we want to give the user a
811 # chance to mark this new article as patrolled.
812 if ( $wgUseRCPatrol
813 && !is_null($rcid)
814 && $rcid != 0
815 && $wgUser->isLoggedIn()
816 && ( $wgUser->isAllowed('patrol') || !$wgOnlySysopsCanPatrol ) )
817 {
818 $wgOut->addHTML(
819 "<div class='patrollink'>" .
820 wfMsg ( 'markaspatrolledlink',
821 $sk->makeKnownLinkObj( $this->mTitle, wfMsg('markaspatrolledtext'), "action=markpatrolled&rcid=$rcid" )
822 ) .
823 '</div>'
824 );
825 }
826
827 # Put link titles into the link cache
828 $wgOut->transformBuffer();
829
830 # Add link titles as META keywords
831 $wgOut->addMetaTags() ;
832
833 $this->viewUpdates();
834 wfProfileOut( $fname );
835 }
836
837 /**
838 * Insert a new empty page record for this article.
839 * This *must* be followed up by creating a revision
840 * and running $this->updateToLatest( $rev_id );
841 * or else the record will be left in a funky state.
842 * Best if all done inside a transaction.
843 *
844 * @param Database $dbw
845 * @param string $restrictions
846 * @return int The newly created page_id key
847 * @access private
848 */
849 function insertOn( &$dbw, $restrictions = '' ) {
850 $fname = 'Article::insertOn';
851 wfProfileIn( $fname );
852
853 $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
854 $dbw->insert( 'page', array(
855 'page_id' => $page_id,
856 'page_namespace' => $this->mTitle->getNamespace(),
857 'page_title' => $this->mTitle->getDBkey(),
858 'page_counter' => 0,
859 'page_restrictions' => $restrictions,
860 'page_is_redirect' => 0, # Will set this shortly...
861 'page_is_new' => 1,
862 'page_random' => wfRandom(),
863 'page_touched' => $dbw->timestamp(),
864 'page_latest' => 0, # Fill this in shortly...
865 ), $fname );
866 $newid = $dbw->insertId();
867
868 $this->mTitle->resetArticleId( $newid );
869
870 wfProfileOut( $fname );
871 return $newid;
872 }
873
874 /**
875 * Update the page record to point to a newly saved revision.
876 *
877 * @param Database $dbw
878 * @param Revision $revision -- for ID number, and text used to set
879 length and redirect status fields
880 * @param int $lastRevision -- if given, will not overwrite the page field
881 * when different from the currently set value.
882 * Giving 0 indicates the new page flag should
883 * be set on.
884 * @return bool true on success, false on failure
885 * @access private
886 */
887 function updateRevisionOn( &$dbw, $revision, $lastRevision = null ) {
888 $fname = 'Article::updateToRevision';
889 wfProfileIn( $fname );
890
891 $conditions = array( 'page_id' => $this->getId() );
892 if( !is_null( $lastRevision ) ) {
893 # An extra check against threads stepping on each other
894 $conditions['page_latest'] = $lastRevision;
895 }
896 $text = $revision->getText();
897 $dbw->update( 'page',
898 array( /* SET */
899 'page_latest' => $revision->getId(),
900 'page_touched' => $dbw->timestamp(),
901 'page_is_new' => ($lastRevision === 0) ? 1 : 0,
902 'page_is_redirect' => Article::isRedirect( $text ),
903 'page_len' => strlen( $text ),
904 ),
905 $conditions,
906 $fname );
907
908 wfProfileOut( $fname );
909 return ( $dbw->affectedRows() != 0 );
910 }
911
912 /**
913 * If the given revision is newer than the currently set page_latest,
914 * update the page record. Otherwise, do nothing.
915 *
916 * @param Database $dbw
917 * @param Revision $revision
918 */
919 function updateIfNewerOn( &$dbw, $revision ) {
920 $fname = 'Article::updateIfNewerOn';
921 wfProfileIn( $fname );
922
923 $row = $dbw->selectRow(
924 array( 'revision', 'page' ),
925 array( 'rev_id', 'rev_timestamp' ),
926 array(
927 'page_id' => $this->getId(),
928 'page_latest=rev_id' ),
929 $fname );
930 if( $row ) {
931 if( $row->rev_timestamp >= $revision->getTimestamp() ) {
932 wfProfileOut( $fname );
933 return false;
934 }
935 $prev = $row->rev_id;
936 } else {
937 # No or missing previous revision; mark the page as new
938 $prev = 0;
939 }
940
941 $ret = $this->updateRevisionOn( $dbw, $revision, $prev );
942 wfProfileOut( $fname );
943 return $ret;
944 }
945
946 /**
947 * Theoretically we could defer these whole insert and update
948 * functions for after display, but that's taking a big leap
949 * of faith, and we want to be able to report database
950 * errors at some point.
951 * @private
952 */
953 function insertNewArticle( $text, $summary, $isminor, $watchthis, $suppressRC=false ) {
954 global $wgOut, $wgUser;
955 global $wgUseSquid, $wgDeferredUpdateList, $wgInternalServer;
956
957 $fname = 'Article::insertNewArticle';
958
959 $this->mGoodAdjustment = $this->isCountable( $text );
960 $this->mTotalAdjustment = 1;
961
962 $ns = $this->mTitle->getNamespace();
963 $ttl = $this->mTitle->getDBkey();
964 $text = $this->preSaveTransform( $text );
965 $isminor = ( $isminor && $wgUser->isLoggedIn() ) ? 1 : 0;
966 $now = wfTimestampNow();
967
968 $dbw =& wfGetDB( DB_MASTER );
969
970 # Add the page record; stake our claim on this title!
971 $newid = $this->insertOn( $dbw );
972
973 # Save the revision text...
974 $revision = new Revision( array(
975 'page' => $newid,
976 'comment' => $summary,
977 'minor_edit' => $isminor,
978 'text' => $text
979 ) );
980 $revisionId = $revision->insertOn( $dbw );
981
982 $this->mTitle->resetArticleID( $newid );
983
984 # Update the page record with revision data
985 $this->updateRevisionOn( $dbw, $revision, 0 );
986
987 Article::onArticleCreate( $this->mTitle );
988 if(!$suppressRC) {
989 RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, 'default',
990 '', strlen( $text ), $revisionId );
991 }
992
993 if ($watchthis) {
994 if(!$this->mTitle->userIsWatching()) $this->watch();
995 } else {
996 if ( $this->mTitle->userIsWatching() ) {
997 $this->unwatch();
998 }
999 }
1000
1001 # The talk page isn't in the regular link tables, so we need to update manually:
1002 $talkns = $ns ^ 1; # talk -> normal; normal -> talk
1003 $dbw->update( 'page',
1004 array( 'page_touched' => $dbw->timestamp($now) ),
1005 array( 'page_namespace' => $talkns,
1006 'page_title' => $ttl ),
1007 $fname );
1008
1009 # standard deferred updates
1010 $this->editUpdates( $text, $summary, $isminor, $now );
1011
1012 $oldid = 0; # new article
1013 $this->showArticle( $text, wfMsg( 'newarticle' ), false, $isminor, $now, $summary, $oldid );
1014 }
1015
1016 function getTextOfLastEditWithSectionReplacedOrAdded($section, $text, $summary = '', $edittime = NULL) {
1017 $fname = 'Article::getTextOfLastEditWithSectionReplacedOrAdded';
1018 if ($section != '') {
1019 if( is_null( $edittime ) ) {
1020 $rev = Revision::newFromTitle( $this->mTitle );
1021 } else {
1022 $dbw =& wfGetDB( DB_MASTER );
1023 $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
1024 }
1025 $oldtext = $rev->getText();
1026
1027 if($section=='new') {
1028 if($summary) $subject="== {$summary} ==\n\n";
1029 $text=$oldtext."\n\n".$subject.$text;
1030 } else {
1031
1032 # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML
1033 # comments to be stripped as well)
1034 $striparray=array();
1035 $parser=new Parser();
1036 $parser->mOutputType=OT_WIKI;
1037 $oldtext=$parser->strip($oldtext, $striparray, true);
1038
1039 # now that we can be sure that no pseudo-sections are in the source,
1040 # split it up
1041 # Unfortunately we can't simply do a preg_replace because that might
1042 # replace the wrong section, so we have to use the section counter instead
1043 $secs=preg_split('/(^=+.+?=+|^<h[1-6].*?' . '>.*?<\/h[1-6].*?' . '>)(?!\S)/mi',
1044 $oldtext,-1,PREG_SPLIT_DELIM_CAPTURE);
1045 $secs[$section*2]=$text."\n\n"; // replace with edited
1046
1047 # section 0 is top (intro) section
1048 if($section!=0) {
1049
1050 # headline of old section - we need to go through this section
1051 # to determine if there are any subsections that now need to
1052 # be erased, as the mother section has been replaced with
1053 # the text of all subsections.
1054 $headline=$secs[$section*2-1];
1055 preg_match( '/^(=+).+?=+|^<h([1-6]).*?' . '>.*?<\/h[1-6].*?' . '>(?!\S)/mi',$headline,$matches);
1056 $hlevel=$matches[1];
1057
1058 # determine headline level for wikimarkup headings
1059 if(strpos($hlevel,'=')!==false) {
1060 $hlevel=strlen($hlevel);
1061 }
1062
1063 $secs[$section*2-1]=''; // erase old headline
1064 $count=$section+1;
1065 $break=false;
1066 while(!empty($secs[$count*2-1]) && !$break) {
1067
1068 $subheadline=$secs[$count*2-1];
1069 preg_match(
1070 '/^(=+).+?=+|^<h([1-6]).*?' . '>.*?<\/h[1-6].*?' . '>(?!\S)/mi',$subheadline,$matches);
1071 $subhlevel=$matches[1];
1072 if(strpos($subhlevel,'=')!==false) {
1073 $subhlevel=strlen($subhlevel);
1074 }
1075 if($subhlevel > $hlevel) {
1076 // erase old subsections
1077 $secs[$count*2-1]='';
1078 $secs[$count*2]='';
1079 }
1080 if($subhlevel <= $hlevel) {
1081 $break=true;
1082 }
1083 $count++;
1084
1085 }
1086
1087 }
1088 $text=join('',$secs);
1089 # reinsert the stuff that we stripped out earlier
1090 $text=$parser->unstrip($text,$striparray);
1091 $text=$parser->unstripNoWiki($text,$striparray);
1092 }
1093
1094 }
1095 return $text;
1096 }
1097
1098 /**
1099 * Change an existing article. Puts the previous version back into the old table, updates RC
1100 * and all necessary caches, mostly via the deferred update array.
1101 *
1102 * It is possible to call this function from a command-line script, but note that you should
1103 * first set $wgUser, and clean up $wgDeferredUpdates after each edit.
1104 */
1105 function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = '' ) {
1106 global $wgOut, $wgUser;
1107 global $wgDBtransactions, $wgMwRedir;
1108 global $wgUseSquid, $wgInternalServer, $wgPostCommitUpdateList;
1109
1110 $fname = 'Article::updateArticle';
1111 $good = true;
1112
1113 $isminor = ( $minor && $wgUser->isLoggedIn() );
1114 if ( $this->isRedirect( $text ) ) {
1115 # Remove all content but redirect
1116 # This could be done by reconstructing the redirect from a title given by
1117 # Title::newFromRedirect(), but then we wouldn't know which synonym the user
1118 # wants to see
1119 if ( preg_match( "/^((" . $wgMwRedir->getBaseRegex() . ')[^\\n]+)/i', $text, $m ) ) {
1120 $redir = 1;
1121 $text = $m[1] . "\n";
1122 }
1123 }
1124 else { $redir = 0; }
1125
1126 $text = $this->preSaveTransform( $text );
1127 $dbw =& wfGetDB( DB_MASTER );
1128 $now = wfTimestampNow();
1129
1130 # Update article, but only if changed.
1131
1132 # It's important that we either rollback or complete, otherwise an attacker could
1133 # overwrite cur entries by sending precisely timed user aborts. Random bored users
1134 # could conceivably have the same effect, especially if cur is locked for long periods.
1135 if( !$wgDBtransactions ) {
1136 $userAbort = ignore_user_abort( true );
1137 }
1138
1139 $oldtext = $this->getContent( true );
1140 $oldsize = strlen( $oldtext );
1141 $newsize = strlen( $text );
1142 $lastRevision = 0;
1143
1144 if ( 0 != strcmp( $text, $oldtext ) ) {
1145 $this->mGoodAdjustment = $this->isCountable( $text )
1146 - $this->isCountable( $oldtext );
1147 $this->mTotalAdjustment = 0;
1148 $now = wfTimestampNow();
1149
1150 $lastRevision = $dbw->selectField(
1151 'page', 'page_latest', array( 'page_id' => $this->getId() ) );
1152
1153 $revision = new Revision( array(
1154 'page' => $this->getId(),
1155 'comment' => $summary,
1156 'minor_edit' => $isminor,
1157 'text' => $text
1158 ) );
1159 $revisionId = $revision->insertOn( $dbw );
1160
1161 # Update page
1162 $ok = $this->updateRevisionOn( $dbw, $revision, $lastRevision );
1163
1164 if( !$ok ) {
1165 /* Belated edit conflict! Run away!! */
1166 $good = false;
1167 } else {
1168 # Update recentchanges and purge cache and whatnot
1169 $bot = (int)($wgUser->isBot() || $forceBot);
1170 RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $wgUser, $summary,
1171 $lastRevision, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
1172 $revisionId );
1173 Article::onArticleEdit( $this->mTitle );
1174 }
1175 }
1176
1177 if( !$wgDBtransactions ) {
1178 ignore_user_abort( $userAbort );
1179 }
1180
1181 if ( $good ) {
1182 if ($watchthis) {
1183 if (!$this->mTitle->userIsWatching()) $this->watch();
1184 } else {
1185 if ( $this->mTitle->userIsWatching() ) {
1186 $this->unwatch();
1187 }
1188 }
1189 # standard deferred updates
1190 $this->editUpdates( $text, $summary, $minor, $now );
1191
1192
1193 $urls = array();
1194 # Template namespace
1195 # Purge all articles linking here
1196 if ( $this->mTitle->getNamespace() == NS_TEMPLATE) {
1197 $titles = $this->mTitle->getLinksTo();
1198 Title::touchArray( $titles );
1199 if ( $wgUseSquid ) {
1200 foreach ( $titles as $title ) {
1201 $urls[] = $title->getInternalURL();
1202 }
1203 }
1204 }
1205
1206 # Squid updates
1207 if ( $wgUseSquid ) {
1208 $urls = array_merge( $urls, $this->mTitle->getSquidURLs() );
1209 $u = new SquidUpdate( $urls );
1210 array_push( $wgPostCommitUpdateList, $u );
1211 }
1212
1213 $this->showArticle( $text, wfMsg( 'updated' ), $sectionanchor, $isminor, $now, $summary, $lastRevision );
1214 }
1215 return $good;
1216 }
1217
1218 /**
1219 * After we've either updated or inserted the article, update
1220 * the link tables and redirect to the new page.
1221 */
1222 function showArticle( $text, $subtitle , $sectionanchor = '', $me2, $now, $summary, $oldid ) {
1223 global $wgUseDumbLinkUpdate, $wgAntiLockFlags, $wgOut, $wgUser, $wgLinkCache, $wgEnotif;
1224 global $wgUseEnotif;
1225
1226 $wgLinkCache = new LinkCache();
1227
1228 if ( !$wgUseDumbLinkUpdate ) {
1229 # Preload links to reduce lock time
1230 if ( $wgAntiLockFlags & ALF_PRELOAD_LINKS ) {
1231 $wgLinkCache->preFill( $this->mTitle );
1232 $wgLinkCache->clear();
1233 }
1234 }
1235
1236 # Parse the text and replace links with placeholders
1237 $wgOut = new OutputPage();
1238
1239 # Pass the current title along in case we're creating a wiki page
1240 # which is different than the currently displayed one (e.g. image
1241 # pages created on file uploads); otherwise, link updates will
1242 # go wrong.
1243 $wgOut->addWikiTextWithTitle( $text, $this->mTitle );
1244
1245 if ( !$wgUseDumbLinkUpdate ) {
1246 # Move the current links back to the second register
1247 $wgLinkCache->swapRegisters();
1248
1249 # Get old version of link table to allow incremental link updates
1250 # Lock this data now since it is needed for an update
1251 $wgLinkCache->forUpdate( true );
1252 $wgLinkCache->preFill( $this->mTitle );
1253
1254 # Swap this old version back into its rightful place
1255 $wgLinkCache->swapRegisters();
1256 }
1257
1258 if( $this->isRedirect( $text ) )
1259 $r = 'redirect=no';
1260 else
1261 $r = '';
1262 $wgOut->redirect( $this->mTitle->getFullURL( $r ).$sectionanchor );
1263
1264 if ( $wgUseEnotif ) {
1265 # this would be better as an extension hook
1266 include_once( "UserMailer.php" );
1267 $wgEnotif = new EmailNotification ();
1268 $wgEnotif->notifyOnPageChange( $this->mTitle, $now, $summary, $me2, $oldid );
1269 }
1270 }
1271
1272 /**
1273 * Mark this particular edit as patrolled
1274 */
1275 function markpatrolled() {
1276 global $wgOut, $wgRequest, $wgOnlySysopsCanPatrol, $wgUseRCPatrol, $wgUser;
1277 $wgOut->setRobotpolicy( 'noindex,follow' );
1278
1279 if ( !$wgUseRCPatrol )
1280 {
1281 $wgOut->errorpage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
1282 return;
1283 }
1284 if ( $wgUser->isAnon() )
1285 {
1286 $wgOut->loginToUse();
1287 return;
1288 }
1289 if ( $wgOnlySysopsCanPatrol && !$wgUser->isAllowed('patrol') )
1290 {
1291 $wgOut->sysopRequired();
1292 return;
1293 }
1294 $rcid = $wgRequest->getVal( 'rcid' );
1295 if ( !is_null ( $rcid ) )
1296 {
1297 RecentChange::markPatrolled( $rcid );
1298 $wgOut->setPagetitle( wfMsg( 'markedaspatrolled' ) );
1299 $wgOut->addWikiText( wfMsg( 'markedaspatrolledtext' ) );
1300
1301 $rcTitle = Title::makeTitle( NS_SPECIAL, 'Recentchanges' );
1302 $wgOut->returnToMain( false, $rcTitle->getPrefixedText() );
1303 }
1304 else
1305 {
1306 $wgOut->errorpage( 'markedaspatrollederror', 'markedaspatrollederrortext' );
1307 }
1308 }
1309
1310 /**
1311 * Validate function
1312 */
1313 function validate() {
1314 global $wgOut, $wgUser, $wgRequest, $wgUseValidation;
1315
1316 if ( !$wgUseValidation ) # Are we using article validation at all?
1317 {
1318 $wgOut->errorpage( "nosuchspecialpage", "nospecialpagetext" );
1319 return ;
1320 }
1321
1322 $wgOut->setRobotpolicy( 'noindex,follow' );
1323 $revision = $wgRequest->getVal( 'revision' );
1324
1325 include_once ( "SpecialValidate.php" ) ; # The "Validation" class
1326
1327 $v = new Validation ;
1328 if ( $wgRequest->getVal ( "mode" , "" ) == "list" )
1329 $t = $v->showList ( $this ) ;
1330 else if ( $wgRequest->getVal ( "mode" , "" ) == "details" )
1331 $t = $v->showDetails ( $this , $wgRequest->getVal( 'revision' ) ) ;
1332 else
1333 $t = $v->validatePageForm ( $this , $revision ) ;
1334
1335 $wgOut->addHTML ( $t ) ;
1336 }
1337
1338 /**
1339 * Add this page to $wgUser's watchlist
1340 */
1341
1342 function watch() {
1343
1344 global $wgUser, $wgOut;
1345
1346 if ( $wgUser->isAnon() ) {
1347 $wgOut->errorpage( 'watchnologin', 'watchnologintext' );
1348 return;
1349 }
1350 if ( wfReadOnly() ) {
1351 $wgOut->readOnlyPage();
1352 return;
1353 }
1354
1355 if (wfRunHooks('WatchArticle', array(&$wgUser, &$this))) {
1356
1357 $wgUser->addWatch( $this->mTitle );
1358 $wgUser->saveSettings();
1359
1360 wfRunHooks('WatchArticleComplete', array(&$wgUser, &$this));
1361
1362 $wgOut->setPagetitle( wfMsg( 'addedwatch' ) );
1363 $wgOut->setRobotpolicy( 'noindex,follow' );
1364
1365 $link = $this->mTitle->getPrefixedText();
1366 $text = wfMsg( 'addedwatchtext', $link );
1367 $wgOut->addWikiText( $text );
1368 }
1369
1370 $wgOut->returnToMain( true, $this->mTitle->getPrefixedText() );
1371 }
1372
1373 /**
1374 * Stop watching a page
1375 */
1376
1377 function unwatch() {
1378
1379 global $wgUser, $wgOut;
1380
1381 if ( $wgUser->isAnon() ) {
1382 $wgOut->errorpage( 'watchnologin', 'watchnologintext' );
1383 return;
1384 }
1385 if ( wfReadOnly() ) {
1386 $wgOut->readOnlyPage();
1387 return;
1388 }
1389
1390 if (wfRunHooks('UnwatchArticle', array(&$wgUser, &$this))) {
1391
1392 $wgUser->removeWatch( $this->mTitle );
1393 $wgUser->saveSettings();
1394
1395 wfRunHooks('UnwatchArticleComplete', array(&$wgUser, &$this));
1396
1397 $wgOut->setPagetitle( wfMsg( 'removedwatch' ) );
1398 $wgOut->setRobotpolicy( 'noindex,follow' );
1399
1400 $link = $this->mTitle->getPrefixedText();
1401 $text = wfMsg( 'removedwatchtext', $link );
1402 $wgOut->addWikiText( $text );
1403 }
1404
1405 $wgOut->returnToMain( true, $this->mTitle->getPrefixedText() );
1406 }
1407
1408 /**
1409 * protect a page
1410 */
1411 function protect( $limit = 'sysop' ) {
1412 global $wgUser, $wgOut, $wgRequest;
1413
1414 if ( ! $wgUser->isAllowed('protect') ) {
1415 $wgOut->sysopRequired();
1416 return;
1417 }
1418 if ( wfReadOnly() ) {
1419 $wgOut->readOnlyPage();
1420 return;
1421 }
1422 $id = $this->mTitle->getArticleID();
1423 if ( 0 == $id ) {
1424 $wgOut->fatalError( wfMsg( 'badarticleerror' ) );
1425 return;
1426 }
1427
1428 $confirm = $wgRequest->wasPosted() &&
1429 $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
1430 $moveonly = $wgRequest->getBool( 'wpMoveOnly' );
1431 $reason = $wgRequest->getText( 'wpReasonProtect' );
1432
1433 if ( $confirm ) {
1434 $dbw =& wfGetDB( DB_MASTER );
1435 $dbw->update( 'page',
1436 array( /* SET */
1437 'page_touched' => $dbw->timestamp(),
1438 'page_restrictions' => (string)$limit
1439 ), array( /* WHERE */
1440 'page_id' => $id
1441 ), 'Article::protect'
1442 );
1443
1444 $restrictions = "move=" . $limit;
1445 if( !$moveonly ) {
1446 $restrictions .= ":edit=" . $limit;
1447 }
1448 if (wfRunHooks('ArticleProtect', array(&$this, &$wgUser, $limit == 'sysop', $reason, $moveonly))) {
1449
1450 $dbw =& wfGetDB( DB_MASTER );
1451 $dbw->update( 'page',
1452 array( /* SET */
1453 'page_touched' => $dbw->timestamp(),
1454 'page_restrictions' => $restrictions
1455 ), array( /* WHERE */
1456 'page_id' => $id
1457 ), 'Article::protect'
1458 );
1459
1460 wfRunHooks('ArticleProtectComplete', array(&$this, &$wgUser, $limit == 'sysop', $reason, $moveonly));
1461
1462 $log = new LogPage( 'protect' );
1463 if ( $limit === '' ) {
1464 $log->addEntry( 'unprotect', $this->mTitle, $reason );
1465 } else {
1466 $log->addEntry( 'protect', $this->mTitle, $reason );
1467 }
1468 $wgOut->redirect( $this->mTitle->getFullURL() );
1469 }
1470 return;
1471 } else {
1472 $reason = htmlspecialchars( wfMsg( 'protectreason' ) );
1473 return $this->confirmProtect( '', $reason, $limit );
1474 }
1475 }
1476
1477 /**
1478 * Output protection confirmation dialog
1479 */
1480 function confirmProtect( $par, $reason, $limit = 'sysop' ) {
1481 global $wgOut, $wgUser;
1482
1483 wfDebug( "Article::confirmProtect\n" );
1484
1485 $sub = $this->mTitle->getPrefixedText();
1486 $wgOut->setRobotpolicy( 'noindex,nofollow' );
1487
1488 $check = '';
1489 $protcom = '';
1490 $moveonly = '';
1491
1492 if ( $limit === '' ) {
1493 $wgOut->setPageTitle( wfMsg( 'confirmunprotect' ) );
1494 $wgOut->setSubtitle( wfMsg( 'unprotectsub', $sub ) );
1495 $wgOut->addWikiText( wfMsg( 'confirmunprotecttext' ) );
1496 $protcom = htmlspecialchars( wfMsg( 'unprotectcomment' ) );
1497 $formaction = $this->mTitle->escapeLocalURL( 'action=unprotect' . $par );
1498 } else {
1499 $wgOut->setPageTitle( wfMsg( 'confirmprotect' ) );
1500 $wgOut->setSubtitle( wfMsg( 'protectsub', $sub ) );
1501 $wgOut->addWikiText( wfMsg( 'confirmprotecttext' ) );
1502 $moveonly = htmlspecialchars( wfMsg( 'protectmoveonly' ) );
1503 $protcom = htmlspecialchars( wfMsg( 'protectcomment' ) );
1504 $formaction = $this->mTitle->escapeLocalURL( 'action=protect' . $par );
1505 }
1506
1507 $confirm = htmlspecialchars( wfMsg( 'confirm' ) );
1508 $token = htmlspecialchars( $wgUser->editToken() );
1509
1510 $wgOut->addHTML( "
1511 <form id='protectconfirm' method='post' action=\"{$formaction}\">
1512 <table border='0'>
1513 <tr>
1514 <td align='right'>
1515 <label for='wpReasonProtect'>{$protcom}:</label>
1516 </td>
1517 <td align='left'>
1518 <input type='text' size='60' name='wpReasonProtect' id='wpReasonProtect' value=\"" . htmlspecialchars( $reason ) . "\" />
1519 </td>
1520 </tr>" );
1521 if($moveonly != '') {
1522 $wgOut->AddHTML( "
1523 <tr>
1524 <td align='right'>
1525 <input type='checkbox' name='wpMoveOnly' value='1' id='wpMoveOnly' />
1526 </td>
1527 <td align='left'>
1528 <label for='wpMoveOnly'>{$moveonly}</label>
1529 </td>
1530 </tr> " );
1531 }
1532 $wgOut->addHTML( "
1533 <tr>
1534 <td>&nbsp;</td>
1535 <td>
1536 <input type='submit' name='wpConfirmProtectB' value=\"{$confirm}\" />
1537 </td>
1538 </tr>
1539 </table>
1540 <input type='hidden' name='wpEditToken' value=\"{$token}\" />
1541 </form>" );
1542
1543 $wgOut->returnToMain( false );
1544 }
1545
1546 /**
1547 * Unprotect the pages
1548 */
1549 function unprotect() {
1550 return $this->protect( '' );
1551 }
1552
1553 /*
1554 * UI entry point for page deletion
1555 */
1556 function delete() {
1557 global $wgUser, $wgOut, $wgMessageCache, $wgRequest;
1558 $fname = 'Article::delete';
1559 $confirm = $wgRequest->wasPosted() &&
1560 $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
1561 $reason = $wgRequest->getText( 'wpReason' );
1562
1563 # This code desperately needs to be totally rewritten
1564
1565 # Check permissions
1566 if( ( !$wgUser->isAllowed( 'delete' ) ) ) {
1567 $wgOut->sysopRequired();
1568 return;
1569 }
1570 if( wfReadOnly() ) {
1571 $wgOut->readOnlyPage();
1572 return;
1573 }
1574
1575 # Better double-check that it hasn't been deleted yet!
1576 $wgOut->setPagetitle( wfMsg( 'confirmdelete' ) );
1577 if( !$this->mTitle->exists() ) {
1578 $wgOut->fatalError( wfMsg( 'cannotdelete' ) );
1579 return;
1580 }
1581
1582 if( $confirm ) {
1583 $this->doDelete( $reason );
1584 return;
1585 }
1586
1587 # determine whether this page has earlier revisions
1588 # and insert a warning if it does
1589 # we select the text because it might be useful below
1590 $dbr =& $this->getDB();
1591 $ns = $this->mTitle->getNamespace();
1592 $title = $this->mTitle->getDBkey();
1593 $revisions = $dbr->select( array( 'page', 'revision' ),
1594 array( 'rev_id', 'rev_user_text' ),
1595 array(
1596 'page_namespace' => $ns,
1597 'page_title' => $title,
1598 'rev_page = page_id'
1599 ), $fname, $this->getSelectOptions( array( 'ORDER BY' => 'rev_timestamp DESC' ) )
1600 );
1601
1602 if( $dbr->numRows( $revisions ) > 1 && !$confirm ) {
1603 $skin=$wgUser->getSkin();
1604 $wgOut->addHTML('<b>'.wfMsg('historywarning'));
1605 $wgOut->addHTML( $skin->historyLink() .'</b>');
1606 }
1607
1608 # Fetch cur_text
1609 $rev =& Revision::newFromTitle( $this->mTitle );
1610
1611 # Fetch name(s) of contributors
1612 $rev_name = '';
1613 $all_same_user = true;
1614 while( $row = $dbr->fetchObject( $revisions ) ) {
1615 if( $rev_name != '' && $rev_name != $row->rev_user_text ) {
1616 $all_same_user = false;
1617 } else {
1618 $rev_name = $row->rev_user_text;
1619 }
1620 }
1621
1622 if( !is_null( $rev ) ) {
1623 # if this is a mini-text, we can paste part of it into the deletion reason
1624 $text = $rev->getText();
1625
1626 #if this is empty, an earlier revision may contain "useful" text
1627 $blanked = false;
1628 if( $text == '' ) {
1629 $prev = $rev->getPrevious();
1630 if( $prev ) {
1631 $text = $prev->getText();
1632 $blanked = true;
1633 }
1634 }
1635
1636 $length = strlen( $text );
1637
1638 # this should not happen, since it is not possible to store an empty, new
1639 # page. Let's insert a standard text in case it does, though
1640 if( $length == 0 && $reason === '' ) {
1641 $reason = wfMsg( 'exblank' );
1642 }
1643
1644 if( $length < 500 && $reason === '' ) {
1645 # comment field=255, let's grep the first 150 to have some user
1646 # space left
1647 global $wgContLang;
1648 $text = $wgContLang->truncate( $text, 150, '...' );
1649
1650 # let's strip out newlines
1651 $text = preg_replace( "/[\n\r]/", '', $text );
1652
1653 if( !$blanked ) {
1654 if( !$all_same_user ) {
1655 $reason = wfMsg( 'excontent', $text );
1656 } else {
1657 $reason = wfMsg( 'excontentauthor', $text, $rev_name );
1658 }
1659 } else {
1660 $reason = wfMsg( 'exbeforeblank', $text );
1661 }
1662 }
1663 }
1664
1665 return $this->confirmDelete( '', $reason );
1666 }
1667
1668 /**
1669 * Output deletion confirmation dialog
1670 */
1671 function confirmDelete( $par, $reason ) {
1672 global $wgOut, $wgUser;
1673
1674 wfDebug( "Article::confirmDelete\n" );
1675
1676 $sub = htmlspecialchars( $this->mTitle->getPrefixedText() );
1677 $wgOut->setSubtitle( wfMsg( 'deletesub', $sub ) );
1678 $wgOut->setRobotpolicy( 'noindex,nofollow' );
1679 $wgOut->addWikiText( wfMsg( 'confirmdeletetext' ) );
1680
1681 $formaction = $this->mTitle->escapeLocalURL( 'action=delete' . $par );
1682
1683 $confirm = htmlspecialchars( wfMsg( 'confirm' ) );
1684 $delcom = htmlspecialchars( wfMsg( 'deletecomment' ) );
1685 $token = htmlspecialchars( $wgUser->editToken() );
1686
1687 $wgOut->addHTML( "
1688 <form id='deleteconfirm' method='post' action=\"{$formaction}\">
1689 <table border='0'>
1690 <tr>
1691 <td align='right'>
1692 <label for='wpReason'>{$delcom}:</label>
1693 </td>
1694 <td align='left'>
1695 <input type='text' size='60' name='wpReason' id='wpReason' value=\"" . htmlspecialchars( $reason ) . "\" />
1696 </td>
1697 </tr>
1698 <tr>
1699 <td>&nbsp;</td>
1700 <td>
1701 <input type='submit' name='wpConfirmB' value=\"{$confirm}\" />
1702 </td>
1703 </tr>
1704 </table>
1705 <input type='hidden' name='wpEditToken' value=\"{$token}\" />
1706 </form>\n" );
1707
1708 $wgOut->returnToMain( false );
1709 }
1710
1711
1712 /**
1713 * Perform a deletion and output success or failure messages
1714 */
1715 function doDelete( $reason ) {
1716 global $wgOut, $wgUser, $wgContLang;
1717 $fname = 'Article::doDelete';
1718 wfDebug( $fname."\n" );
1719
1720 if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason))) {
1721 if ( $this->doDeleteArticle( $reason ) ) {
1722 $deleted = $this->mTitle->getPrefixedText();
1723
1724 $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
1725 $wgOut->setRobotpolicy( 'noindex,nofollow' );
1726
1727 $loglink = '[[Special:Log/delete|' . wfMsg( 'deletionlog' ) . ']]';
1728 $text = wfMsg( 'deletedtext', $deleted, $loglink );
1729
1730 $wgOut->addWikiText( $text );
1731 $wgOut->returnToMain( false );
1732 wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason));
1733 } else {
1734 $wgOut->fatalError( wfMsg( 'cannotdelete' ) );
1735 }
1736 }
1737 }
1738
1739 /**
1740 * Back-end article deletion
1741 * Deletes the article with database consistency, writes logs, purges caches
1742 * Returns success
1743 */
1744 function doDeleteArticle( $reason ) {
1745 global $wgUser;
1746 global $wgUseSquid, $wgDeferredUpdateList, $wgInternalServer, $wgPostCommitUpdateList;
1747
1748 $fname = 'Article::doDeleteArticle';
1749 wfDebug( $fname."\n" );
1750
1751 $dbw =& wfGetDB( DB_MASTER );
1752 $ns = $this->mTitle->getNamespace();
1753 $t = $this->mTitle->getDBkey();
1754 $id = $this->mTitle->getArticleID();
1755
1756 if ( $t == '' || $id == 0 ) {
1757 return false;
1758 }
1759
1760 $u = new SiteStatsUpdate( 0, 1, -$this->isCountable( $this->getContent( true ) ), -1 );
1761 array_push( $wgDeferredUpdateList, $u );
1762
1763 $linksTo = $this->mTitle->getLinksTo();
1764
1765 # Squid purging
1766 if ( $wgUseSquid ) {
1767 $urls = array(
1768 $this->mTitle->getInternalURL(),
1769 $this->mTitle->getInternalURL( 'history' )
1770 );
1771
1772 $u = SquidUpdate::newFromTitles( $linksTo, $urls );
1773 array_push( $wgPostCommitUpdateList, $u );
1774
1775 }
1776
1777 # Client and file cache invalidation
1778 Title::touchArray( $linksTo );
1779
1780
1781 // For now, shunt the revision data into the archive table.
1782 // Text is *not* removed from the text table; bulk storage
1783 // is left intact to avoid breaking block-compression or
1784 // immutable storage schemes.
1785 //
1786 // For backwards compatibility, note that some older archive
1787 // table entries will have ar_text and ar_flags fields still.
1788 //
1789 // In the future, we may keep revisions and mark them with
1790 // the rev_deleted field, which is reserved for this purpose.
1791 $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
1792 array(
1793 'ar_namespace' => 'page_namespace',
1794 'ar_title' => 'page_title',
1795 'ar_comment' => 'rev_comment',
1796 'ar_user' => 'rev_user',
1797 'ar_user_text' => 'rev_user_text',
1798 'ar_timestamp' => 'rev_timestamp',
1799 'ar_minor_edit' => 'rev_minor_edit',
1800 'ar_rev_id' => 'rev_id',
1801 'ar_text_id' => 'rev_text_id',
1802 ), array(
1803 'page_id' => $id,
1804 'page_id = rev_page'
1805 ), $fname
1806 );
1807
1808 # Now that it's safely backed up, delete it
1809 $dbw->delete( 'revision', array( 'rev_page' => $id ), $fname );
1810 $dbw->delete( 'page', array( 'page_id' => $id ), $fname);
1811
1812 # Clean up recentchanges entries...
1813 $dbw->delete( 'recentchanges', array( 'rc_namespace' => $ns, 'rc_title' => $t ), $fname );
1814
1815 # Finally, clean up the link tables
1816 $t = $this->mTitle->getPrefixedDBkey();
1817
1818 Article::onArticleDelete( $this->mTitle );
1819
1820 # Delete outgoing links
1821 $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
1822 $dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
1823 $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
1824
1825 # Log the deletion
1826 $log = new LogPage( 'delete' );
1827 $log->addEntry( 'delete', $this->mTitle, $reason );
1828
1829 # Clear the cached article id so the interface doesn't act like we exist
1830 $this->mTitle->resetArticleID( 0 );
1831 $this->mTitle->mArticleID = 0;
1832 return true;
1833 }
1834
1835 /**
1836 * Revert a modification
1837 */
1838 function rollback() {
1839 global $wgUser, $wgOut, $wgRequest;
1840 $fname = 'Article::rollback';
1841
1842 if ( ! $wgUser->isAllowed('rollback') ) {
1843 $wgOut->sysopRequired();
1844 return;
1845 }
1846 if ( wfReadOnly() ) {
1847 $wgOut->readOnlyPage( $this->getContent( true ) );
1848 return;
1849 }
1850 if( !$wgUser->matchEditToken( $wgRequest->getVal( 'token' ),
1851 array( $this->mTitle->getPrefixedText(),
1852 $wgRequest->getVal( 'from' ) ) ) ) {
1853 $wgOut->setPageTitle( wfMsg( 'rollbackfailed' ) );
1854 $wgOut->addWikiText( wfMsg( 'sessionfailure' ) );
1855 return;
1856 }
1857 $dbw =& wfGetDB( DB_MASTER );
1858
1859 # Enhanced rollback, marks edits rc_bot=1
1860 $bot = $wgRequest->getBool( 'bot' );
1861
1862 # Replace all this user's current edits with the next one down
1863 $tt = $this->mTitle->getDBKey();
1864 $n = $this->mTitle->getNamespace();
1865
1866 # Get the last editor, lock table exclusively
1867 $dbw->begin();
1868 $current = Revision::newFromTitle( $this->mTitle );
1869 if( is_null( $current ) ) {
1870 # Something wrong... no page?
1871 $dbw->rollback();
1872 $wgOut->addHTML( wfMsg( 'notanarticle' ) );
1873 return;
1874 }
1875
1876 $from = str_replace( '_', ' ', $wgRequest->getVal( 'from' ) );
1877 if( $from != $current->getUserText() ) {
1878 $wgOut->setPageTitle(wfmsg('rollbackfailed'));
1879 $wgOut->addWikiText( wfMsg( 'alreadyrolled',
1880 htmlspecialchars( $this->mTitle->getPrefixedText()),
1881 htmlspecialchars( $from ),
1882 htmlspecialchars( $current->getUserText() ) ) );
1883 if( $current->getComment() != '') {
1884 $wgOut->addHTML(
1885 wfMsg( 'editcomment',
1886 htmlspecialchars( $current->getComment() ) ) );
1887 }
1888 return;
1889 }
1890
1891 # Get the last edit not by this guy
1892 $user = IntVal( $current->getUser() );
1893 $user_text = $dbw->addQuotes( $current->getUserText() );
1894 $s = $dbw->selectRow( 'revision',
1895 array( 'rev_id', 'rev_timestamp' ),
1896 array(
1897 'rev_page' => $current->getPage(),
1898 "rev_user <> {$user} OR rev_user_text <> {$user_text}"
1899 ), $fname,
1900 array(
1901 'USE INDEX' => 'page_timestamp',
1902 'ORDER BY' => 'rev_timestamp DESC' )
1903 );
1904 if( $s === false ) {
1905 # Something wrong
1906 $dbw->rollback();
1907 $wgOut->setPageTitle(wfMsg('rollbackfailed'));
1908 $wgOut->addHTML( wfMsg( 'cantrollback' ) );
1909 return;
1910 }
1911
1912 if ( $bot ) {
1913 # Mark all reverted edits as bot
1914 $dbw->update( 'recentchanges',
1915 array( /* SET */
1916 'rc_bot' => 1
1917 ), array( /* WHERE */
1918 'rc_cur_id' => $current->getPage(),
1919 'rc_user_text' => $current->getUserText(),
1920 "rc_timestamp > '{$s->rev_timestamp}'",
1921 ), $fname
1922 );
1923 }
1924
1925 # Save it!
1926 $target = Revision::newFromId( $s->rev_id );
1927 $newcomment = wfMsg( 'revertpage', $target->getUserText(), $from );
1928
1929 $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
1930 $wgOut->setRobotpolicy( 'noindex,nofollow' );
1931 $wgOut->addHTML( '<h2>' . htmlspecialchars( $newcomment ) . "</h2>\n<hr />\n" );
1932
1933 $this->updateArticle( $target->getText(), $newcomment, 1, $this->mTitle->userIsWatching(), $bot );
1934 Article::onArticleEdit( $this->mTitle );
1935
1936 $dbw->commit();
1937 $wgOut->returnToMain( false );
1938 }
1939
1940
1941 /**
1942 * Do standard deferred updates after page view
1943 * @private
1944 */
1945 function viewUpdates() {
1946 global $wgDeferredUpdateList, $wgUseEnotif;
1947
1948 if ( 0 != $this->getID() ) {
1949 global $wgDisableCounters;
1950 if( !$wgDisableCounters ) {
1951 Article::incViewCount( $this->getID() );
1952 $u = new SiteStatsUpdate( 1, 0, 0 );
1953 array_push( $wgDeferredUpdateList, $u );
1954 }
1955 }
1956
1957 # Update newtalk status if user is reading their own
1958 # talk page
1959
1960 global $wgUser;
1961 if ($this->mTitle->getNamespace() == NS_USER_TALK &&
1962 $this->mTitle->getText() == $wgUser->getName())
1963 {
1964 if ( $wgUseEnotif ) {
1965 require_once( 'UserTalkUpdate.php' );
1966 $u = new UserTalkUpdate( 0, $this->mTitle->getNamespace(), $this->mTitle->getDBkey(), false, false, false );
1967 } else {
1968 $wgUser->setNewtalk(0);
1969 $wgUser->saveNewtalk();
1970 }
1971 } elseif ( $wgUseEnotif ) {
1972 $wgUser->clearNotification( $this->mTitle );
1973 }
1974
1975 }
1976
1977 /**
1978 * Do standard deferred updates after page edit.
1979 * Every 1000th edit, prune the recent changes table.
1980 * @private
1981 * @param string $text
1982 */
1983 function editUpdates( $text, $summary, $minoredit, $timestamp_of_pagechange) {
1984 global $wgDeferredUpdateList, $wgDBname, $wgMemc;
1985 global $wgMessageCache, $wgUser, $wgUseEnotif;
1986
1987 wfSeedRandom();
1988 if ( 0 == mt_rand( 0, 999 ) ) {
1989 # Periodically flush old entries from the recentchanges table.
1990 global $wgRCMaxAge;
1991 $dbw =& wfGetDB( DB_MASTER );
1992 $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
1993 $recentchanges = $dbw->tableName( 'recentchanges' );
1994 $sql = "DELETE FROM $recentchanges WHERE rc_timestamp < '{$cutoff}'";
1995 $dbw->query( $sql );
1996 }
1997 $id = $this->getID();
1998 $title = $this->mTitle->getPrefixedDBkey();
1999 $shortTitle = $this->mTitle->getDBkey();
2000
2001 if ( 0 != $id ) {
2002 $u = new LinksUpdate( $id, $title );
2003 array_push( $wgDeferredUpdateList, $u );
2004 $u = new SiteStatsUpdate( 0, 1, $this->mGoodAdjustment, $this->mTotalAdjustment );
2005 array_push( $wgDeferredUpdateList, $u );
2006 $u = new SearchUpdate( $id, $title, $text );
2007 array_push( $wgDeferredUpdateList, $u );
2008
2009 # If this is another user's talk page, update newtalk
2010
2011 if ($this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getName()) {
2012 if ( $wgUseEnotif ) {
2013 require_once( 'UserTalkUpdate.php' );
2014 $u = new UserTalkUpdate( 1, $this->mTitle->getNamespace(), $shortTitle, $summary,
2015 $minoredit, $timestamp_of_pagechange);
2016 } else {
2017 $other = User::newFromName($shortTitle);
2018 if ($other) {
2019 $other->setNewtalk(1);
2020 $other->saveNewtalk();
2021 }
2022 }
2023 }
2024
2025 if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
2026 $wgMessageCache->replace( $shortTitle, $text );
2027 }
2028 }
2029 }
2030
2031 /**
2032 * @todo document this function
2033 * @private
2034 * @param string $oldid Revision ID of this article revision
2035 */
2036 function setOldSubtitle( $oldid=0 ) {
2037 global $wgLang, $wgOut, $wgUser;
2038
2039 $current = ( $oldid == $this->mLatest );
2040 $td = $wgLang->timeanddate( $this->mTimestamp, true );
2041 $sk = $wgUser->getSkin();
2042 $lnk = $current
2043 ? wfMsg( 'currentrevisionlink' )
2044 : $lnk = $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'currentrevisionlink' ) );
2045 $prevlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'previousrevision' ), 'direction=prev&oldid='.$oldid );
2046 $nextlink = $current
2047 ? wfMsg( 'nextrevision' )
2048 : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'nextrevision' ), 'direction=next&oldid='.$oldid );
2049 $r = wfMsg( 'revisionasofwithlink', $td, $lnk, $prevlink, $nextlink );
2050 $wgOut->setSubtitle( $r );
2051 }
2052
2053 /**
2054 * This function is called right before saving the wikitext,
2055 * so we can do things like signatures and links-in-context.
2056 *
2057 * @param string $text
2058 */
2059 function preSaveTransform( $text ) {
2060 global $wgParser, $wgUser;
2061 return $wgParser->preSaveTransform( $text, $this->mTitle, $wgUser, ParserOptions::newFromUser( $wgUser ) );
2062 }
2063
2064 /* Caching functions */
2065
2066 /**
2067 * checkLastModified returns true if it has taken care of all
2068 * output to the client that is necessary for this request.
2069 * (that is, it has sent a cached version of the page)
2070 */
2071 function tryFileCache() {
2072 static $called = false;
2073 if( $called ) {
2074 wfDebug( " tryFileCache() -- called twice!?\n" );
2075 return;
2076 }
2077 $called = true;
2078 if($this->isFileCacheable()) {
2079 $touched = $this->mTouched;
2080 $cache = new CacheManager( $this->mTitle );
2081 if($cache->isFileCacheGood( $touched )) {
2082 global $wgOut;
2083 wfDebug( " tryFileCache() - about to load\n" );
2084 $cache->loadFromFileCache();
2085 return true;
2086 } else {
2087 wfDebug( " tryFileCache() - starting buffer\n" );
2088 ob_start( array(&$cache, 'saveToFileCache' ) );
2089 }
2090 } else {
2091 wfDebug( " tryFileCache() - not cacheable\n" );
2092 }
2093 }
2094
2095 /**
2096 * Check if the page can be cached
2097 * @return bool
2098 */
2099 function isFileCacheable() {
2100 global $wgUser, $wgUseFileCache, $wgShowIPinHeader, $wgRequest;
2101 extract( $wgRequest->getValues( 'action', 'oldid', 'diff', 'redirect', 'printable' ) );
2102
2103 return $wgUseFileCache
2104 and (!$wgShowIPinHeader)
2105 and ($this->getID() != 0)
2106 and ($wgUser->isAnon())
2107 and (!$wgUser->getNewtalk())
2108 and ($this->mTitle->getNamespace() != NS_SPECIAL )
2109 and (empty( $action ) || $action == 'view')
2110 and (!isset($oldid))
2111 and (!isset($diff))
2112 and (!isset($redirect))
2113 and (!isset($printable))
2114 and (!$this->mRedirectedFrom);
2115 }
2116
2117 /**
2118 * Loads cur_touched and returns a value indicating if it should be used
2119 *
2120 */
2121 function checkTouched() {
2122 $fname = 'Article::checkTouched';
2123 if( !$this->mDataLoaded ) {
2124 $dbr =& $this->getDB();
2125 $data = $this->pageDataFromId( $dbr, $this->getId() );
2126 if( $data ) {
2127 $this->loadPageData( $data );
2128 }
2129 }
2130 return !$this->mIsRedirect;
2131 }
2132
2133 /**
2134 * Edit an article without doing all that other stuff
2135 * The article must already exist; link tables etc
2136 * are not updated, caches are not flushed.
2137 *
2138 * @param string $text text submitted
2139 * @param string $comment comment submitted
2140 * @param bool $minor whereas it's a minor modification
2141 */
2142 function quickEdit( $text, $comment = '', $minor = 0 ) {
2143 $fname = 'Article::quickEdit';
2144 wfProfileIn( $fname );
2145
2146 $dbw =& wfGetDB( DB_MASTER );
2147 $dbw->begin();
2148 $revision = new Revision( array(
2149 'page' => $this->getId(),
2150 'text' => $text,
2151 'comment' => $comment,
2152 'minor_edit' => $minor ? 1 : 0,
2153 ) );
2154 $revisionId = $revision->insertOn( $dbw );
2155 $this->updateRevisionOn( $dbw, $revision );
2156 $dbw->commit();
2157
2158 wfProfileOut( $fname );
2159 }
2160
2161 /**
2162 * Used to increment the view counter
2163 *
2164 * @static
2165 * @param integer $id article id
2166 */
2167 function incViewCount( $id ) {
2168 $id = intval( $id );
2169 global $wgHitcounterUpdateFreq;
2170
2171 $dbw =& wfGetDB( DB_MASTER );
2172 $pageTable = $dbw->tableName( 'page' );
2173 $hitcounterTable = $dbw->tableName( 'hitcounter' );
2174 $acchitsTable = $dbw->tableName( 'acchits' );
2175
2176 if( $wgHitcounterUpdateFreq <= 1 ){ //
2177 $dbw->query( "UPDATE $pageTable SET page_counter = page_counter + 1 WHERE page_id = $id" );
2178 return;
2179 }
2180
2181 # Not important enough to warrant an error page in case of failure
2182 $oldignore = $dbw->ignoreErrors( true );
2183
2184 $dbw->query( "INSERT INTO $hitcounterTable (hc_id) VALUES ({$id})" );
2185
2186 $checkfreq = intval( $wgHitcounterUpdateFreq/25 + 1 );
2187 if( (rand() % $checkfreq != 0) or ($dbw->lastErrno() != 0) ){
2188 # Most of the time (or on SQL errors), skip row count check
2189 $dbw->ignoreErrors( $oldignore );
2190 return;
2191 }
2192
2193 $res = $dbw->query("SELECT COUNT(*) as n FROM $hitcounterTable");
2194 $row = $dbw->fetchObject( $res );
2195 $rown = intval( $row->n );
2196 if( $rown >= $wgHitcounterUpdateFreq ){
2197 wfProfileIn( 'Article::incViewCount-collect' );
2198 $old_user_abort = ignore_user_abort( true );
2199
2200 $dbw->query("LOCK TABLES $hitcounterTable WRITE");
2201 $dbw->query("CREATE TEMPORARY TABLE $acchitsTable TYPE=HEAP ".
2202 "SELECT hc_id,COUNT(*) AS hc_n FROM $hitcounterTable ".
2203 'GROUP BY hc_id');
2204 $dbw->query("DELETE FROM $hitcounterTable");
2205 $dbw->query('UNLOCK TABLES');
2206 $dbw->query("UPDATE $pageTable,$acchitsTable SET page_counter=page_counter + hc_n ".
2207 'WHERE page_id = hc_id');
2208 $dbw->query("DROP TABLE $acchitsTable");
2209
2210 ignore_user_abort( $old_user_abort );
2211 wfProfileOut( 'Article::incViewCount-collect' );
2212 }
2213 $dbw->ignoreErrors( $oldignore );
2214 }
2215
2216 /**#@+
2217 * The onArticle*() functions are supposed to be a kind of hooks
2218 * which should be called whenever any of the specified actions
2219 * are done.
2220 *
2221 * This is a good place to put code to clear caches, for instance.
2222 *
2223 * This is called on page move and undelete, as well as edit
2224 * @static
2225 * @param $title_obj a title object
2226 */
2227
2228 function onArticleCreate($title_obj) {
2229 global $wgUseSquid, $wgPostCommitUpdateList;
2230
2231 $title_obj->touchLinks();
2232 $titles = $title_obj->getLinksTo();
2233
2234 # Purge squid
2235 if ( $wgUseSquid ) {
2236 $urls = $title_obj->getSquidURLs();
2237 foreach ( $titles as $linkTitle ) {
2238 $urls[] = $linkTitle->getInternalURL();
2239 }
2240 $u = new SquidUpdate( $urls );
2241 array_push( $wgPostCommitUpdateList, $u );
2242 }
2243 }
2244
2245 function onArticleDelete($title_obj) {
2246 $title_obj->touchLinks();
2247 }
2248
2249 function onArticleEdit($title_obj) {
2250 // This would be an appropriate place to purge caches.
2251 // Why's this not in here now?
2252 }
2253
2254 /**#@-*/
2255
2256 /**
2257 * Info about this page
2258 * Called for ?action=info when $wgAllowPageInfo is on.
2259 *
2260 * @access public
2261 */
2262 function info() {
2263 global $wgLang, $wgOut, $wgAllowPageInfo;
2264 $fname = 'Article::info';
2265
2266 if ( !$wgAllowPageInfo ) {
2267 $wgOut->errorpage( 'nosuchaction', 'nosuchactiontext' );
2268 return;
2269 }
2270
2271 $page = $this->mTitle->getSubjectPage();
2272
2273 $wgOut->setPagetitle( $page->getPrefixedText() );
2274 $wgOut->setSubtitle( wfMsg( 'infosubtitle' ));
2275
2276 # first, see if the page exists at all.
2277 $exists = $page->getArticleId() != 0;
2278 if( !$exists ) {
2279 $wgOut->addHTML( wfMsg('noarticletext') );
2280 } else {
2281 $dbr =& $this->getDB( DB_SLAVE );
2282 $wl_clause = array(
2283 'wl_title' => $page->getDBkey(),
2284 'wl_namespace' => $page->getNamespace() );
2285 $numwatchers = $dbr->selectField(
2286 'watchlist',
2287 'COUNT(*)',
2288 $wl_clause,
2289 $fname,
2290 $this->getSelectOptions() );
2291
2292 $pageInfo = $this->pageCountInfo( $page );
2293 $talkInfo = $this->pageCountInfo( $page->getTalkPage() );
2294
2295 $wgOut->addHTML( "<ul><li>" . wfMsg("numwatchers", $wgLang->formatNum( $numwatchers ) ) . '</li>' );
2296 $wgOut->addHTML( "<li>" . wfMsg('numedits', $wgLang->formatNum( $pageInfo['edits'] ) ) . '</li>');
2297 if( $talkInfo ) {
2298 $wgOut->addHTML( '<li>' . wfMsg("numtalkedits", $wgLang->formatNum( $talkInfo['edits'] ) ) . '</li>');
2299 }
2300 $wgOut->addHTML( '<li>' . wfMsg("numauthors", $wgLang->formatNum( $pageInfo['authors'] ) ) . '</li>' );
2301 if( $talkInfo ) {
2302 $wgOut->addHTML( '<li>' . wfMsg('numtalkauthors', $wgLang->formatNum( $talkInfo['authors'] ) ) . '</li>' );
2303 }
2304 $wgOut->addHTML( '</ul>' );
2305
2306 }
2307 }
2308
2309 /**
2310 * Return the total number of edits and number of unique editors
2311 * on a given page. If page does not exist, returns false.
2312 *
2313 * @param Title $title
2314 * @return array
2315 * @access private
2316 */
2317 function pageCountInfo( $title ) {
2318 $id = $title->getArticleId();
2319 if( $id == 0 ) {
2320 return false;
2321 }
2322
2323 $dbr =& $this->getDB( DB_SLAVE );
2324
2325 $rev_clause = array( 'rev_page' => $id );
2326 $fname = 'Article::pageCountInfo';
2327
2328 $edits = $dbr->selectField(
2329 'revision',
2330 'COUNT(rev_page)',
2331 $rev_clause,
2332 $fname,
2333 $this->getSelectOptions() );
2334
2335 $authors = $dbr->selectField(
2336 'revision',
2337 'COUNT(DISTINCT rev_user_text)',
2338 $rev_clause,
2339 $fname,
2340 $this->getSelectOptions() );
2341
2342 return array( 'edits' => $edits, 'authors' => $authors );
2343 }
2344
2345 /**
2346 * Return a list of templates used by this article.
2347 * Uses the links table to find the templates
2348 *
2349 * @return array
2350 */
2351 function getUsedTemplates() {
2352 $result = array();
2353 $id = $this->mTitle->getArticleID();
2354
2355 $db =& wfGetDB( DB_SLAVE );
2356 $res = $db->select( array( 'pagelinks' ),
2357 array( 'pl_title' ),
2358 array(
2359 'pl_from' => $id,
2360 'pl_namespace' => NS_TEMPLATE ),
2361 'Article:getUsedTemplates' );
2362 if ( false !== $res ) {
2363 if ( $db->numRows( $res ) ) {
2364 while ( $row = $db->fetchObject( $res ) ) {
2365 $result[] = $row->pl_title;
2366 }
2367 }
2368 }
2369 $db->freeResult( $res );
2370 return $result;
2371 }
2372
2373
2374 }
2375
2376
2377 ?>