r13842 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r13841‎ | r13842 | r13843 >
Date:15:28, 24 April 2006
Author:hashar
Status:old
Tags:
Comment:
merge trunk@13778 - trunk@13841
Modified paths:
  • /branches/hashar/RELEASE-NOTES (modified) (history)
  • /branches/hashar/includes/CategoryPage.php (modified) (history)
  • /branches/hashar/includes/Database.php (modified) (history)
  • /branches/hashar/includes/DefaultSettings.php (modified) (history)
  • /branches/hashar/includes/LinkBatch.php (modified) (history)
  • /branches/hashar/includes/LinksUpdate.php (modified) (history)
  • /branches/hashar/includes/QueryPage.php (modified) (history)
  • /branches/hashar/includes/Revision.php (modified) (history)
  • /branches/hashar/includes/SkinTemplate.php (modified) (history)
  • /branches/hashar/includes/SpecialBrokenRedirects.php (modified) (history)
  • /branches/hashar/includes/SpecialDoubleRedirects.php (modified) (history)
  • /branches/hashar/includes/SpecialImport.php (modified) (history)
  • /branches/hashar/includes/SpecialListusers.php (modified) (history)
  • /branches/hashar/includes/SpecialMostcategories.php (modified) (history)
  • /branches/hashar/includes/SpecialMostlinked.php (modified) (history)
  • /branches/hashar/includes/SpecialMostrevisions.php (modified) (history)
  • /branches/hashar/includes/SpecialNewpages.php (modified) (history)
  • /branches/hashar/includes/SpecialUnusedtemplates.php (modified) (history)
  • /branches/hashar/includes/SpecialVersion.php (modified) (history)
  • /branches/hashar/includes/SpecialWantedpages.php (modified) (history)
  • /branches/hashar/includes/SpecialWatchlist.php (modified) (history)
  • /branches/hashar/languages/LanguageBs.php (modified) (history)
  • /branches/hashar/languages/Messages.php (modified) (history)
  • /branches/hashar/languages/MessagesFr.php (modified) (history)
  • /branches/hashar/languages/MessagesId.php (modified) (history)
  • /branches/hashar/maintenance/storage/checkStorage.php (added) (history)
  • /branches/hashar/maintenance/storage/checkStorage.php (added) (history)

Diff [purge]

Index: branches/hashar/includes/LinksUpdate.php
@@ -173,29 +173,60 @@
174174
175175 wfProfileOut( $fname );
176176 }
 177+
 178+ /**
 179+ * Invalidate the cache of a list of pages from a single namespace
 180+ *
 181+ * @param integer $namespace
 182+ * @param array $dbkeys
 183+ */
 184+ function invalidatePages( $namespace, $dbkeys ) {
 185+ $fname = 'LinksUpdate::invalidatePages';
 186+
 187+ if ( !count( $dbkeys ) ) {
 188+ return;
 189+ }
 190+
 191+ /**
 192+ * Determine which pages need to be updated
 193+ * This is necessary to prevent the job queue from smashing the DB with
 194+ * large numbers of concurrent invalidations of the same page
 195+ */
 196+ $now = $this->mDb->timestamp();
 197+ $ids = array();
 198+ $res = $this->mDb->select( 'page', array( 'page_id' ),
 199+ array(
 200+ 'page_namespace' => $namespace,
 201+ 'page_title IN (' . $this->mDb->makeList( $dbkeys ) . ')',
 202+ 'page_touched < ' . $this->mDb->addQuotes( $now )
 203+ ), $fname
 204+ );
 205+ while ( $row = $this->mDb->fetchObject( $res ) ) {
 206+ $ids[] = $row->page_id;
 207+ }
 208+ if ( !count( $ids ) ) {
 209+ return;
 210+ }
 211+
 212+ /**
 213+ * Do the update
 214+ * We still need the page_touched condition, in case the row has changed since
 215+ * the non-locking select above.
 216+ */
 217+ $this->mDb->update( 'page', array( 'page_touched' => $now ),
 218+ array(
 219+ 'page_id IN (' . $this->mDb->makeList( $ids ) . ')',
 220+ 'page_touched < ' . $this->mDb->addQuotes( $now )
 221+ ), $fname
 222+ );
 223+ }
177224
178225 function invalidateCategories( $cats ) {
179 - $fname = 'LinksUpdate::invalidateCategories';
180 - if ( count( $cats ) ) {
181 - $this->mDb->update( 'page', array( 'page_touched' => $this->mDb->timestamp() ),
182 - array(
183 - 'page_namespace' => NS_CATEGORY,
184 - 'page_title IN (' . $this->mDb->makeList( array_keys( $cats ) ) . ')'
185 - ), $fname
186 - );
187 - }
 226+ $this->invalidatePages( NS_CATEGORY, array_keys( $cats ) );
188227 }
189228
190229 function invalidateImageDescriptions( $images ) {
191 - $fname = 'LinksUpdate::invalidateImageDescriptions';
192 - if ( count( $images ) ) {
193 - $this->mDb->update( 'page', array( 'page_touched' => $this->mDb->timestamp() ),
194 - array(
195 - 'page_namespace' => NS_IMAGE,
196 - 'page_title IN (' . $this->mDb->makeList( array_keys( $images ) ) . ')'
197 - ), $fname
198 - );
199 - }
 230+ $this->invalidatePages( NS_IMAGE, array_keys( $images ) );
200231 }
201232
202233 function dumbTableUpdate( $table, $insertions, $fromField ) {
Index: branches/hashar/includes/SpecialNewpages.php
@@ -106,6 +106,47 @@
107107 }
108108 return parent::feedItemDesc( $row );
109109 }
 110+
 111+ /**
 112+ * Show a namespace selection form for filtering
 113+ *
 114+ * @return string
 115+ */
 116+ function getPageHeader() {
 117+ $thisTitle = Title::makeTitle( NS_SPECIAL, $this->getName() );
 118+ $form = wfOpenElement( 'form', array(
 119+ 'method' => 'post',
 120+ 'action' => $thisTitle->getLocalUrl() ) );
 121+ $form .= wfElement( 'label', array( 'for' => 'namespace' ),
 122+ wfMsg( 'namespace' ) ) . ' ';
 123+ $form .= HtmlNamespaceSelector( $this->namespace );
 124+ # Preserve the offset and limit
 125+ $form .= wfElement( 'input', array(
 126+ 'type' => 'hidden',
 127+ 'name' => 'offset',
 128+ 'value' => $this->offset ) );
 129+ $form .= wfElement( 'input', array(
 130+ 'type' => 'hidden',
 131+ 'name' => 'limit',
 132+ 'value' => $this->limit ) );
 133+ $form .= wfElement( 'input', array(
 134+ 'type' => 'submit',
 135+ 'name' => 'submit',
 136+ 'id' => 'submit',
 137+ 'value' => wfMsg( 'allpagessubmit' ) ) );
 138+ $form .= wfCloseElement( 'form' );
 139+ return( $form );
 140+ }
 141+
 142+ /**
 143+ * Link parameters
 144+ *
 145+ * @return array
 146+ */
 147+ function linkParameters() {
 148+ return( array( 'namespace' => $this->namespace ) );
 149+ }
 150+
110151 }
111152
112153 /**
@@ -136,7 +177,11 @@
137178 }
138179 }
139180 }
 181+ } else {
 182+ if( $ns = $wgRequest->getInt( 'namespace', 0 ) )
 183+ $namespace = $ns;
140184 }
 185+
141186 if ( ! isset( $shownavigation ) )
142187 $shownavigation = ! $specialPage->including();
143188
Index: branches/hashar/includes/SpecialImport.php
@@ -122,6 +122,7 @@
123123 */
124124 class WikiRevision {
125125 var $title = NULL;
 126+ var $id = 0;
126127 var $timestamp = "20010115000000";
127128 var $user = 0;
128129 var $user_text = "";
@@ -133,6 +134,10 @@
134135 $this->title = Title::newFromText( $text );
135136 }
136137
 138+ function setID( $id ) {
 139+ $this->id = $id;
 140+ }
 141+
137142 function setTimestamp( $ts ) {
138143 # 2003-08-05T18:30:02Z
139144 $this->timestamp = wfTimestamp( TS_MW, $ts );
@@ -162,6 +167,10 @@
163168 return $this->title;
164169 }
165170
 171+ function getID() {
 172+ return $this->id;
 173+ }
 174+
166175 function getTimestamp() {
167176 return $this->timestamp;
168177 }
@@ -468,6 +477,11 @@
469478 $this->workTitle = $this->appenddata;
470479 $this->pageCallback( $this->workTitle );
471480 break;
 481+ case "id":
 482+ if ( $this->parenttag == 'revision' ) {
 483+ $this->workRevision->setID( $this->appenddata );
 484+ }
 485+ break;
