MediaWikiComplexMessages.php

Go to the documentation of this file.
00001 <?php
00016 abstract class ComplexMessages {
00017     const LANG_MASTER = 0;
00018     const LANG_CHAIN = 1;
00019     const LANG_CURRENT = 2;
00020 
00021     protected $language = null;
00022     protected $targetDir = null;
00023     protected $id = '__BUG__';
00024     protected $variable = '__BUG__';
00025     protected $data = array();
00026     protected $elementsInArray = true;
00027     protected $databaseMsg = '__BUG__';
00028     protected $chainable = false;
00029     protected $firstMagic = false;
00030     protected $constants = array();
00031 
00032     protected $tableAttributes = array(
00033         'class' => 'wikitable',
00034         'border' => '2',
00035         'cellpadding' => '4',
00036         'cellspacing' => '0',
00037         'style' => 'background-color: #F9F9F9; border: 1px #AAAAAA solid; border-collapse: collapse;',
00038     );
00039 
00040     public function __construct( $langCode ) {
00041         $this->language = $langCode;
00042 
00043         $language = Language::factory( $langCode );
00044         $this->targetDir = $language->getDir();
00045     }
00046 
00047     public function getTitle() {
00048         // Give grep a chance to find the usages:
00049         // translate-magic-special, translate-magic-words, translate-magic-namespace
00050         return wfMessage( 'translate-magic-' . $this->id )->text();
00051     }
00052 
00053     #
00054     # Data retrieval
00055     #
00056     protected $init = false;
00057 
00058     public function getGroups() {
00059         if ( !$this->init ) {
00060             $saved = $this->getSavedData();
00061             foreach ( $this->data as &$group ) {
00062                 $this->getData( $group, $saved );
00063             }
00064             $this->init = true;
00065         }
00066 
00067         return $this->data;
00068     }
00069 
00070     public function cleanData( $defs, $current ) {
00071         foreach ( $current as $item => $values ) {
00072             if ( !$this->elementsInArray ) {
00073                 break;
00074             }
00075 
00076             if ( !isset( $defs[$item] ) ) {
00077                 unset( $current[$item] );
00078                 continue;
00079             }
00080 
00081             foreach ( $values as $index => $value )
00082                 if ( in_array( $value, $defs[$item], true ) ) {
00083                     unset( $current[$item][$index] );
00084                 }
00085         }
00086 
00087         return $current;
00088     }
00089 
00090     public function mergeMagic( $defs, $current ) {
00091         foreach ( $current as $item => &$values ) {
00092             $newchain = $defs[$item];
00093             array_splice( $newchain, 1, 0, $values );
00094             $values = $newchain;
00095         }
00096 
00097         return $current;
00098     }
00099 
00100     public function getData( &$group, $savedData ) {
00101         $defs = $this->readVariable( $group, 'en' );
00102         $code = $this->language;
00103 
00104         $current = $savedData + $this->readVariable( $group, $code );
00105 
00106         // Clean up duplicates to definitions from saved data
00107         $current = $this->cleanData( $defs, $current );
00108 
00109         $chain = $current;
00110         if ( $this->chainable ) {
00111             foreach ( Language::getFallbacksFor( $code ) as $code ) {
00112                 $fbdata = $this->readVariable( $group, $code );
00113                 if ( $this->firstMagic ) {
00114                     $fbdata = $this->cleanData( $defs, $fbdata );
00115                 }
00116 
00117                 $chain = array_merge_recursive( $chain, $fbdata );
00118             }
00119         }
00120 
00121         if ( $this->firstMagic ) {
00122             $chain = $this->mergeMagic( $defs, $chain );
00123         }
00124 
00125         $data = $group['data'] = array( $defs, $chain, $current );
00126 
00127         return $data;
00128     }
00129 
00135     public function loadFromRequest( WebRequest $request ) {
00136         $saved = $this->parse( $this->formatForSave( $request ) );
00137         foreach ( $this->data as &$group ) {
00138             $this->getData( $group, $saved );
00139         }
00140     }
00141 
00146     protected function getSavedData() {
00147         $data = TranslateUtils::getMessageContent( $this->databaseMsg, $this->language );
00148 
00149         if ( !$data ) {
00150             return array();
00151         } else {
00152             return $this->parse( $data );
00153         }
00154     }
00155 
00156     protected function parse( $data ) {
00157         $lines = array_map( 'trim', explode( "\n", $data ) );
00158         $array = array();
00159         foreach ( $lines as $line ) {
00160             if ( $line === '' || $line[0] === '#' || $line[0] === '<' ) {
00161                 continue;
00162             }
00163 
00164             if ( strpos( $line, '=' ) === false ) {
00165                 continue;
00166             }
00167 
00168             list( $name, $values ) = array_map( 'trim', explode( '=', $line, 2 ) );
00169             if ( $name === '' || $values === '' ) {
00170                 continue;
00171             }
00172 
00173             $data = array_map( 'trim', explode( ',', $values ) );
00174             $array[$name] = $data;
00175         }
00176 
00177         return $array;
00178     }
00179 
00185     protected function getIterator( $group ) {
00186         $groups = $this->getGroups();
00187 
00188         return array_keys( $groups[$group]['data'][self::LANG_MASTER] );
00189     }
00190 
00191     protected function val( $group, $type, $key ) {
00192         $array = $this->getGroups();
00193         wfSuppressWarnings();
00194         $subarray = $array[$group]['data'][$type][$key];
00195         wfRestoreWarnings();
00196         if ( $this->elementsInArray ) {
00197             if ( !$subarray || !count( $subarray ) ) {
00198                 return array();
00199             }
00200         } else {
00201             if ( !$subarray ) {
00202                 return array();
00203             }
00204         }
00205 
00206         if ( !is_array( $subarray ) ) {
00207             $subarray = array( $subarray );
00208         }
00209 
00210         return $subarray;
00211     }
00212 
00218     protected function readVariable( $group, $code ) {
00219         $file = $group['file'];
00220         if ( !$group['code'] ) {
00221             $file = str_replace( '%CODE%', str_replace( '-', '_', ucfirst( $code ) ), $file );
00222         }
00223 
00224         ${$group['var']} = array(); # Initialize
00225         if ( file_exists( $file ) ) {
00226             require $file; # Include
00227         }
00228 
00229         if ( $group['code'] ) {
00230             wfSuppressWarnings();
00231             $data = (array) ${$group['var']} [$code];
00232             wfRestoreWarnings();
00233         } else {
00234             $data = ${$group['var']};
00235         }
00236 
00237         return self::arrayMapRecursive( 'strval', $data );
00238     }
00239 
00240     public static function arrayMapRecursive( $callback, $data ) {
00241         foreach ( $data as $index => $values ) {
00242             if ( is_array( $values ) ) {
00243                 $data[$index] = self::arrayMapRecursive( $callback, $values );
00244             } else {
00245                 $data[$index] = call_user_func( $callback, $values );
00246             }
00247         }
00248 
00249         return $data;
00250     }
00251 
00252     #
00253     # /Data retrieval
00254     #
00255 
00256     #
00257     # Output
00258     #
00259     public function header( $title ) {
00260         $colspan = array( 'colspan' => 3 );
00261         $header = Xml::element( 'th', $colspan, $this->getTitle() . ' - ' . $title );
00262         $subheading[] = '<th>' . wfMessage( 'translate-magic-cm-original' )->escaped() . '</th>';
00263         $subheading[] = '<th>' . wfMessage( 'translate-magic-cm-current' )->escaped() . '</th>';
00264         $subheading[] = '<th>' . wfMessage( 'translate-magic-cm-to-be' )->escaped() . '</th>';
00265 
00266         return '<tr>' . $header . '</tr>' .
00267             '<tr>' . implode( "\n", $subheading ) . '</tr>';
00268     }
00269 
00270     public function output() {
00271         $colspan = array( 'colspan' => 3 );
00272 
00273         $s = Xml::openElement( 'table', $this->tableAttributes );
00274 
00275         foreach ( array_keys( $this->data ) as $group ) {
00276             $s .= $this->header( $this->data[$group]['label'] );
00277 
00278             foreach ( $this->getIterator( $group ) as $key ) {
00279                 $rowContents = '';
00280 
00281                 $value = $this->val( $group, self::LANG_MASTER, $key );
00282                 if ( $this->firstMagic ) {
00283                     array_shift( $value );
00284                 }
00285 
00286                 $value = array_map( 'htmlspecialchars', $value );
00287                 // Force ltr direction. The source is pretty much guaranteed to be English-based.
00288                 $rowContents .= '<td dir="ltr">' . $this->formatElement( $value ) . '</td>';
00289 
00290                 $value = $this->val( $group, self::LANG_CHAIN, $key );
00291                 if ( $this->firstMagic ) {
00292                     array_shift( $value );
00293                 }
00294 
00295                 // Apply bidi-isolation to each value.
00296                 // The values can both RTL and LTR and mixing them in a comma list
00297                 // can mix things up.
00298                 foreach ( $value as &$currentTranslation ) {
00299                     $currentTranslation = Xml::element( 'bdi', null, $currentTranslation );
00300                 }
00301                 $value = $this->highlight( $key, $value );
00302                 $rowContents .= '<td>' . $this->formatElement( $value ) . '</td>';
00303 
00304                 $value = $this->val( $group, self::LANG_CURRENT, $key );
00305                 $rowContents .= '<td>';
00306                 $rowContents .= $this->editElement( $key, $this->formatElement( $value ) );
00307                 $rowContents .= '</td>';
00308 
00309                 $s .= Xml::tags( 'tr', array( 'id' => "mw-sp-magic-$key" ), $rowContents );
00310             }
00311         }
00312 
00313         $context = RequestContext::getMain();
00314 
00315         if ( $context->getUser()->isAllowed( 'translate' ) ) {
00316             $s .= '<tr>' . Xml::tags( 'td', $colspan, $this->getButtons() ) . '<tr>';
00317         }
00318 
00319         $s .= Xml::closeElement( 'table' );
00320 
00321         return Xml::tags(
00322             'form',
00323             array(
00324                 'method' => 'post',
00325                 'action' => $context->getRequest()->getRequestURL()
00326             ),
00327             $s
00328         );
00329     }
00330 
00331     public function getButtons() {
00332         return Xml::inputLabel(
00333             wfMessage( 'translate-magic-cm-comment' )->text(),
00334             'comment',
00335             'sp-translate-magic-comment'
00336         ) .
00337         Xml::submitButton(
00338             wfMessage( 'translate-magic-cm-save' )->text(),
00339             array( 'name' => 'savetodb' )
00340         );
00341     }
00342 
00343     public function formatElement( $element ) {
00344         if ( !count( $element ) ) {
00345             return '';
00346         }
00347 
00348         if ( is_array( $element ) ) {
00349             $element = array_map( 'trim', $element );
00350             $element = implode( ', ', $element );
00351         }
00352 
00353         return trim( $element );
00354     }
00355 
00356     function getKeyForEdit( $key ) {
00357         return Sanitizer::escapeId( 'sp-translate-magic-cm-' . $this->id . $key );
00358     }
00359 
00360     public function editElement( $key, $contents ) {
00361         return Xml::input( $this->getKeyForEdit( $key ), 40, $contents, array(
00362             'lang' => $this->language,
00363             'dir' => $this->targetDir,
00364         ) );
00365     }
00366 
00367     #
00368     # /Output
00369     #
00370 
00371     #
00372     # Save to database
00373     #
00374 
00375     function getKeyForSave() {
00376         return $this->databaseMsg . '/' . $this->language;
00377     }
00378 
00383     function formatForSave( WebRequest $request ) {
00384         $text = '';
00385 
00386         // Do not replace spaces by underscores for magic words. See bug 46613
00387         $replaceSpace = $request->getVal( 'module' ) !== 'magic';
00388 
00389         foreach ( array_keys( $this->data ) as $group ) {
00390             foreach ( $this->getIterator( $group ) as $key ) {
00391                 $data = $request->getText( $this->getKeyForEdit( $key ) );
00392                 // Make a nice array out of the submit with trimmed values.
00393                 $data = array_map( 'trim', explode( ',', $data ) );
00394 
00395                 if ( $replaceSpace ) {
00396                     // Normalise: Replace spaces with underscores.
00397                     $data = str_replace( ' ', '_', $data );
00398                 }
00399 
00400                 // Create final format.
00401                 $data = implode( ', ', $data );
00402                 if ( $data !== '' ) {
00403                     $text .= "$key = $data\n";
00404                 }
00405             }
00406         }
00407 
00408         return $text;
00409     }
00410 
00411     public function save( $request ) {
00412         $title = Title::newFromText( 'MediaWiki:' . $this->getKeyForSave() );
00413         $article = new Article( $title );
00414 
00415         $data = "# DO NOT EDIT THIS PAGE DIRECTLY! Use [[Special:AdvancedTranslate]].\n<pre>\n" .
00416             $this->formatForSave( $request ) . "\n</pre>";
00417 
00418         $comment = $request->getText(
00419             'comment',
00420             wfMessage( 'translate-magic-cm-updatedusing' )->inContentLanguage()->text()
00421         );
00422         $status = $article->doEdit( $data, $comment, 0 );
00423 
00424         if ( $status === false || ( is_object( $status ) && !$status->isOK() ) ) {
00425             throw new MWException( wfMessage( 'translate-magic-cm-savefailed' )->text() );
00426         }
00427 
00428         /* Reset outdated array */
00429         $this->init = false;
00430     }
00431 
00432     #
00433     # !Save to database
00434     #
00435 
00436     #
00437     # Export
00438     #
00439     public function validate( &$errors = array(), $filter = false ) {
00440         $used = array();
00441         foreach ( array_keys( $this->data ) as $group ) {
00442             if ( $filter !== false && !in_array( $group, (array)$filter, true ) ) {
00443                 continue;
00444             }
00445 
00446             $this->validateEach( $errors, $group, $used );
00447         }
00448     }
00449 
00450     protected function validateEach( &$errors = array(), $group, &$used ) {
00451         foreach ( $this->getIterator( $group ) as $key ) {
00452             $values = $this->val( $group, self::LANG_CURRENT, $key );
00453             $link = Xml::element( 'a', array( 'href' => "#mw-sp-magic-$key" ), $key );
00454 
00455             if ( count( $values ) !== count( array_filter( $values ) ) ) {
00456                 $errors[] = "There is empty value in $link.";
00457             }
00458 
00459             foreach ( $values as $v ) {
00460                 if ( isset( $used[$v] ) ) {
00461                     $otherkey = $used[$v];
00462                     $first = Xml::element(
00463                         'a',
00464                         array( 'href' => "#mw-sp-magic-$otherkey" ),
00465                         $otherkey
00466                     );
00467                     $errors[] = "Translation <strong>$v</strong> is used more than once " .
00468                         "for $first and $link.";
00469                 } else {
00470                     $used[$v] = $key;
00471                 }
00472             }
00473         }
00474     }
00475 
00476     public function export( $filter = false ) {
00477         $text = '';
00478         $errors = array();
00479         $this->validate( $errors, $filter );
00480         foreach ( $errors as $_ ) $text .= "#!!# $_\n";
00481 
00482         foreach ( $this->getGroups() as $group => $data ) {
00483             if ( $filter !== false && !in_array( $group, (array)$filter, true ) ) {
00484                 continue;
00485             }
00486 
00487             $text .= $this->exportEach( $group, $data );
00488         }
00489 
00490         return $text;
00491     }
00492 
00493     protected function exportEach( $group, $data ) {
00494         $var = $data['var'];
00495         $items = $data['data'];
00496 
00497         $extra = $data['code'] ? "['{$this->language}']" : '';
00498 
00499         $out = '';
00500 
00501         $indexKeys = array();
00502         foreach ( array_keys( $items[self::LANG_MASTER] ) as $key ) {
00503             $indexKeys[$key] = isset( $this->constants[$key] ) ?
00504                 $this->constants[$key] :
00505                 "'$key'";
00506         }
00507 
00508         $padTo = max( array_map( 'strlen', $indexKeys ) ) + 3;
00509 
00510         foreach ( $this->getIterator( $group ) as $key ) {
00511             $temp = "\t{$indexKeys[$key]}";
00512 
00513             while ( strlen( $temp ) <= $padTo ) {
00514                 $temp .= ' ';
00515             }
00516 
00517             $from = self::LANG_CURRENT;
00518             // Abuse of the firstMagic property, should use something proper
00519             if ( $this->firstMagic ) {
00520                 $from = self::LANG_CHAIN;
00521             }
00522 
00523             // Check for translations
00524             $val = $this->val( $group, self::LANG_CURRENT, $key );
00525             if ( !$val || !count( $val ) ) {
00526                 continue;
00527             }
00528 
00529             // Then get the data we really want
00530             $val = $this->val( $group, $from, $key );
00531 
00532             // Remove duplicated entries, causes problems with magic words
00533             // Just to be sure, it should not be possible to save invalid data anymore
00534             $val = array_unique( $val /* @todo SORT_REGULAR */ );
00535 
00536             // So do empty elements...
00537             foreach ( $val as $k => $v ) {
00538                 if ( $v === '' ) {
00539                     unset( $val[$k] );
00540                 }
00541             }
00542 
00543             // Another check
00544             if ( !count( $val ) ) {
00545                 continue;
00546             }
00547 
00548             $normalized = array_map( array( $this, 'normalize' ), $val );
00549             if ( $this->elementsInArray ) {
00550                 $temp .= "=> array( " . implode( ', ', $normalized ) . " ),";
00551             } else {
00552                 $temp .= "=> " . implode( ', ', $normalized ) . ",";
00553             }
00554             $out .= $temp . "\n";
00555         }
00556 
00557         if ( $out !== '' ) {
00558             $text = "# {$data['label']} \n";
00559             $text .= "\$$var$extra = array(\n" . $out . ");\n\n";
00560 
00561             return $text;
00562         } else {
00563             return '';
00564         }
00565     }
00566 
00573     protected function normalize( $data ) {
00574         # Escape quotes
00575         if ( !is_string( $data ) ) {
00576             throw new MWException();
00577         }
00578         $data = preg_replace( "/(?<!\\\\)'/", "\'", trim( $data ) );
00579 
00580         return "'$data'";
00581     }
00582 
00583     #
00584     # /Export
00585     #
00586     public function highlight( $key, $values ) {
00587         return $values;
00588     }
00589 }
00590 
00595 class SpecialPageAliasesCM extends ComplexMessages {
00596     protected $id = SpecialMagic::MODULE_SPECIAL;
00597     protected $databaseMsg = 'sp-translate-data-SpecialPageAliases';
00598     protected $chainable = true;
00599 
00600     public function __construct( $code ) {
00601         parent::__construct( $code );
00602         $this->data['core'] = array(
00603             'label' => 'MediaWiki Core',
00604             'var' => 'specialPageAliases',
00605             'file' => Language::getMessagesFileName( '%CODE%' ),
00606             'code' => false,
00607         );
00608 
00609         global $wgTranslateExtensionDirectory;
00610         $groups = MessageGroups::singleton()->getGroups();
00611         foreach ( $groups as $g ) {
00612             if ( !$g instanceof MediaWikiExtensionMessageGroup ) {
00613                 continue;
00614             }
00615             $conf = $g->getConfiguration();
00616             if ( !isset( $conf['FILES']['aliasFile'] ) ) {
00617                 continue;
00618             }
00619             $file = $conf['FILES']['aliasFile'];
00620 
00621             $file = "$wgTranslateExtensionDirectory/$file";
00622             if ( file_exists( $file ) ) {
00623                 $this->data[$g->getId()] = array(
00624                     'label' => $g->getLabel(),
00625                     'var' => 'specialPageAliases',
00626                     'file' => $file,
00627                     'code' => $code,
00628                 );
00629             }
00630         }
00631     }
00632 
00633     public function highlight( $key, $values ) {
00634         if ( count( $values ) ) {
00635             if ( !isset( $values[0] ) ) {
00636                 throw new MWException( "Something missing from values: " .
00637                     print_r( $values, true ) );
00638             }
00639 
00640             $values[0] = "<strong>$values[0]</strong>";
00641         }
00642 
00643         return $values;
00644     }
00645 
00646     protected function validateEach( &$errors = array(), $group, &$used ) {
00647         parent::validateEach( $errors, $group, $used );
00648         foreach ( $this->getIterator( $group ) as $key ) {
00649             $values = $this->val( $group, self::LANG_CURRENT, $key );
00650 
00651             foreach ( $values as $_ ) {
00652                 wfSuppressWarnings();
00653                 $title = SpecialPage::getTitleFor( $_ );
00654                 wfRestoreWarnings();
00655                 $link = Xml::element( 'a', array( 'href' => "#mw-sp-magic-$key" ), $key );
00656                 if ( $title === null ) {
00657                     if ( $_ !== '' ) {
00658                         // Empty values checked elsewhere
00659                         $errors[] = "Translation <strong>$_</strong> is invalid title in $link.";
00660                     }
00661                 } else {
00662                     $text = $title->getText();
00663                     $dbkey = $title->getDBkey();
00664                     if ( $text !== $_ && $dbkey !== $_ ) {
00665                         $errors[] = "Translation <strong>$_</strong> for $link is not in " .
00666                             "normalised form, which is <strong>$text</strong>";
00667                     }
00668                 }
00669             }
00670         }
00671     }
00672 }
00673 
00678 class MagicWordsCM extends ComplexMessages {
00679     protected $id = SpecialMagic::MODULE_MAGIC;
00680     protected $firstMagic = true;
00681     protected $chainable = true;
00682     protected $databaseMsg = 'sp-translate-data-MagicWords';
00683 
00684     public function __construct( $code ) {
00685         parent::__construct( $code );
00686         $this->data['core'] = array(
00687             'label' => 'MediaWiki Core',
00688             'var' => 'magicWords',
00689             'file' => Language::getMessagesFileName( '%CODE%' ),
00690             'code' => false,
00691         );
00692 
00693         global $wgTranslateExtensionDirectory;
00694         $groups = MessageGroups::singleton()->getGroups();
00695         foreach ( $groups as $g ) {
00696             if ( !$g instanceof MediaWikiExtensionMessageGroup ) {
00697                 continue;
00698             }
00699             $conf = $g->getConfiguration();
00700             if ( !isset( $conf['FILES']['magicFile'] ) ) {
00701                 continue;
00702             }
00703             $file = $conf['FILES']['magicFile'];
00704 
00705             $file = "$wgTranslateExtensionDirectory/$file";
00706             if ( file_exists( $file ) ) {
00707                 $this->data[$g->getId()] = array(
00708                     'label' => $g->getLabel(),
00709                     'var' => 'magicWords',
00710                     'file' => $file,
00711                     'code' => $code,
00712                 );
00713             }
00714         }
00715     }
00716 
00717     public function highlight( $key, $values ) {
00718         if ( count( $values ) && $key === 'redirect' ) {
00719             $values[0] = "<strong>$values[0]</strong>";
00720         }
00721 
00722         return $values;
00723     }
00724 }
00725 
00730 class NamespaceCM extends ComplexMessages {
00731     protected $id = SpecialMagic::MODULE_NAMESPACE;
00732     protected $elementsInArray = false;
00733     protected $databaseMsg = 'sp-translate-data-Namespaces';
00734 
00735     public function __construct( $code ) {
00736         parent::__construct( $code );
00737         $this->data['core'] = array(
00738             'label' => 'MediaWiki Core',
00739             'var' => 'namespaceNames',
00740             'file' => Language::getMessagesFileName( '%CODE%' ),
00741             'code' => false,
00742         );
00743     }
00744 
00745     protected $constants = array(
00746         -2 => 'NS_MEDIA',
00747         -1 => 'NS_SPECIAL',
00748         0 => 'NS_MAIN',
00749         1 => 'NS_TALK',
00750         2 => 'NS_USER',
00751         3 => 'NS_USER_TALK',
00752         4 => 'NS_PROJECT',
00753         5 => 'NS_PROJECT_TALK',
00754         6 => 'NS_FILE',
00755         7 => 'NS_FILE_TALK',
00756         8 => 'NS_MEDIAWIKI',
00757         9 => 'NS_MEDIAWIKI_TALK',
00758         10 => 'NS_TEMPLATE',
00759         11 => 'NS_TEMPLATE_TALK',
00760         12 => 'NS_HELP',
00761         13 => 'NS_HELP_TALK',
00762         14 => 'NS_CATEGORY',
00763         15 => 'NS_CATEGORY_TALK',
00764     );
00765 
00766     protected function validateEach( &$errors = array(), $group, &$used ) {
00767         parent::validateEach( $errors, $group, $used );
00768         foreach ( $this->getIterator( $group ) as $key ) {
00769             $values = $this->val( $group, self::LANG_CURRENT, $key );
00770 
00771             if ( count( $values ) > 1 ) {
00772                 $link = Xml::element( 'a', array( 'href' => "#mw-sp-magic-$key" ), $key );
00773                 $errors[] = "Namespace $link can have only one translation. Replace the " .
00774                     "translation with a new one, and notify staff about the change.";
00775             }
00776         }
00777     }
00778 }
Generated on Tue Oct 29 00:00:23 2013 for MediaWiki Translate Extension by  doxygen 1.6.3