472486 case "text":
473487 $this->workRevision->setText( $this->appenddata );
474488 break;
Index: branches/hashar/includes/SpecialUnusedtemplates.php
@@ -47,7 +47,8 @@
4848 }
4949
5050 function getPageHeader() {
51 - return wfMsgHtml( 'unusedtemplatestext' );
 51+ global $wgOut;
 52+ return $wgOut->parse( wfMsg( 'unusedtemplatestext' ) );
5253 }
5354
5455 }
Index: branches/hashar/includes/SpecialVersion.php
@@ -38,7 +38,7 @@
3939 }
4040
4141 /**#@+
42 - * @access private
 42+ * @private
4343 */
4444
4545 /**
Index: branches/hashar/includes/SpecialMostcategories.php
@@ -34,7 +34,7 @@
3535 FROM $categorylinks
3636 LEFT JOIN $page ON cl_from = page_id
3737 WHERE page_namespace = " . NS_MAIN . "
38 - GROUP BY cl_from, page_namespace, page_title
 38+ GROUP BY cl_from
3939 HAVING COUNT(*) > 1
4040 ";
4141 }
Index: branches/hashar/includes/LinkBatch.php
@@ -47,6 +47,20 @@
4848 }
4949
5050 /**
 51+ * Returns true if no pages have been added, false otherwise.
 52+ */
 53+ function isEmpty() {
 54+ return ($this->getSize() == 0);
 55+ }
 56+
 57+ /**
 58+ * Returns the size of the batch.
 59+ */
 60+ function getSize() {
 61+ return count( $this->data );
 62+ }
 63+
 64+ /**
5165 * Do the query and add the results to the LinkCache object
5266 * Return an array mapping PDBK to ID
5367 */
@@ -100,7 +114,7 @@
101115 $fname = 'LinkBatch::doQuery';
102116 $namespaces = array();
103117
104 - if ( !count( $this->data ) ) {
 118+ if ( $this->isEmpty() ) {
105119 return false;
106120 }
107121 wfProfileIn( $fname );
Index: branches/hashar/includes/SpecialWantedpages.php
@@ -52,7 +52,7 @@
5353 }
5454
5555 /**
56 - * Fetch user page links and cache their existence
 56+ * Cache page existence for performance
5757 */
5858 function preprocessResults( &$db, &$res ) {
5959 $batch = new LinkBatch;
@@ -70,17 +70,42 @@
7171 function formatResult( $skin, $result ) {
7272 global $wgContLang;
7373
74 - $nt = Title::makeTitle( $result->namespace, $result->title );
75 - $text = $wgContLang->convert( $nt->getPrefixedText() );
76 - $plink = $this->isCached() ?
77 - $skin->makeLinkObj( $nt, $text ) :
78 - $skin->makeBrokenLink( $nt->getPrefixedText(), $text );
 74+ $title = Title::makeTitleSafe( $result->namespace, $result->title );
7975
80 - $nl = wfMsg( 'nlinks', $result->value );
81 - $nlink = $skin->makeKnownLink( $wgContLang->specialPage( 'Whatlinkshere' ), $nl, 'target=' . $nt->getPrefixedURL() );
82 -
83 - return $this->nlinks ? "$plink ($nlink)" : $plink;
 76+ if( $this->isCached() ) {
 77+ # Check existence; which is stored in the link cache
 78+ if( !$title->exists() ) {
 79+ # Make a redlink
 80+ $pageLink = $skin->makeBrokenLinkObj( $title );
 81+ } else {
 82+ # Make a struck-out blue link
 83+ $pageLink = "<s>" . $skin->makeKnownLinkObj( $title ) . "</s>";
 84+ }
 85+ } else {
 86+ # Not cached? Don't bother checking existence; it can't
 87+ $pageLink = $skin->makeBrokenLinkObj( $title );
 88+ }
 89+
 90+ # Make a link to "what links here" if it's required
 91+ $wlhLink = $this->nlinks
 92+ ? " (" . $this->makeWlhLink( $title, $skin, wfMsgHtml( 'nlinks', $result->value ) ) . ")"
 93+ : "";
 94+
 95+ return "{$pageLink}{$wlhLink}";
8496 }
 97+
 98+ /**
 99+ * Make a "what links here" link for a specified title
 100+ * @param $title Title to make the link for
 101+ * @param $skin Skin to use
 102+ * @param $text Link text
 103+ * @return string
 104+ */
 105+ function makeWlhLink( &$title, &$skin, $text ) {
 106+ $wlhTitle = Title::makeTitle( NS_SPECIAL, 'Whatlinkshere' );
 107+ return $skin->makeKnownLinkObj( $wlhTitle, $text, 'target=' . $title->getPrefixedUrl() );
 108+ }
 109+
85110 }
86111
87112 /**
Index: branches/hashar/includes/SpecialWatchlist.php
@@ -90,6 +90,7 @@
9191 if( $wl->removeWatch() === false ) {
9292 $wgOut->addHTML( "<br />\n" . wfMsg( 'couldntremove', htmlspecialchars($one) ) );
9393 } else {
 94+ wfRunHooks('UnwatchArticle', array(&$wgUser, new Article($t)));
9495 $wgOut->addHTML( ' (' . htmlspecialchars($one) . ')' );
9596 }
9697 } else {
Index: branches/hashar/includes/DefaultSettings.php
@@ -1623,7 +1623,7 @@
16241624 '/^Mozilla\/4\.[^ ]+ .*?\((?!compatible).*; [UIN]/',
16251625
16261626 /**
1627 - * MSIE on Mac OS 9 is teh sux0r, converts þ to <thorn>, ð to <eth>, Þ to <THORN> and �? to <ETH>
 1627+ * MSIE on Mac OS 9 is teh sux0r, converts þ to <thorn>, ð to <eth>, Þ to <THORN> and Ð to <ETH>
16281628 *
16291629 * Known useragents:
16301630 * - Mozilla/4.0 (compatible; MSIE 5.0; Mac_PowerPC)
Index: branches/hashar/includes/Revision.php
@@ -530,6 +530,11 @@
531531 if( in_array( 'object', $flags ) ) {
532532 # Generic compressed storage
533533 $obj = unserialize( $text );
 534+ if ( !is_object( $obj ) ) {
 535+ // Invalid object
 536+ wfProfileOut( $fname );
 537+ return false;
 538+ }
534539 $text = $obj->getText();
535540 }
536541
Index: branches/hashar/includes/SkinTemplate.php
@@ -37,7 +37,7 @@
3838 * Wrapper object for MediaWiki's localization functions,
3939 * to be passed to the template engine.
4040 *
41 - * @access private
 41+ * @private
4242 * @package MediaWiki
4343 */
4444 class MediaWiki_I18N {
@@ -74,7 +74,7 @@
7575 */
7676 class SkinTemplate extends Skin {
7777 /**#@+
78 - * @access private
 78+ * @private
7979 */
8080
8181 /**
@@ -120,7 +120,7 @@
121121 * @param string $repository subdirectory where we keep template files
122122 * @param string $cache_dir
123123 * @return object
124 - * @access private
 124+ * @private
125125 */
126126 function setupTemplate( $classname, $repository=false, $cache_dir=false ) {
127127 return new $classname();
@@ -130,7 +130,7 @@
131131 * initialize various variables and generate the template
132132 *
133133 * @param OutputPage $out
134 - * @access public
 134+ * @public
135135 */
136136 function outputPage( &$out ) {
137137 global $wgTitle, $wgArticle, $wgUser, $wgLang, $wgContLang, $wgOut;
@@ -438,7 +438,7 @@
439439 * For the base class, assume strings all around.
440440 *
441441 * @param mixed $str
442 - * @access private
 442+ * @private
443443 */
444444 function printOrError( &$str ) {
445445 echo $str;
@@ -447,7 +447,7 @@
448448 /**
449449 * build array of urls for personal toolbar
450450 * @return array
451 - * @access private
 451+ * @private
452452 */
453453 function buildPersonalUrls() {
454454 global $wgTitle, $wgShowIPinHeader;
@@ -584,7 +584,7 @@
585585 /**
586586 * an array of edit links by default used for the tabs
587587 * @return array
588 - * @access private
 588+ * @private
589589 */
590590 function buildContentActionUrls () {
591591 global $wgContLang;
@@ -762,7 +762,7 @@
763763 /**
764764 * build array of common navigation links
765765 * @return array
766 - * @access private
 766+ * @private
767767 */
768768 function buildNavUrls () {
769769 global $wgUseTrackbacks, $wgTitle, $wgArticle;
@@ -870,14 +870,14 @@
871871 /**
872872 * Generate strings used for xml 'id' names
873873 * @return string
874 - * @access private
 874+ * @private
875875 */
876876 function getNameSpaceKey () {
877877 return $this->mTitle->getNamespaceKey();
878878 }
879879
880880 /**
881 - * @access private
 881+ * @private
882882 */
883883 function setupUserCss() {
884884 $fname = 'SkinTemplate::setupUserCss';
@@ -926,7 +926,7 @@
927927 }
928928
929929 /**
930 - * @access private
 930+ * @private
931931 */
932932 function setupUserJs() {
933933 $fname = 'SkinTemplate::setupUserJs';
@@ -950,7 +950,7 @@
951951 * Code for extensions to hook into to provide per-page CSS, see
952952 * extensions/PageCSS/PageCSS.php for an implementation of this.
953953 *
954 - * @access private
 954+ * @private
955955 */
956956 function setupPageCss() {
957957 $fname = 'SkinTemplate::setupPageCss';
@@ -964,7 +964,7 @@
965965
966966 /**
967967 * returns css with user-specific options
968 - * @access public
 968+ * @public
969969 */
970970
971971 function getUserStylesheet() {
@@ -978,7 +978,7 @@
979979 }
980980
981981 /**
982 - * @access public
 982+ * @public
983983 */
984984 function getUserJs() {
985985 $fname = 'SkinTemplate::getUserJs';
@@ -1010,7 +1010,7 @@
10111011 */
10121012 class QuickTemplate {
10131013 /**
1014 - * @access public
 1014+ * @public
10151015 */
10161016 function QuickTemplate() {
10171017 $this->data = array();
@@ -1018,28 +1018,28 @@
10191019 }
10201020
10211021 /**
1022 - * @access public
 1022+ * @public
10231023 */
10241024 function set( $name, $value ) {
10251025 $this->data[$name] = $value;
10261026 }
10271027
10281028 /**
1029 - * @access public
 1029+ * @public
10301030 */
10311031 function setRef($name, &$value) {
10321032 $this->data[$name] =& $value;
10331033 }
10341034
10351035 /**
1036 - * @access public
 1036+ * @public
10371037 */
10381038 function setTranslator( &$t ) {
10391039 $this->translator = &$t;
10401040 }
10411041
10421042 /**
1043 - * @access public
 1043+ * @public
10441044 */
10451045 function execute() {
10461046 echo "Override this function.";
@@ -1047,28 +1047,28 @@
10481048
10491049
10501050 /**
1051 - * @access private
 1051+ * @private
10521052 */
10531053 function text( $str ) {
10541054 echo htmlspecialchars( $this->data[$str] );
10551055 }
10561056
10571057 /**
1058 - * @access private
 1058+ * @private
10591059 */
10601060 function html( $str ) {
10611061 echo $this->data[$str];
10621062 }
10631063
10641064 /**
1065 - * @access private
 1065+ * @private
10661066 */
10671067 function msg( $str ) {
10681068 echo htmlspecialchars( $this->translator->translate( $str ) );
10691069 }
10701070
10711071 /**
1072 - * @access private
 1072+ * @private
10731073 */
10741074 function msgHtml( $str ) {
10751075 echo $this->translator->translate( $str );
@@ -1076,7 +1076,7 @@
10771077
10781078 /**
10791079 * An ugly, ugly hack.
1080 - * @access private
 1080+ * @private
10811081 */
10821082 function msgWiki( $str ) {
10831083 global $wgParser, $wgTitle, $wgOut;
@@ -1088,14 +1088,14 @@
10891089 }
10901090
10911091 /**
1092 - * @access private
 1092+ * @private
10931093 */
10941094 function haveData( $str ) {
10951095 return $this->data[$str];
10961096 }
10971097
10981098 /**
1099 - * @access private
 1099+ * @private
11001100 */
11011101 function haveMsg( $str ) {
11021102 $msg = $this->translator->translate( $str );
Index: branches/hashar/includes/SpecialMostlinked.php
@@ -37,7 +37,7 @@
3838 page_namespace
3939 FROM $pagelinks
4040 LEFT JOIN $page ON pl_namespace=page_namespace AND pl_title=page_title
41 - GROUP BY pl_namespace,pl_title,page_namespace
 41+ GROUP BY pl_namespace,pl_title
4242 HAVING COUNT(*) > 1";
4343 }
4444
Index: branches/hashar/includes/SpecialListusers.php
@@ -135,7 +135,7 @@
136136 "FROM $user ".
137137 "LEFT JOIN $user_groups ON user_id=ug_user " .
138138 $this->userQueryWhere( $dbr ) .
139 - " GROUP BY user_name, user_id";
 139+ " GROUP BY user_name";
140140
141141 return $sql;
142142 }
Index: branches/hashar/includes/Database.php
@@ -1078,8 +1078,10 @@
10791079
10801080 /**
10811081 * Makes a wfStrencoded list from an array
1082 - * $mode: LIST_COMMA - comma separated, no field names
 1082+ * $mode:
 1083+ * LIST_COMMA - comma separated, no field names
10831084 * LIST_AND - ANDed WHERE clause (without the WHERE)
 1085+ * LIST_OR - ORed WHERE clause (without the WHERE)
10841086 * LIST_SET - comma separated with field names, like a SET clause
10851087 * LIST_NAMES - comma separated field names
10861088 */
@@ -1104,7 +1106,7 @@
11051107 }
11061108 if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) {
11071109 $list .= "($value)";
1108 - } elseif ( $mode == LIST_AND && is_array ($value) ) {
 1110+ } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array ($value) ) {
11091111 $list .= $field." IN (".$this->makeList($value).") ";
11101112 } else {
11111113 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
Index: branches/hashar/includes/SpecialDoubleRedirects.php
@@ -29,24 +29,38 @@
3030 return '<p>'.wfMsg("doubleredirectstext")."</p><br />\n";
3131 }
3232
33 - function getSQL() {
34 - $dbr =& wfGetDB( DB_SLAVE );
 33+ function getSQLText( &$dbr, $namespace = null, $title = null ) {
 34+
3535 extract( $dbr->tableNames( 'page', 'pagelinks' ) );
3636
37 - $sql = "SELECT 'DoubleRedirects' as type," .
38 - " pa.page_namespace as namespace, pa.page_title as title," .
39 - " pb.page_namespace as nsb, pb.page_title as tb," .
40 - " pc.page_namespace as nsc, pc.page_title as tc" .
41 - " FROM $pagelinks AS la, $pagelinks AS lb, $page AS pa, $page AS pb, $page AS pc" .
42 - " WHERE pa.page_is_redirect=1 AND pb.page_is_redirect=1" .
43 - " AND la.pl_from=pa.page_id" .
44 - " AND la.pl_namespace=pb.page_namespace" .
45 - " AND la.pl_title=pb.page_title" .
46 - " AND lb.pl_from=pb.page_id" .
47 - " AND lb.pl_namespace=pc.page_namespace" .
48 - " AND lb.pl_title=pc.page_title";
 37+ $limitToTitle = !( $namespace === null && $title === null );
 38+ $sql = $limitToTitle ? "SELECT" : "SELECT 'DoubleRedirects' as type," ;
 39+ $sql .=
 40+ " pa.page_namespace as namespace, pa.page_title as title," .
 41+ " pb.page_namespace as nsb, pb.page_title as tb," .
 42+ " pc.page_namespace as nsc, pc.page_title as tc" .
 43+ " FROM $pagelinks AS la, $pagelinks AS lb, $page AS pa, $page AS pb, $page AS pc" .
 44+ " WHERE pa.page_is_redirect=1 AND pb.page_is_redirect=1" .
 45+ " AND la.pl_from=pa.page_id" .
 46+ " AND la.pl_namespace=pb.page_namespace" .
 47+ " AND la.pl_title=pb.page_title" .
 48+ " AND lb.pl_from=pb.page_id" .
 49+ " AND lb.pl_namespace=pc.page_namespace" .
 50+ " AND lb.pl_title=pc.page_title";
 51+
 52+ if( $limitToTitle ) {
 53+ $encTitle = $dbr->addQuotes( $title );
 54+ $sql .= " AND pa.page_namespace=$namespace" .
 55+ " AND pa.page_title=$encTitle";
 56+ }
 57+
4958 return $sql;
5059 }
 60+
 61+ function getSQL() {
 62+ $dbr =& wfGetDB( DB_SLAVE );
 63+ return $this->getSQLText( $dbr );
 64+ }
5165
5266 function getOrder() {
5367 return '';
@@ -60,25 +74,11 @@
6175
6276 if ( $result && !isset( $result->nsb ) ) {
6377 $dbr =& wfGetDB( DB_SLAVE );
64 - extract( $dbr->tableNames( 'page', 'pagelinks' ) );
65 - $encTitle = $dbr->addQuotes( $result->title );
66 -
67 - $sql = "SELECT pa.page_namespace as namespace, pa.page_title as title," .
68 - " pb.page_namespace as nsb, pb.page_title as tb," .
69 - " pc.page_namespace as nsc, pc.page_title as tc" .
70 - " FROM $pagelinks AS la, $pagelinks AS lb, $page AS pa, $page AS pb, $page AS pc" .
71 - " WHERE pa.page_is_redirect=1 AND pb.page_is_redirect=1" .
72 - " AND la.pl_from=pa.page_id" .
73 - " AND la.pl_namespace=pb.page_namespace" .
74 - " AND la.pl_title=pb.page_title" .
75 - " AND lb.pl_from=pb.page_id" .
76 - " AND lb.pl_namespace=pc.page_namespace" .
77 - " AND lb.pl_title=pc.page_title" .
78 - " AND pa.page_namespace={$result->namespace}" .
79 - " AND pa.page_title=$encTitle";
 78+ $sql = $this->getSQLText( $dbr, $result->namespace, $result->title );
8079 $res = $dbr->query( $sql, $fname );
8180 if ( $res ) {
8281 $result = $dbr->fetchObject( $res );
 82+ $dbr->freeResult( $res );
8383 }
8484 }
8585 if ( !$result ) {
Index: branches/hashar/includes/QueryPage.php
@@ -63,6 +63,14 @@
6464 * @var bool
6565 */
6666 var $listoutput = false;
 67+
 68+ /**
 69+ * The offset and limit in use, as passed to the query() function
 70+ *
 71+ * @var integer
 72+ */
 73+ var $offset = 0;
 74+ var $limit = 0;
6775
6876 /**
6977 * A mutator for $this->listoutput;
@@ -264,6 +272,9 @@
265273 function doQuery( $offset, $limit, $shownavigation=true ) {
266274 global $wgUser, $wgOut, $wgLang, $wgContLang;
267275
 276+ $this->offset = $offset;
 277+ $this->limit = $limit;
 278+
268279 $sname = $this->getName();
269280 $fname = get_class($this) . '::doQuery';
270281 $sql = $this->getSQL();
Index: branches/hashar/includes/CategoryPage.php
@@ -61,7 +61,7 @@
6262 * @param string $from -- return only sort keys from this item on
6363 * @param string $until -- don't return keys after this point.
6464 * @return string HTML output
65 - * @access private
 65+ * @private
6666 */
6767 function doCategoryMagic( $from = '', $until = '' ) {
6868 global $wgContLang,$wgUser, $wgCategoryMagicGallery, $wgCategoryPagingLimit, $wgInterlanguageTitles, $wgLanguageCode;
@@ -210,7 +210,7 @@
211211 * @param array $articles
212212 * @param string $message
213213 * @return string
214 - * @access private
 214+ * @private
215215 */
216216 function formatCount( $articles, $message ) {
217217 global $wgContLang;
@@ -229,7 +229,7 @@
230230 * @param array $articles_start_char
231231 * @param int $cutoff
232232 * @return string
233 - * @access private
 233+ * @private
234234 */
235235 function formatList( $articles, $articles_start_char, $cutoff = 6 ) {
236236 if ( count ( $articles ) > $cutoff ) {
@@ -248,7 +248,7 @@
249249 * @param array $articles
250250 * @param array $articles_start_char
251251 * @return string
252 - * @access private
 252+ * @private
253253 */
254254 function columnList( $articles, $articles_start_char ) {
255255 // divide list into three equal chunks
@@ -307,7 +307,7 @@
308308 * @param array $articles
309309 * @param array $articles_start_char
310310 * @return string
311 - * @access private
 311+ * @private
312312 */
313313 function shortList( $articles, $articles_start_char ) {
314314 $r = '<h3>' . htmlspecialchars( $articles_start_char[0] ) . "</h3>\n";
@@ -332,7 +332,7 @@
333333 * @param int $limit
334334 * @param array $query - additional query options to pass
335335 * @return string
336 - * @access private
 336+ * @private
337337 */
338338 function pagingLinks( $title, $first, $last, $limit, $query = array() ) {
339339 global $wgUser, $wgLang;
Index: branches/hashar/includes/SpecialMostrevisions.php
@@ -36,7 +36,7 @@
3737 FROM $revision
3838 LEFT JOIN $page ON page_id = rev_page
3939 WHERE page_namespace = " . NS_MAIN . "
40 - GROUP BY rev_page, page_namespace, page_title
 40+ GROUP BY rev_page
4141 HAVING COUNT(*) > 1
4242 ";
4343 }
Index: branches/hashar/includes/SpecialBrokenRedirects.php
@@ -26,7 +26,8 @@
2727 function isSyndicated() { return false; }
2828
2929 function getPageHeader( ) {
30 - return wfMsgWikiHtml('brokenredirectstext')."<br />\n";
 30+ global $wgOut;
 31+ return $wgOut->parse( wfMsg( 'brokenredirectstext' ) );
3132 }
3233
3334 function getSQL() {
Index: branches/hashar/RELEASE-NOTES
@@ -108,8 +108,13 @@
109109 * (bug 5422) Stub for Romani (rmy) language which extends ro
110110 * Fix linktrail for LanguageSr
111111 * (bug 5664) Fix Bosnian linktrail
 112+* (bug 3825) Namespace filtering on Special:Newpages
 113+* (bug 1922) When Special:Wantedpages is cached, mark links to pages
 114+ which have since been created
 115+* (bug 5659) Change grammar hacks for Bosnian Wikimedia namespaces.
 116+ This sort of special casing should be removed and fixed properly.
 117+* Remove useless whitespace from Special:Brokenredirects header
112118
113 -
114119 == Compatibility ==
115120
116121 Older PHP 4.2 and 4.1 releases are no longer supported; PHP 4 users must
Index: branches/hashar/languages/MessagesFr.php
@@ -612,13 +612,15 @@
613613 'statistics' => 'Statistiques',
614614 'sitestats' => 'Statistiques du site',
615615 'userstats' => 'Statistiques utilisateur',
616 -'sitestatstext' => 'La base de données contient actuellement <b>$1</b> pages.
 616+'sitestatstext' => "La base de données contient actuellement <b>$1</b> pages.
617617
618 -Ce chiffre inclut les pages « discussion », les pages relatives à {{SITENAME}}, les pages minimales ("bouchons"), les pages de redirection, ainsi que d\'autres pages qui ne peuvent sans doute pas être considérées comme des articles.
619 -Si l\'on exclut ces pages, il reste <b>$2</b> pages qui sont probablement de véritables articles.<p>
 618+Ce chiffre inclut les pages « discussion », les pages relatives à {{SITENAME}}, les pages minimales (\"bouchons\"), les pages de redirection, ainsi que d'autres pages qui ne peuvent sans doute pas être considérées comme des articles.
 619+Si l'on exclut ces pages, il reste <b>$2</b> pages qui sont probablement de véritables articles.<p>
620620 <b>$3</b> pages ont été consultées et <b>$4</b> pages modifiées.
621621
622 -Cela représente une moyenne de <b>$5</b> modifications par page et de <b>$6</b> consultations pour une modification.',
 622+Cela représente une moyenne de <b>$5</b> modifications par page et de <b>$6</b> consultations pour une modification.</p>
 623+
 624+<p>Il y a '''$7''' articles dans [http://meta.wikimedia.org/wiki/Help:Job_queue le file de tâche].</p>",
623625 'userstatstext' => 'Il y a <b>$1</b> utilisateurs enregistrés. Parmi ceux-ci, <b>$2</b> ont le statut d\'administrateur (voir $3).',
624626
625627
Index: branches/hashar/languages/Messages.php
@@ -36,7 +36,7 @@
3737 'tog-highlightbroken' => 'Format broken links <a href="" class="new">like this</a> (alternative: like this<a href="" class="internal">?</a>).',
3838 'tog-justify' => 'Justify paragraphs',
3939 'tog-hideminor' => 'Hide minor edits in recent changes',
40 -'tog-extendwatchlist' => 'Enhanced watchlist',
 40+'tog-extendwatchlist' => 'Expand watchlist to show all applicable changes',
4141 'tog-usenewrc' => 'Enhanced recent changes (JavaScript)',
4242 'tog-numberheadings' => 'Auto-number headings',
4343 'tog-showtoolbar' => 'Show edit toolbar (JavaScript)',
Index: branches/hashar/languages/LanguageBs.php
@@ -56,6 +56,7 @@
5757 '6:12, 5 jan 2001',
5858 );
5959
 60+/* NOT USED IN STABLE VERSION */
6061 /* private */ $wgMagicWordsBs = array(
6162 # ID CASE SYNONYMS
6263 MAG_REDIRECT => array( 0, '#Preusmjeri', '#redirect', '#preusmjeri', '#PREUSMJERI' ),
@@ -168,95 +169,95 @@
169170 function convertGrammar( $word, $case ) {
170171 switch ( $case ) {
171172 case 'genitiv': # genitive
172 - if ( $word == 'Vikipedija' ) {
173 - $word = 'Vikipedije';
174 - } elseif ( $word == 'Vikiknjige' ) {
175 - $word = 'Vikiknjiga';
176 - } elseif ( $word == 'Vikivijesti' ) {
177 - $word = 'Vikivijesti';
178 - } elseif ( $word == 'Vikicitati' ) {
179 - $word = 'Vikicitata';
180 - } elseif ( $word == 'Vikiizvor' ) {
181 - $word = 'Vikiizvora';
182 - } elseif ( $word == 'Vikiriječnik' ) {
183 - $word = 'Vikiriječnika';
 173+ if ( $word == 'Wikipedia' ) {
 174+ $word = 'Wikipedije';
 175+ } elseif ( $word == 'Wikiknjige' ) {
 176+ $word = 'Wikiknjiga';
 177+ } elseif ( $word == 'Wikivijesti' ) {
 178+ $word = 'Wikivijesti';
 179+ } elseif ( $word == 'Wikicitati' ) {
 180+ $word = 'Wikicitata';
 181+ } elseif ( $word == 'Wikiizvor' ) {
 182+ $word = 'Wikiizvora';
 183+ } elseif ( $word == 'Vikirječnik' ) {
 184+ $word = 'Vikirječnika';
184185 }
185186 break;
186187 case 'dativ': # dative
187 - if ( $word == 'Vikipedija' ) {
188 - $word = 'Vikipediji';
189 - } elseif ( $word == 'Vikiknjige' ) {
190 - $word = 'Vikiknjigama';
191 - } elseif ( $word == 'Vikicitati' ) {
192 - $word = 'Vikicitatima';
193 - } elseif ( $word == 'Vikivijesti' ) {
194 - $word = 'Vikivijestima';
195 - } elseif ( $word == 'Vikiizvor' ) {
196 - $word = 'Vikiizvoru';
197 - } elseif ( $word == 'Vikiriječnik' ) {
198 - $word = 'Vikiriječniku';
 188+ if ( $word == 'Wikipedia' ) {
 189+ $word = 'Wikipediji';
 190+ } elseif ( $word == 'Wikiknjige' ) {
 191+ $word = 'Wikiknjigama';
 192+ } elseif ( $word == 'Wikicitati' ) {
 193+ $word = 'Wikicitatima';
 194+ } elseif ( $word == 'Wikivijesti' ) {
 195+ $word = 'Wikivijestima';
 196+ } elseif ( $word == 'Wikiizvor' ) {
 197+ $word = 'Wikiizvoru';
 198+ } elseif ( $word == 'Vikirječnik' ) {
 199+ $word = 'Vikirječniku';
199200 }
200201 break;
201202 case 'akuzativ': # akusative
202 - if ( $word == 'Vikipedija' ) {
203 - $word = 'Vikipediju';
204 - } elseif ( $word == 'Vikiknjige' ) {
205 - $word = 'Vikiknjige';
206 - } elseif ( $word == 'Vikicitati' ) {
207 - $word = 'Vikicitate';
208 - } elseif ( $word == 'Vikivijesti' ) {
209 - $word = 'Vikivijesti';
210 - } elseif ( $word == 'Vikiizvor' ) {
211 - $word = 'Vikiizvora';
212 - } elseif ( $word == 'Vikiriječnik' ) {
213 - $word = 'Vikiriječnika';
 203+ if ( $word == 'Wikipedia' ) {
 204+ $word = 'Wikipediju';
 205+ } elseif ( $word == 'Wikiknjige' ) {
 206+ $word = 'Wikiknjige';
 207+ } elseif ( $word == 'Wikicitati' ) {
 208+ $word = 'Wikicitate';
 209+ } elseif ( $word == 'Wikivijesti' ) {
 210+ $word = 'Wikivijesti';
 211+ } elseif ( $word == 'Wikiizvor' ) {
 212+ $word = 'Wikiizvora';
 213+ } elseif ( $word == 'Vikirječnik' ) {
 214+ $word = 'Vikirječnika';
214215 }
215216 break;
216217 case 'vokativ': # vocative
217 - if ( $word == 'Vikipedija' ) {
218 - $word = 'Vikipedijo';
219 - } elseif ( $word == 'Vikiknjige' ) {
220 - $word = 'Vikiknjige';
221 - } elseif ( $word == 'Vikicitati' ) {
222 - $word = 'Vikicitati';
223 - } elseif ( $word == 'Vikivijesti' ) {
224 - $word = 'Vikivijesti';
225 - } elseif ( $word == 'Vikiizvor' ) {
226 - $word = 'Vikizivoru';
227 - } elseif ( $word == 'Vikiriječnik' ) {
228 - $word = 'Vikiriječniče';
 218+ if ( $word == 'Wikipedia' ) {
 219+ $word = 'Wikipedijo';
 220+ } elseif ( $word == 'Wikiknjige' ) {
 221+ $word = 'Wikiknjige';
 222+ } elseif ( $word == 'Wikicitati' ) {
 223+ $word = 'Wikicitati';
 224+ } elseif ( $word == 'Wikivijesti' ) {
 225+ $word = 'Wikivijesti';
 226+ } elseif ( $word == 'Wikiizvor' ) {
 227+ $word = 'Wikizivoru';
 228+ } elseif ( $word == 'Vikirječnik' ) {
 229+ $word = 'Vikirječniče';
229230 }
230231 break;
231232 case 'instrumental': # instrumental
232 - if ( $word == 'Vikipedija' ) {
233 - $word = 's Vikipediom';
234 - } elseif ( $word == 'Vikiknjige' ) {
235 - $word = 's Vikiknjigama';
236 - } elseif ( $word == 'Vikicitati' ) {
237 - $word = 's Vikicitatima';
238 - } elseif ( $word == 'Vikivijesti' ) {
239 - $word = 's Vikivijestima';
240 - } elseif ( $word == 'Vikiizvor' ) {
241 - $word = 's Vikiizvorom';
242 - } elseif ( $word == 'Vikiriječnik' ) {
243 - $word = 's Vikiriječnikom';
 233+ if ( $word == 'Wikipedia' ) {
 234+ $word = 's Wikipediom';
 235+ } elseif ( $word == 'Wikiknjige' ) {
 236+ $word = 's Wikiknjigama';
 237+ } elseif ( $word == 'Wikicitati' ) {
 238+ $word = 's Wikicitatima';
 239+ } elseif ( $word == 'Wikivijesti' ) {
 240+ $word = 's Wikivijestima';
 241+ } elseif ( $word == 'Wikiizvor' ) {
 242+ $word = 's Wikiizvorom';
 243+ } elseif ( $word == 'Vikirječnik' ) {
 244+ $word = 's Vikirječnikom';
244245 } else {
245246 $word = 's ' . $word;
246247 }
247248 break;
248249 case 'lokativ': # locative
249 - if ( $word == 'Vikipedija' ) {
 250+ if ( $word == 'Wikipedia' ) {
250251 $word = 'o Wikipediji';
251 - } elseif ( $word == 'Vikiknjige' ) {
252 - $word = 'o Vikiknjigama';
253 - } elseif ( $word == 'Vikicitati' ) {
254 - $word = 'o Vikicitatima';
255 - } elseif ( $word == 'Vikivijesti' ) {
256 - $word = 'o Vikivijestima';
257 - } elseif ( $word == 'Vikiizvor' ) {
258 - $word = 'o Vikiizvoru';
259 - } elseif ( $word == 'Vikiriječnik' ) {
260 - $word = 'o Vikiriječniku';
 252+ } elseif ( $word == 'Wikiknjige' ) {
 253+ $word = 'o Wikiknjigama';
 254+ } elseif ( $word == 'Wikicitati' ) {
 255+ $word = 'o Wikicitatima';
 256+ } elseif ( $word == 'Wikivijesti' ) {
 257+ $word = 'o Wikivijestima';
 258+ } elseif ( $word == 'Wikiizvor' ) {
 259+ $word = 'o Wikiizvoru';
 260+ } elseif ( $word == 'Vikirječnik' ) {
 261+ $word = 'o Vikirječniku';
261262 } else {
262263 $word = 'o ' . $word;
263264 }
@@ -268,4 +269,4 @@
269270
270271 }
271272
272 -?>
 273+?>
\ No newline at end of file
Index: branches/hashar/languages/MessagesId.php
@@ -14,7 +14,7 @@
1515 'tog-showtoolbar' => 'Tampilkan batang alat penyuntingan',
1616 'tog-editondblclick' => 'Sunting halaman dengan klik ganda (JavaScript)',
1717 'tog-editsection'=> 'Fungsikan penyuntingan subbab melalui pranala [sunting]',
18 -'tog-editsectiononrightclick' => 'Fungsikan penyuntingan subbab dengan klik-kanan<br/>pada judul bagian (JavaScript)',
 18+'tog-editsectiononrightclick' => 'Fungsikan penyuntingan subbab dengan klik-kanan pada judul bagian (JavaScript)',
1919 'tog-showtoc' => 'Tampilkan daftar isi<br />(untuk artikel yang mempunyai lebih dari 3 judul)',
2020 'tog-rememberpassword' => 'Ingat kata sandi pada setiap sesi',
2121 'tog-editwidth' => 'Kotak sunting memiliki lebar penuh',
@@ -550,11 +550,11 @@
551551
552552 # Preferences page
553553 #
554 -"preferences" => "Konfigurasi",
 554+"preferences" => "Preferensi",
555555 "prefsnologin" => "Belum masuk log",
556556 "prefsnologintext" => "Anda harus [[Special:Userlogin|masuk log]] untuk menetapkan preferensi Anda.",
557557
558 -"prefsreset" => "Konfigurasi telah dikembalikan ke asal dari storage.",
 558+"prefsreset" => "Preferensi telah dikembalikan ke konfigurasi baku.",
559559 "qbsettings" => "Pengaturan quickbar",
560560 "changepassword" => "Ganti kata sandi",
561561 "skin" => "Kulit",
@@ -591,7 +591,7 @@
592592 "contextchars" => "Karakter untuk konteks per baris",
593593 "stubthreshold" => "Threshold tampilan stub",
594594 "recentchangescount" => "Jumlah judul dalam perubahan terbaru",
595 -"savedprefs" => "Konfigurasi Anda telah disimpan",
 595+"savedprefs" => "Preferensi Anda telah disimpan",
596596 'timezonelegend' => 'Daerah waktu',
597597 "timezonetext" => "Masukkan perbedaan waktu (dalam jam) antara waktu setempat dengan waktu server (UTC).",
598598 "localtime" => "Waktu setempat",
@@ -796,9 +796,15 @@
797797 "statistics" => "Statistik",
798798 "sitestats" => "Statistik situs",
799799 "userstats" => "Statistik pengguna",
800 -"sitestatstext" => "Ada sejumlah <b>$1</b> halaman dalam basis data. Ini termasuk halaman \"pembicaraan\", halaman tentang {{SITENAME}}, halaman minimum \"stub\", peralihan halaman, dan halaman-halaman lain yang mungkin bukan artikel. Selain itu, ada <b>$2</b> halaman yang mungkin adalah artikel yang sah.<p> Ada sejumlah <b>$3</b> penampilan halaman, dan sejumlah <b>$4</b> penyuntingan sejak wiki ini dimulai. Ini berarti rata-rata <b>$5</b> suntingan per halaman, dan <b>$6</b> penampilan per penyuntingan.",
801 -"userstatstext" => "Ada <b>$1</b> pengguna terdaftar. <b>$2</b> diantaranya adalah administrator (lihat $3).",
 800+"sitestatstext" => "Terdapat total '''$1''' halaman dalam basis data. Ini termasuk halaman \"pembicaraan\", halaman tentang {{SITENAME}}, halaman \"rintisan\" minimum, halaman peralihan, dan halaman-halaman lain yang mungkin tidak masuk kriteria artikel. Selain itu, ada '''$2''' halaman yang mungkin termasuk artikel yang sah.
802801
 802+'''$8''' berkas telah dimuat.
 803+
 804+Ada sejumlah '''$3''' penampilan halaman, dan sejumlah '''$4''' penyuntingan sejak wiki ini dimulai. Ini berarti rata-rata '''$5''' suntingan per halaman, dan '''$6''' penampilan per penyuntingan.
 805+
 806+[http://meta.wikimedia.org/wiki/Help:Job_queue Antrian job] adalah sebanyak '''$7'''.",
 807+"userstatstext" => "Ada '''$1''' pengguna terdaftar, dimana '''$2''' (atau '''$4%''') diantaranya adalah administrator (lihat $3).",
 808+
803809 "disambiguations" => "Halaman disambiguasi",
804810 "disambiguationspage" => "Project:Pranala_ke_halaman_disambiguation",
805811 "disambiguationstext" => "Halaman-halaman berikut ini berpaut ke sebuah <i>halaman disambiguation</i>. Halaman-halaman tersebut seharusnya berpaut ke topik-topik yang tepat.<br />Satu halaman dianggap sebagai disambiguation apabila halaman tersebut disambung dari $1.<br />Pranala dari namespace lain <i>tidak</i> terdaftar di sini.",
@@ -907,20 +913,20 @@
908914 #
909915 "watchlist" => "Daftar pantauan",
910916 "watchlistsub" => "(untuk pengguna \"$1\")",
911 -"nowatchlist" => "Daftar pemantauan Anda kosong.",
 917+"nowatchlist" => "Daftar pantauan Anda kosong.",
912918 'watchlistcount' => "'''Anda memiliki $1 entri di daftar pantauan Anda, termasuk halaman diskusi/bicara.'''",
913919 'clearwatchlist' => 'Kosongkan daftar pantauan',
914920 'watchlistcleartext' => 'Apakah Anda yakin untuk menghapusnya?',
915921 'watchlistclearbutton' => 'Kosongkan daftar pantauan',
916922 'watchlistcleardone' => 'Daftar pantauan Anda telah dikosongkan. $1 entri telah dihapus.',
917923 "watchnologin" => "Belum masuk log",
918 -"watchnologintext" => "Anda harus [[Special:Userlogin|masuk log]] untuk mengubah daftar pemantauan.",
919 -"addedwatch" => "Telah ditambahkan ke daftar pemantauan",
920 -"addedwatchtext" => "Halaman \"$1\" telah ditambahkan ke [[Special:Watchlist|daftar pemantauan]]. Pada masa yang akan datang, semua perubahan pada halaman tersebut berikut halaman pembicaraannya akan didaftar di sini, dan halaman tersebut akan <b>dicetak tebal</b> dalam [[Special:Recentchanges|daftar perubahan terbaru]] supaya lebih mudah dilihat.\n\n<p>Apabila nanti Anda ingin menghapus halaman dari daftar pemantauan, klik \"Berhenti memantau\" pada batang sebelah.",
921 -"removedwatch" => "Telah dihapus dari daftar pemantauan",
922 -"removedwatchtext" => "Halaman \"$1\" telah dihapus dari daftar pemantauan.",
 924+"watchnologintext" => "Anda harus [[Special:Userlogin|masuk log]] untuk mengubah daftar pantauan.",
 925+"addedwatch" => "Telah ditambahkan ke daftar pantauan",
 926+"addedwatchtext" => "Halaman \"$1\" telah ditambahkan ke [[Special:Watchlist|daftar pantauan]]. Pada masa yang akan datang, semua perubahan pada halaman tersebut berikut halaman pembicaraannya akan didaftar di sini, dan halaman tersebut akan <b>dicetak tebal</b> dalam [[Special:Recentchanges|daftar perubahan terbaru]] supaya lebih mudah dilihat.\n\n<p>Apabila nanti Anda ingin menghapus halaman dari daftar pantauan, klik \"Berhenti memantau\" pada batang sebelah.",
 927+"removedwatch" => "Telah dihapus dari daftar pantauan",
 928+"removedwatchtext" => "Halaman \"$1\" telah dihapus dari daftar pantauan.",
923929 'watch' => 'Pantau',
924 -"watchthispage" => "Tambahkan ke daftar pemantauan",
 930+"watchthispage" => "Tambahkan ke daftar pantauan",
925931 'unwatch' => 'Berhenti memantau',
926932 "unwatchthispage" => "Berhenti memantau",
927933 "notanarticle" => "Bukan sebuah artikel",
@@ -930,15 +936,15 @@
931937 'wlheader-showupdated' => "* Halaman-halaman yang telah berubah sejak kunjungan terakhir Anda ditampilkan dengan '''huruf tebal'''",
932938 "watchmethod-recent"=> "periksa daftar perubahan terbaru terhadap halaman yang dipantau",
933939 "watchmethod-list" => "periksa halaman yang dipantau terhadap perubahan terbaru",
934 -"removechecked" => "Hapus item yang diberi tanda cek dari daftar pemantauan",
935 -"watchlistcontains" => "Daftar pemantauan Anda berisi $1 halaman.",
936 -"watcheditlist" => "Berikut ini adalah daftar halaman-halaman yang Anda pantau. Untuk menghapus halaman dari daftar pemantauan Anda, berikan tanda cek pada kotak cek di sebelah judul halaman yang ingin Anda hapus, lalu klik tombol 'hapus yang dicek' yang terletak di bagian bawah layar.",
937 -"removingchecked" => "Menghapus item-item yang diminta dari daftar pemantauan Anda...",
 940+"removechecked" => "Hapus item yang diberi tanda cek dari daftar pantauan",
 941+"watchlistcontains" => "Daftar pantauan Anda berisi $1 halaman.",
 942+"watcheditlist" => "Berikut ini adalah daftar halaman-halaman yang Anda pantau. Untuk menghapus halaman dari daftar pantauan Anda, berikan tanda cek pada kotak cek di sebelah judul halaman yang ingin Anda hapus, lalu klik tombol 'hapus yang dicek' yang terletak di bagian bawah layar.",
 943+"removingchecked" => "Menghapus item-item yang diminta dari daftar pantauan Anda...",
938944 "couldntremove" => "Tidak dapat menghapus item '$1'...",
939945 "iteminvalidname" => "Ada masalah dengan item '$1' (namanya tidak sah)...",
940946 "wlnote" => "Di bawah ini adalah daftar $1 perubahan terakhir dalam <b>$2</b> jam terakhir.",
941947 "wlshowlast" => "Tampilkan $1 jam $2 hari $3 terakhir",
942 -"wlsaved" => "Ini adalah versi tersimpan dari daftar pemantauan Anda.",
 948+"wlsaved" => "Ini adalah versi tersimpan dari daftar pantauan Anda.",
943949 'wlhideshowown' => '$1 suntingan saya.',
944950 'wlhideshowbots' => '$1 suntingan bot.',
945951
@@ -966,7 +972,7 @@
967973 Sistem notifikasi {{SITENAME}}
968974
969975 --
970 -Untuk mengubah konfigurasi daftar pantauan Anda, kunjungi
 976+Untuk mengubah preferensi daftar pantauan Anda, kunjungi
971977 {{SERVER}}{{localurl:Special:Watchlist/edit}}
972978
973979 Masukan dan bantuan lanjutan:
@@ -1141,8 +1147,8 @@
11421148 #
11431149 "lockdb" => "Kunci basis data",
11441150 "unlockdb" => "Buka kunci basis data",
1145 -"lockdbtext" => "Mengunci basis data akan menghentikan kemampuan semua pengguna dalam menyunting halaman, mengubah preferensi pengguna, menyunting daftar pemantauan mereka, dan hal-hal lain yang memerlukan perubahan terhadap basis data. Pastikan bahwa ini adalah yang ingin Anda lakukan, dan bahwa Anda akan membuka kunci basis data setelah pemeliharaan selesai.",
1146 -"unlockdbtext" => "Membuka kunci basis data akan mengembalikan kemampuan semua pengguna dalam menyunting halaman, mengubah preferensi pengguna, menyunting daftar pemantauan mereka, dan hal-hal lain yang memerlukan perubahan terhadap basis data. Pastikan bahwa ini adalah yang ingin Anda lakukan.",
 1151+"lockdbtext" => "Mengunci basis data akan menghentikan kemampuan semua pengguna dalam menyunting halaman, mengubah preferensi pengguna, menyunting daftar pantauan mereka, dan hal-hal lain yang memerlukan perubahan terhadap basis data. Pastikan bahwa ini adalah yang ingin Anda lakukan, dan bahwa Anda akan membuka kunci basis data setelah pemeliharaan selesai.",
 1152+"unlockdbtext" => "Membuka kunci basis data akan mengembalikan kemampuan semua pengguna dalam menyunting halaman, mengubah preferensi pengguna, menyunting daftar pantauan mereka, dan hal-hal lain yang memerlukan perubahan terhadap basis data. Pastikan bahwa ini adalah yang ingin Anda lakukan.",
11471153 "lockconfirm" => "Ya, saya memang ingin mengunci basis data.",
11481154 "unlockconfirm" => "Ya, saya memang ingin membuka kunci basis data.",
11491155 "lockbtn" => "Kunci basis data",
@@ -1242,7 +1248,7 @@
12431249 'tooltip-preview' => 'Pratilik perubahan Anda -- mohon gunakan ini sebelum menyimpan! [alt-p]',
12441250 'tooltip-diff' => 'Lihat perubahan yang telah Anda lakukan. [alt-v]',
12451251 'tooltip-compareselectedversions' => 'Lihat perbedaan antara dua versi halaman yang dipilih. [alt-v]',
1246 -'tooltip-watch' => 'Tambahkan halaman ini ke daftar pemantauan Anda [alt-w]',
 1252+'tooltip-watch' => 'Tambahkan halaman ini ke daftar pantauan Anda [alt-w]',
12471253
12481254 # stylesheets
12491255
@@ -1315,7 +1321,7 @@
13161322 ta[\'pt-anonuserpage\'] = new Array(\'.\',\'Halaman pengguna IP Anda\');
13171323 ta[\'pt-mytalk\'] = new Array(\'n\',\'Halaman pembicaraan saya\');
13181324 ta[\'pt-anontalk\'] = new Array(\'n\',\'Diskusi tentang suntingan dari alamat IP ini\');
1319 -ta[\'pt-preferences\'] = new Array(\'\',\'Konfigurasi saya\');
 1325+ta[\'pt-preferences\'] = new Array(\'\',\'Preferensi saya\');
13201326 ta[\'pt-watchlist\'] = new Array(\'l\',\'Daftar halaman yang Anda pantau.\');
13211327 ta[\'pt-mycontris\'] = new Array(\'y\',\'Daftar sumbangan saya\');
13221328 ta[\'pt-login\'] = new Array(\'o\',\'Anda disarankan untuk masuk log, meskipun hal itu tidak diwajibkan.\');
@@ -1330,8 +1336,8 @@
13311337 ta[\'ca-delete\'] = new Array(\'d\',\'Hapus halaman ini\');
13321338 ta[\'ca-undelete\'] = new Array(\'d\',\'Kembalikan suntingan ke halaman ini sebelum halaman ini dihapus\');
13331339 ta[\'ca-move\'] = new Array(\'m\',\'Pindahkan halaman ini\');
1334 -ta[\'ca-watch\'] = new Array(\'w\',\'Tambahkan halaman ini ke daftar pemantauan Anda\');
1335 -ta[\'ca-unwatch\'] = new Array(\'w\',\'Hapus halaman ini dari daftar pemantauan Anda\');
 1340+ta[\'ca-watch\'] = new Array(\'w\',\'Tambahkan halaman ini ke daftar pantauan Anda\');
 1341+ta[\'ca-unwatch\'] = new Array(\'w\',\'Hapus halaman ini dari daftar pantauan Anda\');
13361342 ta[\'search\'] = new Array(\'f\',\'Cari dalam wiki ini\');
13371343 ta[\'p-logo\'] = new Array(\'\',\'Halaman Utama\');
13381344 ta[\'n-mainpage\'] = new Array(\'z\',\'Kunjungi Halaman Utama\');
@@ -1414,7 +1420,286 @@
14151421 * fnumber
14161422 * focallength',
14171423
 1424+# Exif tags
 1425+'exif-imagewidth' =>'Lebar',
 1426+'exif-imagelength' =>'Tinggi',
 1427+'exif-bitspersample' =>'Bit per komponen',
 1428+'exif-compression' =>'Skema kompresi',
 1429+'exif-photometricinterpretation' =>'Komposisi piksel',
 1430+'exif-orientation' =>'Orientasi',
 1431+'exif-samplesperpixel' =>'Jumlah komponen',
 1432+'exif-planarconfiguration' =>'Pengaturan data',
 1433+'exif-ycbcrsubsampling' =>'Rasio subsampling Y ke C',
 1434+'exif-ycbcrpositioning' =>'Penempatan Y dan C',
 1435+'exif-xresolution' =>'Resolusi horizontal',
 1436+'exif-yresolution' =>'Resolusi vertical',
 1437+'exif-resolutionunit' =>'Satuan resolusi X dan Y',
 1438+'exif-stripoffsets' =>'Lokasi data gambar',
 1439+'exif-rowsperstrip' =>'Jumlah baris per strip',
 1440+'exif-stripbytecounts' =>'Byte per strip kompresi',
 1441+'exif-jpeginterchangeformat' =>'Offset ke JPEG SOI',
 1442+'exif-jpeginterchangeformatlength' =>'Byte data JPEG',
 1443+'exif-transferfunction' =>'Fungsi transfer',
 1444+'exif-whitepoint' =>'Kromatisitas titik putih',
 1445+'exif-primarychromaticities' =>'Kromatisitas warna primer',
 1446+'exif-ycbcrcoefficients' =>'Koefisien matriks transformasi ruang warna',
 1447+'exif-referenceblackwhite' =>'Nilai referensi pasangan hitam putih',
 1448+'exif-datetime' =>'Tanggal dan waktu perubahan berkas',
 1449+'exif-imagedescription' =>'Judul gambar',
 1450+'exif-make' =>'Produsen kamera',
 1451+'exif-model' =>'Model kamera',
 1452+'exif-software' =>'Perangkat lunak',
 1453+'exif-artist' =>'Pembuat',
 1454+'exif-copyright' =>'Pemilik hak cipta',
 1455+'exif-exifversion' =>'Versi Exif',
 1456+'exif-flashpixversion' =>'Dukungan versi Flashpix',
 1457+'exif-colorspace' =>'Ruang warna',
 1458+'exif-componentsconfiguration' =>'Arti tiap komponen',
 1459+'exif-compressedbitsperpixel' =>'Mode kompresi gambar',
 1460+'exif-pixelydimension' =>'Lebar gambar yang sah',
 1461+'exif-pixelxdimension' =>'Tinggi gambar yang sah',
 1462+'exif-makernote' =>'Catatan produsen',
 1463+'exif-usercomment' =>'Komentar pengguna',
 1464+'exif-relatedsoundfile' =>'Berkas audio yang berhubungan',
 1465+'exif-datetimeoriginal' =>'Tanggal dan waktu pembuatan data',
 1466+'exif-datetimedigitized' =>'Tanggal dan waktu digitalisasi',
 1467+'exif-subsectime' =>'Subdetik DateTime',
 1468+'exif-subsectimeoriginal' =>'Subdetik DateTimeOriginal',
 1469+'exif-subsectimedigitized' =>'Subdetik DateTimeDigitized',
 1470+'exif-exposuretime' =>'Waktu exposure',
 1471+'exif-exposuretime-format' => '$1 detik ($2)',
 1472+'exif-fnumber' =>'F Number',
 1473+'exif-fnumber-format' =>'f/$1',
 1474+'exif-exposureprogram' =>'Exposure Program',
 1475+'exif-spectralsensitivity' =>'Sensitivitas spektral',
 1476+'exif-isospeedratings' =>'ISO speed rating',
 1477+'exif-oecf' =>'Faktor konversi optoelektronik',
 1478+'exif-shutterspeedvalue' =>'Kecepatan shutter',
 1479+'exif-aperturevalue' =>'Aperture',
 1480+'exif-brightnessvalue' =>'Brightness',
 1481+'exif-exposurebiasvalue' =>'Exposure bias',
 1482+'exif-maxaperturevalue' =>'Maximum land aperture',
 1483+'exif-subjectdistance' =>'Jarak subyek',
 1484+'exif-meteringmode' =>'Metering mode',
 1485+'exif-lightsource' =>'Sumber cahaya',
 1486+'exif-flash' =>'Flash',
 1487+'exif-focallength' =>'Lens focal length',
 1488+'exif-focallength-format' =>'$1 mm',
 1489+'exif-subjectarea' =>'Wilayah subyek',
 1490+'exif-flashenergy' =>'Flash energy',
 1491+'exif-spatialfrequencyresponse' =>'Respons frekuensi spasial',
 1492+'exif-focalplanexresolution' =>'Resolusi focal plane X',
 1493+'exif-focalplaneyresolution' =>'Resolusi focal plane Y',
 1494+'exif-focalplaneresolutionunit' =>'Unit resolusi focal plane',
 1495+'exif-subjectlocation' =>'Lokasi subyek',
 1496+'exif-exposureindex' =>'Indeks exposure',
 1497+'exif-sensingmethod' =>'Metode sensing',
 1498+'exif-filesource' =>'Sumber berkas',
 1499+'exif-scenetype' =>'Tipe scene',
 1500+'exif-cfapattern' =>'Pola CFA',
 1501+'exif-customrendered' =>'Proses buatan gambar',
 1502+'exif-exposuremode' =>'Mode exposure',
 1503+'exif-whitebalance' =>'White Balance',
 1504+'exif-digitalzoomratio' =>'Rasio pembesaran digital',
 1505+'exif-focallengthin35mmfilm' =>'Focal length in 35 mm film',
 1506+'exif-scenecapturetype' =>'Tipe scene capture',
 1507+'exif-gaincontrol' =>'Kontrol scene',
 1508+'exif-contrast' =>'Kontras',
 1509+'exif-saturation' =>'Saturasi',
 1510+'exif-sharpness' =>'Ketajaman',
 1511+'exif-devicesettingdescription' =>'Deskripsi pengaturan alat',
 1512+'exif-subjectdistancerange' =>'Jarak subyek',
 1513+'exif-imageuniqueid' =>'ID unik gambar',
 1514+'exif-gpsversionid' =>'Versi tag GPS',
 1515+'exif-gpslatituderef' =>'Lintang Utara atau Selatan',
 1516+'exif-gpslatitude' =>'Lintang',
 1517+'exif-gpslongituderef' =>'Bujur Timur atau Barat',
 1518+'exif-gpslongitude' =>'Bujur',
 1519+'exif-gpsaltituderef' =>'Referensi ketinggian',
 1520+'exif-gpsaltitude' =>'Ketinggian',
 1521+'exif-gpstimestamp' =>'Waktu GPS (jam atom)',
 1522+'exif-gpssatellites' =>'Satelit untuk pengukuran',
 1523+'exif-gpsstatus' =>'Status penerima',
 1524+'exif-gpsmeasuremode' =>'Mode pengukuran',
 1525+'exif-gpsdop' =>'Ketepatan pengukuran',
 1526+'exif-gpsspeedref' =>'Unit kecepatan',
 1527+'exif-gpsspeed' =>'Kecepatan penerima GPS',
 1528+'exif-gpstrackref' =>'Referensi arah gerakan',
 1529+'exif-gpstrack' =>'Arah gerakan',
 1530+'exif-gpsimgdirectionref' =>'Referensi arah gambar',
 1531+'exif-gpsimgdirection' =>'Arah gambar',
 1532+'exif-gpsmapdatum' =>'Data survei geodesi',
 1533+'exif-gpsdestlatituderef' =>'Referensi lintang dari tujuan',
 1534+'exif-gpsdestlatitude' =>'Lintang tujuan',
 1535+'exif-gpsdestlongituderef' =>'Referensi bujur dari tujuan',
 1536+'exif-gpsdestlongitude' =>'Bujur tujuan',
 1537+'exif-gpsdestbearingref' =>'Referensi bearing of destination',
 1538+'exif-gpsdestbearing' =>'Bearing of destination',
 1539+'exif-gpsdestdistanceref' =>'Referensi jarak dari tujuan',
 1540+'exif-gpsdestdistance' =>'Jarak dari tujuan',
 1541+'exif-gpsprocessingmethod' =>'Nama metode proses GPS',
 1542+'exif-gpsareainformation' =>'Nama wilayah GPS',
 1543+'exif-gpsdatestamp' =>'Tanggal GPS',
 1544+'exif-gpsdifferential' =>'Koreksi diferensial GPS',
14181545
 1546+# Make & model, can be wikified in order to link to the camera and model name
 1547+
 1548+'exif-make-value' => '$1',
 1549+'exif-model-value' =>'$1',
 1550+'exif-software-value' => '$1',
 1551+
 1552+# Exif attributes
 1553+
 1554+'exif-compression-1' => 'Tak terkompresi',
 1555+'exif-compression-6' => 'JPEG',
 1556+
 1557+'exif-photometricinterpretation-2' => 'RGB',
 1558+'exif-photometricinterpretation-6' => 'YCbCr',
 1559+
 1560+'exif-orientation-1' => 'Normal', // 0th row: top; 0th column: left
 1561+'exif-orientation-2' => 'Dibalik horizontal', // 0th row: top; 0th column: right
 1562+'exif-orientation-3' => 'Diputar 180°', // 0th row: bottom; 0th column: right
 1563+'exif-orientation-4' => 'Dibalik vertikal', // 0th row: bottom; 0th column: left
 1564+'exif-orientation-5' => 'Diputar 90° CCW dan dibalik vertical', // 0th row: left; 0th column: top
 1565+'exif-orientation-6' => 'Diputar 90° CW', // 0th row: right; 0th column: top
 1566+'exif-orientation-7' => 'Diputar 90° CW dan dibalik vertical', // 0th row: right; 0th column: bottom
 1567+'exif-orientation-8' => 'Diputar 90° CCW', // 0th row: left; 0th column: bottom
 1568+
 1569+'exif-planarconfiguration-1' => 'format chunky',
 1570+'exif-planarconfiguration-2' => 'format planar',
 1571+
 1572+'exif-xyresolution-i' => '$1 dpi',
 1573+'exif-xyresolution-c' => '$1 dpc',
 1574+
 1575+'exif-colorspace-1' => 'sRGB',
 1576+'exif-colorspace-ffff.h' => 'FFFF.H',
 1577+
 1578+'exif-componentsconfiguration-0' => 'tak tersedia',
 1579+'exif-componentsconfiguration-1' => 'Y',
 1580+'exif-componentsconfiguration-2' => 'Cb',
 1581+'exif-componentsconfiguration-3' => 'Cr',
 1582+'exif-componentsconfiguration-4' => 'R',
 1583+'exif-componentsconfiguration-5' => 'G',
 1584+'exif-componentsconfiguration-6' => 'B',
 1585+
 1586+'exif-exposureprogram-0' => 'Tak terdefinisi',
 1587+'exif-exposureprogram-1' => 'Manual',
 1588+'exif-exposureprogram-2' => 'Program normal',
 1589+'exif-exposureprogram-3' => 'Prioritas aperture',
 1590+'exif-exposureprogram-4' => 'Prioritas shutter',
 1591+'exif-exposureprogram-5' => 'Program kreatif (condong ke depth of field)',
 1592+'exif-exposureprogram-6' => 'Program aksi (condong ke fast shutter speed)',
 1593+'exif-exposureprogram-7' => 'Mode potret (untuk foto closeup dengan latar belakang tak fokus)',
 1594+'exif-exposureprogram-8' => 'Mode pemandangan (untuk foto pemandangan dengan latar belakang fokus)',
 1595+
 1596+'exif-subjectdistance-value' => '$1 meter',
 1597+
 1598+'exif-meteringmode-0' => 'Tak diketahui',
 1599+'exif-meteringmode-1' => 'Average',
 1600+'exif-meteringmode-2' => 'CenterWeightedAverage',
 1601+'exif-meteringmode-3' => 'Spot',
 1602+'exif-meteringmode-4' => 'MultiSpot',
 1603+'exif-meteringmode-5' => 'Pattern',
 1604+'exif-meteringmode-6' => 'Partial',
 1605+'exif-meteringmode-255' => 'Lain-lain',
 1606+
 1607+'exif-lightsource-0' => 'Tak diketahui',
 1608+'exif-lightsource-1' => 'Daylight',
 1609+'exif-lightsource-2' => 'Fluorescent',
 1610+'exif-lightsource-3' => 'Tungsten (incandescent light)',
 1611+'exif-lightsource-4' => 'Flash',
 1612+'exif-lightsource-9' => 'Fine weather',
 1613+'exif-lightsource-10' => 'Cloudy weather',
 1614+'exif-lightsource-11' => 'Shade',
 1615+'exif-lightsource-12' => 'Daylight fluorescent (D 5700 – 7100K)',
 1616+'exif-lightsource-13' => 'Day white fluorescent (N 4600 – 5400K)',
 1617+'exif-lightsource-14' => 'Cool white fluorescent (W 3900 – 4500K)',
 1618+'exif-lightsource-15' => 'White fluorescent (WW 3200 – 3700K)',
 1619+'exif-lightsource-17' => 'Standard light A',
 1620+'exif-lightsource-18' => 'Standard light B',
 1621+'exif-lightsource-19' => 'Standard light C',
 1622+'exif-lightsource-20' => 'D55',
 1623+'exif-lightsource-21' => 'D65',
 1624+'exif-lightsource-22' => 'D75',
 1625+'exif-lightsource-23' => 'D50',
 1626+'exif-lightsource-24' => 'ISO studio tungsten',
 1627+'exif-lightsource-255' => 'Sumber cahaya lain',
 1628+
 1629+'exif-focalplaneresolutionunit-2' => 'inci',
 1630+
 1631+'exif-sensingmethod-1' => 'Tak terdefinisi',
 1632+'exif-sensingmethod-2' => 'One-chip color area sensor',
 1633+'exif-sensingmethod-3' => 'Two-chip color area sensor',
 1634+'exif-sensingmethod-4' => 'Three-chip color area sensor',
 1635+'exif-sensingmethod-5' => 'Color sequential area sensor',
 1636+'exif-sensingmethod-7' => 'Trilinear sensor',
 1637+'exif-sensingmethod-8' => 'Color sequential linear sensor',
 1638+
 1639+'exif-filesource-3' => 'DSC',
 1640+
 1641+'exif-scenetype-1' => 'Gambar foto langsung',
 1642+
 1643+'exif-customrendered-0' => 'Proses normal',
 1644+'exif-customrendered-1' => 'Proses kustom',
 1645+
 1646+'exif-exposuremode-0' => 'Exposure otomatis',
 1647+'exif-exposuremode-1' => 'Exposure manual',
 1648+'exif-exposuremode-2' => 'Bracket otomatis',
 1649+
 1650+'exif-whitebalance-0' => 'Auto white balance',
 1651+'exif-whitebalance-1' => 'Manual white balance',
 1652+
 1653+'exif-scenecapturetype-0' => 'Standar',
 1654+'exif-scenecapturetype-1' => 'Melebar',
 1655+'exif-scenecapturetype-2' => 'Potret',
 1656+'exif-scenecapturetype-3' => 'Scene malam',
 1657+
 1658+'exif-gaincontrol-0' => 'Tak ada',
 1659+'exif-gaincontrol-1' => 'Low gain up',
 1660+'exif-gaincontrol-2' => 'High gain up',
 1661+'exif-gaincontrol-3' => 'Low gain down',
 1662+'exif-gaincontrol-4' => 'High gain down',
 1663+
 1664+'exif-contrast-0' => 'Normal',
 1665+'exif-contrast-1' => 'Lembut',
 1666+'exif-contrast-2' => 'Keras',
 1667+
 1668+'exif-saturation-0' => 'Normal',
 1669+'exif-saturation-1' => 'Saturasi rendah',
 1670+'exif-saturation-2' => 'Saturasi tinggi',
 1671+
 1672+'exif-sharpness-0' => 'Normal',
 1673+'exif-sharpness-1' => 'Lembut',
 1674+'exif-sharpness-2' => 'Keras',
 1675+
 1676+'exif-subjectdistancerange-0' => 'Tak diketahui',
 1677+'exif-subjectdistancerange-1' => 'Makro',
 1678+'exif-subjectdistancerange-2' => 'Close view',
 1679+'exif-subjectdistancerange-3' => 'Distant view',
 1680+
 1681+// Pseudotags used for GPSLatitudeRef and GPSDestLatitudeRef
 1682+'exif-gpslatitude-n' => 'Lintang utara',
 1683+'exif-gpslatitude-s' => 'Lintang selatan',
 1684+
 1685+// Pseudotags used for GPSLongitudeRef and GPSDestLongitudeRef
 1686+'exif-gpslongitude-e' => 'Bujur timur',
 1687+'exif-gpslongitude-w' => 'Bujur barat',
 1688+
 1689+'exif-gpsstatus-a' => 'Pengukuran sedang berlangsung',
 1690+'exif-gpsstatus-v' => 'Interoperabilitas pengukuran',
 1691+
 1692+'exif-gpsmeasuremode-2' => 'Pengukuran 2-dimensi',
 1693+'exif-gpsmeasuremode-3' => 'Pengukuran 3-dimensi',
 1694+
 1695+// Pseudotags used for GPSSpeedRef and GPSDestDistanceRef
 1696+'exif-gpsspeed-k' => 'Kilometer per jam',
 1697+'exif-gpsspeed-m' => 'Mil per jam',
 1698+'exif-gpsspeed-n' => 'Knot',
 1699+
 1700+// Pseudotags used for GPSTrackRef, GPSImgDirectionRef and GPSDestBearingRef
 1701+'exif-gpsdirection-t' => 'Arah sejati',
 1702+'exif-gpsdirection-m' => 'Arah magnetis',
 1703+
14191704 # external editor support
14201705 'edit-externally' => 'Sunting berkas ini dengan aplikasi luar',
14211706 'edit-externally-help' => 'Lihat [http://meta.wikimedia.org/wiki/Help:External_editors instruksi pengaturan] untuk informasi lebih lanjut.',
Index: branches/hashar/maintenance/storage/checkStorage.php
@@ -0,0 +1,468 @@
 2+<?php
 3+
 4+/**
 5+ * Fsck for MediaWiki
 6+ */
 7+
 8+define( 'CONCAT_HEADER', 'O:27:"concatenatedgziphistoryblob"' );
 9+
 10+if ( !defined( 'MEDIAWIKI' ) ) {
 11+ require_once( dirname(__FILE__) . '/../commandLine.inc' );
 12+ require_once( 'ExternalStore.php' );
 13+ require_once( 'ExternalStoreDB.php' );
 14+ require_once( 'SpecialImport.php' );
 15+
 16+ $cs = new CheckStorage;
 17+ $fix = isset( $options['fix'] );
 18+ if ( isset( $args[0] ) ) {
 19+ $xml = $args[0];
 20+ } else {
 21+ $xml = false;
 22+ }
 23+ $cs->check( $fix, $xml );
 24+}
 25+
 26+
 27+//----------------------------------------------------------------------------------
 28+
 29+class CheckStorage
 30+{
 31+ var $oldIdMap, $errors;
 32+ var $dbStore = null;
 33+
 34+ var $errorDescriptions = array(
 35+ 'restore text' => 'Damaged text, need to be restored from a backup',
 36+ 'restore revision' => 'Damaged revision row, need to be restored from a backup',
 37+ 'unfixable' => 'Unexpected errors with no automated fixing method',
 38+ 'fixed' => 'Errors already fixed',
 39+ 'fixable' => 'Errors which would already be fixed if --fix was specified',
 40+ );
 41+
 42+ function check( $fix = false, $xml = '' ) {
 43+ $fname = 'checkStorage';
 44+ $dbr =& wfGetDB( DB_SLAVE );
 45+ if ( $fix ) {
 46+ $dbw =& wfGetDB( DB_MASTER );
 47+ print "Checking, will fix errors if possible...\n";
 48+ } else {
 49+ print "Checking...\n";
 50+ }
 51+ $maxRevId = $dbr->selectField( 'revision', 'MAX(rev_id)', false, $fname );
 52+ $chunkSize = 1000;
 53+ $flagStats = array();
 54+ $objectStats = array();
 55+ $knownFlags = array( 'external', 'gzip', 'object', 'utf-8' );
 56+ $this->errors = array(
 57+ 'restore text' => array(),
 58+ 'restore revision' => array(),
 59+ 'unfixable' => array(),
 60+ 'fixed' => array(),
 61+ 'fixable' => array(),
 62+ );
 63+
 64+ for ( $chunkStart = 1 ; $chunkStart < $maxRevId; $chunkStart += $chunkSize ) {
 65+ $chunkEnd = $chunkStart + $chunkSize - 1;
 66+ //print "$chunkStart of $maxRevId\n";
 67+
 68+ // Fetch revision rows
 69+ $this->oldIdMap = array();
 70+ $dbr->ping();
 71+ $res = $dbr->select( 'revision', array( 'rev_id', 'rev_text_id' ),
 72+ array( "rev_id BETWEEN $chunkStart AND $chunkEnd" ), $fname );
 73+ while ( $row = $dbr->fetchObject( $res ) ) {
 74+ $this->oldIdMap[$row->rev_id] = $row->rev_text_id;
 75+ }
 76+ $dbr->freeResult( $res );
 77+
 78+ if ( !count( $this->oldIdMap ) ) {
 79+ continue;
 80+ }
 81+
 82+ // Fetch old_flags
 83+ $missingTextRows = array_flip( $this->oldIdMap );
 84+ $externalRevs = array();
 85+ $objectRevs = array();
 86+ $res = $dbr->select( 'text', array( 'old_id', 'old_flags' ),
 87+ 'old_id IN (' . implode( ',', $this->oldIdMap ) . ')', $fname );
 88+ while ( $row = $dbr->fetchObject( $res ) ) {
 89+ $flags = $row->old_flags;
 90+ $id = $row->old_id;
 91+
 92+ // Create flagStats row if it doesn't exist
 93+ $flagStats = $flagStats + array( $flags => 0 );
 94+ // Increment counter
 95+ $flagStats[$flags]++;
 96+
 97+ // Not missing
 98+ unset( $missingTextRows[$row->old_id] );
 99+
 100+ // Check for external or object
 101+ if ( $flags == '' ) {
 102+ $flagArray = array();
 103+ } else {
 104+ $flagArray = explode( ',', $flags );
 105+ }
 106+ if ( in_array( 'external', $flagArray ) ) {
 107+ $externalRevs[] = $id;
 108+ } elseif ( in_array( 'object', $flagArray ) ) {
 109+ $objectRevs[] = $id;
 110+ }
 111+
 112+ // Check for unrecognised flags
 113+ if ( $flags == '0' ) {
 114+ // This is a known bug from 2004
 115+ // It's safe to just erase the old_flags field
 116+ if ( $fix ) {
 117+ $this->error( 'fixed', "Warning: old_flags set to 0", $id );
 118+ $dbw->ping();
 119+ $dbw->update( 'text', array( 'old_flags' => '' ),
 120+ array( 'old_id' => $id ), $fname );
 121+ echo "Fixed\n";
 122+ } else {
 123+ $this->error( 'fixable', "Warning: old_flags set to 0", $id );
 124+ }
 125+ } elseif ( count( array_diff( $flagArray, $knownFlags ) ) ) {
 126+ $this->error( 'unfixable', "Error: invalid flags field \"$flags\"", $id );
 127+ }
 128+ }
 129+ $dbr->freeResult( $res );
 130+
 131+ // Output errors for any missing text rows
 132+ foreach ( $missingTextRows as $oldId => $revId ) {
 133+ $this->error( 'restore revision', "Error: missing text row", $oldId );
 134+ }
 135+
 136+ // Verify external revisions
 137+ $externalConcatBlobs = array();
 138+ $externalNormalBlobs = array();
 139+ if ( count( $externalRevs ) ) {
 140+ $res = $dbr->select( 'text', array( 'old_id', 'old_flags', 'old_text' ),
 141+ array( 'old_id IN (' . implode( ',', $externalRevs ) . ')' ), $fname );
 142+ while ( $row = $dbr->fetchObject( $res ) ) {
 143+ $urlParts = explode( '://', $row->old_text, 2 );
 144+ if ( count( $urlParts ) !== 2 || $urlParts[1] == '' ) {
 145+ $this->error( 'restore text', "Error: invalid URL \"{$row->old_text}\"", $row->old_id );
 146+ continue;
 147+ }
 148+ list( $proto, $path ) = $urlParts;
 149+ if ( $proto != 'DB' ) {
 150+ $this->error( 'restore text', "Error: invalid external protocol \"$proto\"", $row->old_id );
 151+ continue;
 152+ }
 153+ $path = explode( '/', $row->old_text );
 154+ $cluster = $path[2];
 155+ $id = $path[3];
 156+ if ( isset( $path[4] ) ) {
 157+ $externalConcatBlobs[$cluster][$id][] = $row->old_id;
 158+ } else {
 159+ $externalNormalBlobs[$cluster][$id][] = $row->old_id;
 160+ }
 161+ }
 162+ $dbr->freeResult( $res );
 163+ }
 164+
 165+ // Check external concat blobs for the right header
 166+ $this->checkExternalConcatBlobs( $externalConcatBlobs );
 167+
 168+ // Check external normal blobs for existence
 169+ if ( count( $externalNormalBlobs ) ) {
 170+ if ( is_null( $this->dbStore ) ) {
 171+ $this->dbStore = new ExternalStoreDB;
 172+ }
 173+ foreach ( $externalConcatBlobs as $cluster => $xBlobIds ) {
 174+ $blobIds = array_keys( $xBlobIds );
 175+ $extDb =& $this->dbStore->getSlave( $cluster );
 176+ $blobsTable = $this->dbStore->getTable( $extDb );
 177+ $res = $extDb->select( $blobsTable,
 178+ array( 'blob_id' ),
 179+ array( 'blob_id IN( ' . implode( ',', $blobIds ) . ')' ), $fname );
 180+ while ( $row = $extDb->fetchObject( $res ) ) {
 181+ unset( $xBlobIds[$row->blob_id] );
 182+ }
 183+ $extDb->freeResult( $res );
 184+ // Print errors for missing blobs rows
 185+ foreach ( $xBlobIds as $blobId => $oldId ) {
 186+ $this->error( 'restore text', "Error: missing target $blobId for one-part ES URL", $oldId );
 187+ }
 188+ }
 189+ }
 190+
 191+ // Check local objects
 192+ $dbr->ping();
 193+ $concatBlobs = array();
 194+ $curIds = array();
 195+ if ( count( $objectRevs ) ) {
 196+ $headerLength = 300;
 197+ $res = $dbr->select( 'text', array( 'old_id', 'old_flags', "LEFT(old_text, $headerLength) AS header" ),
 198+ array( 'old_id IN (' . implode( ',', $objectRevs ) . ')' ), $fname );
 199+ while ( $row = $dbr->fetchObject( $res ) ) {
 200+ $oldId = $row->old_id;
 201+ if ( !preg_match( '/^O:(\d+):"(\w+)"/', $row->header, $matches ) ) {
 202+ $this->error( 'restore text', "Error: invalid object header", $oldId );
 203+ continue;
 204+ }
 205+
 206+ $className = strtolower( $matches[2] );
 207+ if ( strlen( $className ) != $matches[1] ) {
 208+ $this->error( 'restore text', "Error: invalid object header, wrong class name length", $oldId );
 209+ continue;
 210+ }
 211+
 212+ $objectStats = $objectStats + array( $className => 0 );
 213+ $objectStats[$className]++;
 214+
 215+ switch ( $className ) {
 216+ case 'concatenatedgziphistoryblob':
 217+ // Good
 218+ break;
 219+ case 'historyblobstub':
 220+ case 'historyblobcurstub':
 221+ if ( strlen( $row->header ) == $headerLength ) {
 222+ $this->error( 'unfixable', "Error: overlong stub header", $oldId );
 223+ continue;
 224+ }
 225+ $stubObj = unserialize( $row->header );
 226+ if ( !is_object( $stubObj ) ) {
 227+ $this->error( 'restore text', "Error: unable to unserialize stub object", $oldId );
 228+ continue;
 229+ }
 230+ if ( $className == 'historyblobstub' ) {
 231+ $concatBlobs[$stubObj->mOldId][] = $oldId;
 232+ } else {
 233+ $curIds[$stubObj->mCurId][] = $oldId;
 234+ }
 235+ break;
 236+ default:
 237+ $this->error( 'unfixable', "Error: unrecognised object class \"$className\"", $oldId );
 238+ }
 239+ }
 240+ $dbr->freeResult( $res );
 241+ }
 242+
 243+ // Check local concat blob validity
 244+ $externalConcatBlobs = array();
 245+ if ( count( $concatBlobs ) ) {
 246+ $headerLength = 300;
 247+ $res = $dbr->select( 'text', array( 'old_id', 'old_flags', "LEFT(old_text, $headerLength) AS header" ),
 248+ array( 'old_id IN (' . implode( ',', array_keys( $concatBlobs ) ) . ')' ), $fname );
 249+ while ( $row = $dbr->fetchObject( $res ) ) {
 250+ $flags = explode( ',', $row->old_flags );
 251+ if ( in_array( 'external', $flags ) ) {
 252+ // Concat blob is in external storage?
 253+ if ( in_array( 'object', $flags ) ) {
 254+ $urlParts = explode( '/', $row->header );
 255+ if ( $urlParts[0] != 'DB:' ) {
 256+ $this->error( 'unfixable', "Error: unrecognised external storage type \"{$urlParts[0]}", $row->old_id );
 257+ } else {
 258+ $cluster = $urlParts[2];
 259+ $id = $urlParts[3];
 260+ if ( !isset( $externalConcatBlobs[$cluster][$id] ) ) {
 261+ $externalConcatBlobs[$cluster][$id] = array();
 262+ }
 263+ $externalConcatBlobs[$cluster][$id] = array_merge(
 264+ $externalConcatBlobs[$cluster][$id], $concatBlobs[$row->old_id]
 265+ );
 266+ }
 267+ } else {
 268+ $this->error( 'unfixable', "Error: invalid flags \"{$row->old_flags}\" on concat bulk row {$row->old_id}",
 269+ $concatBlobs[$row->old_id] );
 270+ }
 271+ } elseif ( strcasecmp( substr( $row->header, 0, strlen( CONCAT_HEADER ) ), CONCAT_HEADER ) ) {
 272+ $this->error( 'restore text', "Error: Incorrect object header for concat bulk row {$row->old_id}",
 273+ $concatBlobs[$row->old_id] );
 274+ } # else good
 275+
 276+ unset( $concatBlobs[$row->old_id] );
 277+ }
 278+ $dbr->freeResult( $res );
 279+ }
 280+
 281+ // Check targets of unresolved stubs
 282+ $this->checkExternalConcatBlobs( $externalConcatBlobs );
 283+
 284+ // next chunk
 285+ }
 286+
 287+ print "\n\nErrors:\n";
 288+ foreach( $this->errors as $name => $errors ) {
 289+ if ( count( $errors ) ) {
 290+ $description = $this->errorDescriptions[$name];
 291+ echo "$description: " . implode( ',', array_keys( $errors ) ) . "\n";
 292+ }
 293+ }
 294+
 295+ if ( count( $this->errors['restore text'] ) && $fix ) {
 296+ if ( (string)$xml !== '' ) {
 297+ $this->restoreText( array_keys( $this->errors['restore text'] ), $xml );
 298+ } else {
 299+ echo "Can't fix text, no XML backup specified\n";
 300+ }
 301+ }
 302+
 303+ print "\nFlag statistics:\n";
 304+ $total = array_sum( $flagStats );
 305+ foreach ( $flagStats as $flag => $count ) {
 306+ printf( "%-30s %10d %5.2f%%\n", $flag, $count, $count / $total * 100 );
 307+ }
 308+ print "\nLocal object statistics:\n";
 309+ $total = array_sum( $objectStats );
 310+ foreach ( $objectStats as $className => $count ) {
 311+ printf( "%-30s %10d %5.2f%%\n", $className, $count, $count / $total * 100 );
 312+ }
 313+ }
 314+
 315+
 316+ function error( $type, $msg, $ids ) {
 317+ if ( is_array( $ids ) && count( $ids ) == 1 ) {
 318+ $ids = reset( $ids );
 319+ }
 320+ if ( is_array( $ids ) ) {
 321+ $revIds = array();
 322+ foreach ( $ids as $id ) {
 323+ $revIds = array_merge( $revIds, array_keys( $this->oldIdMap, $id ) );
 324+ }
 325+ print "$msg in text rows " . implode( ', ', $ids ) .
 326+ ", revisions " . implode( ', ', $revIds ) . "\n";
 327+ } else {
 328+ $id = $ids;
 329+ $revIds = array_keys( $this->oldIdMap, $id );
 330+ if ( count( $revIds ) == 1 ) {
 331+ print "$msg in old_id $id, rev_id {$revIds[0]}\n";
 332+ } else {
 333+ print "$msg in old_id $id, revisions " . implode( ', ', $revIds ) . "\n";
 334+ }
 335+ }
 336+ $this->errors[$type] = $this->errors[$type] + array_flip( $revIds );
 337+ }
 338+
 339+ function checkExternalConcatBlobs( $externalConcatBlobs ) {
 340+ $fname = 'CheckStorage::checkExternalConcatBlobs';
 341+ if ( !count( $externalConcatBlobs ) ) {
 342+ return;
 343+ }
 344+
 345+ if ( is_null( $this->dbStore ) ) {
 346+ $this->dbStore = new ExternalStoreDB;
 347+ }
 348+
 349+ foreach ( $externalConcatBlobs as $cluster => $oldIds ) {
 350+ $blobIds = array_keys( $oldIds );
 351+ $extDb =& $this->dbStore->getSlave( $cluster );
 352+ $blobsTable = $this->dbStore->getTable( $extDb );
 353+ $headerLength = strlen( CONCAT_HEADER );
 354+ $res = $extDb->select( $blobsTable,
 355+ array( 'blob_id', "LEFT(blob_text, $headerLength) AS header" ),
 356+ array( 'blob_id IN( ' . implode( ',', $blobIds ) . ')' ), $fname );
 357+ while ( $row = $extDb->fetchObject( $res ) ) {
 358+ if ( strcasecmp( $row->header, CONCAT_HEADER ) ) {
 359+ $this->error( 'restore text', "Error: invalid header on target $cluster/{$row->blob_id} of two-part ES URL",
 360+ $oldIds[$row->blob_id] );
 361+ }
 362+ unset( $oldIds[$row->blob_id] );
 363+
 364+ }
 365+ $extDb->freeResult( $res );
 366+
 367+ // Print errors for missing blobs rows
 368+ foreach ( $oldIds as $blobId => $oldIds ) {
 369+ $this->error( 'restore text', "Error: missing target $cluster/$blobId for two-part ES URL", $oldIds );
 370+ }
 371+ }
 372+ }
 373+
 374+ function restoreText( $revIds, $xml ) {
 375+ global $wgTmpDirectory, $wgDBname;
 376+
 377+ if ( !count( $revIds ) ) {
 378+ return;
 379+ }
 380+
 381+ print "Restoring text from XML backup...\n";
 382+
 383+ $revFileName = "$wgTmpDirectory/broken-revlist-$wgDBname";
 384+ $filteredXmlFileName = "$wgTmpDirectory/filtered-$wgDBname.xml";
 385+
 386+ // Write revision list
 387+ if ( !file_put_contents( $revFileName, implode( "\n", $revIds ) ) ) {
 388+ echo "Error writing revision list, can't restore text\n";
 389+ return;
 390+ }
 391+
 392+ // Run mwdumper
 393+ echo "Filtering XML dump...\n";
 394+ $exitStatus = 0;
 395+ passthru( 'mwdumper ' .
 396+ wfEscapeShellArg(
 397+ "--output=file:$filteredXmlFileName",
 398+ "--filter=revlist:$revFileName",
 399+ $xml
 400+ ), $exitStatus
 401+ );
 402+
 403+ if ( $exitStatus ) {
 404+ echo "mwdumper died with exit status $exitStatus\n";
 405+ return;
 406+ }
 407+
 408+ $file = fopen( $filteredXmlFileName, 'r' );
 409+ if ( !$file ) {
 410+ echo "Unable to open filtered XML file\n";
 411+ return;
 412+ }
 413+
 414+ $dbr =& wfGetDB( DB_SLAVE );
 415+ $dbw =& wfGetDB( DB_MASTER );
 416+ $dbr->ping();
 417+ $dbw->ping();
 418+
 419+ $source = new ImportStreamSource( $file );
 420+ $importer = new WikiImporter( $source );
 421+ $importer->setRevisionCallback( array( &$this, 'importRevision' ) );
 422+ $importer->doImport();
 423+ }
 424+
 425+ function importRevision( &$revision, &$importer ) {
 426+ $fname = 'CheckStorage::importRevision';
 427+
 428+ $id = $revision->getID();
 429+ $text = $revision->getText();
 430+ if ( $text === '' ) {
 431+ // This is what happens if the revision was broken at the time the
 432+ // dump was made. Unfortunately, it also happens if the revision was
 433+ // legitimately blank, so there's no way to tell the difference. To
 434+ // be safe, we'll skip it and leave it broken
 435+ $id = $id ? $id : '';
 436+ echo "Revision $id is blank in the dump, may have been broken before export\n";
 437+ return;
 438+ }
 439+
 440+ if ( !$id ) {
 441+ // No ID, can't import
 442+ echo "No id tag in revision, can't import\n";
 443+ return;
 444+ }
 445+
 446+ // Find text row again
 447+ $dbr =& wfGetDB( DB_SLAVE );
 448+ $oldId = $dbr->selectField( 'revision', 'rev_text_id', array( 'rev_id' => $id ), $fname );
 449+ if ( !$oldId ) {
 450+ echo "Missing revision row for rev_id $id\n";
 451+ return;
 452+ }
 453+
 454+ // Compress the text
 455+ $flags = Revision::compressRevisionText( $text );
 456+
 457+ // Update the text row
 458+ $dbw->update( 'text',
 459+ array( 'old_flags' => $flags, 'old_text' => $text ),
 460+ array( 'old_id' => $oldId ),
 461+ $fname, array( 'LIMIT' => 1 )
 462+ );
 463+
 464+ // Remove it from the unfixed list and add it to the fixed list
 465+ unset( $this->errors['restore text'][$id] );
 466+ $this->errors['fixed'][$id] = true;
 467+ }
 468+}
 469+?>

Status & tagging log