--- mediawiki-1.5beta4/includes/SpecialExport.php 2005-07-13 03:59:12.000000000 +0200 +++ mwMM/applic/includes/SpecialExport.php 2005-08-20 17:45:52.867577320 +0200 @@ -1,4 +1,18 @@ # http://www.mediawiki.org/ # @@ -22,64 +36,349 @@ * @subpackage SpecialPage */ +# This is not a valid entry point, perform no further processing unless MEDIAWIKI is defined +if( !defined( 'MEDIAWIKI' ) ) { + die( "This file is part of MediaWiki and is not a valid entry point\n" ); +} + /** */ require_once( 'Revision.php' ); +/* +* ==== comment to be removed after patch acceptance ==== +* why not just boolean's for the related parameters ? +* do we expect other values ? +*/ +define( 'MW_EXPORT_FULL', 0 ); +define( 'MW_EXPORT_CURRENT', 1 ); + +define( 'MW_EXPORT_BUFFER', 0 ); +define( 'MW_EXPORT_STREAM', 1 ); + +/* +* ==== comment to be removed after patch acceptance === +* wfSpecialExport does 4 things : +* 1 - process the request and/or the config parameters +* 2 - send HTTP header(s) +* 3 - send the XML output +* 4 - build the form +* Because this patch increases a little bit tasks 1, 2 and 4, +* it seems more convenient to leave task 1 here, +* but to move tasks 2, 3 and 4 in separate functions. +* In order to avoid wfXxx name space pollution, these 3 functions +* are created as static function of class WikiExporter +* doHeaders() , doExport() , doForm +*/ + /** - * + * Entry point + * @param $page string */ function wfSpecialExport( $page = '' ) { - global $wgOut, $wgLang, $wgRequest; + global $wgRequest; + + // config parameters : + + $wgSpecialExportFilename = is_integer($wgSpecialExportFilename) ? + $wgSpecialExportFilename : MW_EXPORT_FILENAME_DEFAULT ; + $wgSpecialExportDownload = is_integer($wgSpecialExportDownload) ? + $wgSpecialExportDownload : MW_EXPORT_DISPO_DEFAULT ; + $wgSpecialExportPrefix = is_string($wgSpecialExportPrefix) ? + $wgSpecialExportPrefix : MW_EXPORT_DEFAULT_PREFIX ; + + // proceed/prepare request : if( $wgRequest->getVal( 'action' ) == 'submit') { $page = $wgRequest->getText( 'pages' ); $curonly = $wgRequest->getCheck( 'curonly' ); + $szRequesXmlFileName = $wgRequest->getText( 'szXmlFileName' ); + $zRequestDispo = $wgRequest->getCheck( 'zDispo' ); } else { # Pre-check the 'current version only' box in the UI $curonly = true; } if( $page != '' ) { + + // output HTTP header(s) : + + WikiExporter::doHeaders( + // $zXmlDisposition = true // shall we use Content-Disposition ? + (( $wgSpecialExportDownload == MW_EXPORT_DISPO_ALLWAYS) || $zRequestDispo ) + // , $szSuggestedPrefix = MW_EXPORT_DEFAULT_PREFIX + , $wgSpecialExportPrefix + // , $zDate = true + , (($wgSpecialExportFilename == MW_EXPORT_FILENAME_ASK_ELSE_TIMESTAMP) + || ($wgSpecialExportFilename == MW_EXPORT_FILENAME_TIMESTAMP)) + , $szXmlFileName + // , $zRelyOnFileName = false + // , $uMaxLength = 250 // RARELY USED ! + // , $zGerman = true // RARELY USED ! + ) ; + + // output XML : + + WikiExporter::doExport( + $page + // , $history = MW_EXPORT_CURRENT + , ( $curonly ? MW_EXPORT_CURRENT : MW_EXPORT_FULL ) + // , $buffer = MW_EXPORT_BUFFER + ) ; + + } else { + + // prepare the form : + + WikiExporter::doForm( + // $zCurOnly = true // preset value of the 'current v. all' checkbox + $curonly + // , $zDisposition = true // preset value for the 'download' checkbox + , (($wgSpecialExportDownload == MW_EXPORT_DISPO_SUGGESTED) || $zRequestDispo) + // , $szXmlDispositionFileName = '' // preset value for the file name + , $szXmlFileName + // , $zAskFilename = true // shall we prompt the user for a filename? + , (($wgSpecialExportFilename == MW_EXPORT_FILENAME_ASK_ELSE_TIMESTAMP) + || ($wgSpecialExportFilename == MW_EXPORT_FILENAME_ASK_ELSE_RANDOM)) + // , $zAskDisposition = true // shall we prompt the user for download? + , (($wgSpecialExportDownload == MW_EXPORT_DISPO_PERHAPS) + || ($wgSpecialExportDownload == MW_EXPORT_DISPO_SUGGESTED)) + // , $szSuggestedPrefix = MW_EXPORT_DEFAULT_PREFIX + , $wgSpecialExportPrefix + // , $zDate = true + , (($wgSpecialExportFilename == MW_EXPORT_FILENAME_ASK_ELSE_TIMESTAMP) + || ($wgSpecialExportFilename == MW_EXPORT_FILENAME_TIMESTAMP)) + // , $zRelyOnFileName = false + // , $uMaxLength = 250 // RARELY USED ! + // , $zGerman = true // RARELY USED ! + ) ; + } +} + +/** + * @package MediaWiki + * @subpackage SpecialPage + */ +class WikiExporter { + /**#@+ + * @access public + * @static + */ + + /** + * @param boolean $zXmlDisposition : shall we use Content-Disposition ? + * @param string $szSuggestedPrefix + * @param boolean $zDate : use a timestamp (otherwise use a random value) + * @param string $szXmlDispositionFileName + * @param boolean $zRelyOnFileName (turn on if you are sure file name is ok, probably beacause it was generated automatically) + * @param unsigned $uMaxLength (RARE) Maximum filename length. Default is 250 characters. + * @param boolean $zGerman (RARE) proceed also german characters + */ + function doHeaders( + $zXmlDisposition = true // shall we use Content-Disposition ? + , $szSuggestedPrefix = MW_EXPORT_DEFAULT_PREFIX + , $zDate = true + , $szXmlDispositionFileName = '' + , $zRelyOnFileName = false + , $uMaxLength = 250 // RARELY USED ! + , $zGerman = true // RARELY USED ! + ) + { + global $wgOut ; $wgOut->disable(); header( "Content-type: application/xml; charset=utf-8" ); - $pages = explode( "\n", $page ); + if($zXmlDisposition) + { + if( ! $zRelyOnFileName ) + { + $szXmlDispositionFileName = wfUnixFileName( + $szXmlDispositionFileName,$uMaxLength,$zGerman) ; + } + if(! $szXmlDispositionFileName) + { + $szXmlDispositionFileName = wfSuggestFileName( + $szSuggestedPrefix,$zDate,$uMaxLength) ; + } + header("Content-Disposition: attachment; filename=\"{$szXmlDispositionFileName}\"'"); + } + } + /** + * factorize and run... + * + * @param string $page + * @param unsigned $history one of MW_EXPORT_FULL or MW_EXPORT_CURRENT + * @param unsigned $buffer one of MW_EXPORT_BUFFER or MW_EXPORT_STREAM + */ + function doExport( + $page + /* + * ==== comment to be removed after patch acceptance ==== + * why not just boolean's for these 2 parameters ? + */ + , $history = MW_EXPORT_CURRENT + , $buffer = MW_EXPORT_BUFFER + ) + { + $pages = explode( "\n", $page ); $db =& wfGetDB( DB_SLAVE ); - $history = $curonly ? MW_EXPORT_CURRENT : MW_EXPORT_FULL; - $exporter = new WikiExporter( $db, $history ); + $exporter = new WikiExporter( + $db + , $history = MW_EXPORT_CURRENT + , $buffer = MW_EXPORT_BUFFER + ); + /* + * ==== comment to be removed after patch acceptance ==== + * Mind that openStream() and closeStream() are, in fact, static functions + */ $exporter->openStream(); + // WikiExporter::openStream(); //... $exporter->pagesByName( $pages ); $exporter->closeStream(); - return; + // WikiExporter::closeStream(); //... } + /** + * write down the Export form on $wgOut + * + * @param boolean $curonly ($zCurOnly) preset value of the 'current v. all' checkbox + * @param boolean $zDisposition preset value for the 'download' checkbox + * @param string $szXmlDispositionFileName preset value for the file name + * @param boolean $zAskFilename shall we prompt the user for a filename? + * @param boolean $zAskDisposition shall we prompt the user for download? + * @param boolean $zDate use time stamp if we need to generate a file name + * @param boolean $zRelyOnFileName (turn on if you are sure file name is ok, probably beacause it was generated automatically) + * @param unsigned $uMaxLength (rarely used) + * @param boolean $zGerman (rarely used) + */ + function doForm( + $zCurOnly = true // preset value of the 'current v. all' checkbox + , $zDisposition = true // preset value for the 'download' checkbox + , $szXmlDispositionFileName = '' // preset value for the file name + , $zAskFilename = true // shall we prompt the user for a filename? + , $zAskDisposition = true // shall we prompt the user for download? + , $szSuggestedPrefix = MW_EXPORT_DEFAULT_PREFIX + , $zDate = true + , $zRelyOnFileName = false + , $uMaxLength = 250 // RARELY USED ! + , $zGerman = true // RARELY USED ! + ) + { + global $wgOut ; + $wgOut->addWikiText( wfMsg( "exporttext" ) ); $titleObj = Title::makeTitle( NS_SPECIAL, "Export" ); $action = $titleObj->escapeLocalURL( 'action=submit' ); + /* + * === comment to be removed after patch acceptance : + * there was a minor bug here : $curonly was ignored + */ + $szCurOnlyChecked = $zCurOnly ? " checked='checked' " : '' ; + $szPromptCurOnly = wfMsg( "exportcuronly" ) ; $wgOut->addHTML( "

-
- -
+
+" ) ; + + if($zAskDisposition) + { + /* + * === comment to be removed after patch acceptance : + * === Mind that 'exportpromptdisposition' is a NEW message. + */ + $szPromptDisposition = wfMsg('exportpromptdisposition') ; + $szDispositionChecked = $zDisposition ? " checked='checked' " : '' ; + $wgOut->addHTML( " +
" ); -} + } -define( 'MW_EXPORT_FULL', 0 ); -define( 'MW_EXPORT_CURRENT', 1 ); + if($zAskFilename) + { + /* + * === comment to be removed after patch acceptance : + * === Mind that 'exportpromptfilename' is a NEW message. + */ + $szPromptFilename = wfMsg('exportpromptfilename') ; + if( ! $zRelyOnFileName ) + { + /* + $szXmlDispositionFileName may perfectly be empty. + However, if it is provided, then it MUST be ok! + It is probably already builded automatically and correct. + Anyway, we still make an ultimate checking. + */ + $szXmlDispositionFileName = trim($szXmlDispositionFileName) ; + if($szXmlDispositionFileName) + { + $szXmlDispositionFileName = wfUnixFileName($szXmlDispositionFileName,$uMaxLength,$zGerman) ; + } + } + if(! $szXmlDispositionFileName) + { + $szXmlDispositionFileName = wfSuggestFileName($szSuggestedPrefix,$zDate,$uMaxLength) ; + } + $wgOut->addHTML( " + + +
+" ); + } -define( 'MW_EXPORT_BUFFER', 0 ); -define( 'MW_EXPORT_STREAM', 1 ); + /* + * === comment to be removed after patch acceptance : + * === Mind that 'exportsubmit' is a NEW message. + * The reason is that, at least in french, standard words often used on submit buttons + * such as 'submit', 'send', 'apply', 'transmit' are very ambiguous. + * 'Send' is generally translated by 'Envoyer'. + * But what means 'envoyer'? From where/who? To where/who? + * Why not 'receive' or 'recevoir' ? etc + * An *explicit* word such as 'Export', 'Import', 'Download', 'Upload' + * must absolutely be used (at least in some languages)... + */ + $szSubmit = wfMsg('exportsubmit') ; + $wgOut->addHTML( " + + +" ); + /* + * ==== comments to be removed when patch is accepted : + * Mind the 3 new messages (see Language.php) + * - exportpromptdisposition + * - exportpromptfilename + * - exportsubmit + */ + } -/** - * @package MediaWiki - * @subpackage SpecialPage + /**#@-*/ + + /**#@+ + * @access private + */ + /** + * @var function */ -class WikiExporter { var $pageCallback = null; + /** + * @var function + */ var $revCallback = null; + /** + * @var unsigned mode + */ + var $history ; + /** + * @var unsigned mode + */ + var $buffer ; + /** + * @var Database + */ + var $db ; + /**#@-*/ /** * If using MW_EXPORT_STREAM to stream a large amount of data, @@ -89,11 +388,14 @@ * main query is still running. * * @param Database $db - * @param int $history one of MW_EXPORT_FULL or MW_EXPORT_CURRENT - * @param int $buffer one of MW_EXPORT_BUFFER or MW_EXPORT_STREAM + * @param unsigned $history one of MW_EXPORT_FULL or MW_EXPORT_CURRENT + * @param unsigned $buffer one of MW_EXPORT_BUFFER or MW_EXPORT_STREAM */ - function WikiExporter( &$db, $history = MW_EXPORT_CURRENT, - $buffer = MW_EXPORT_BUFFER ) { + function WikiExporter( + &$db + , $history = MW_EXPORT_CURRENT + , $buffer = MW_EXPORT_BUFFER + ) { $this->db =& $db; $this->history = $history; $this->buffer = $buffer; @@ -251,6 +553,10 @@ // -------------------- private implementation below -------------------- + /**#@+ + * @access private + */ + function dumpFrom( $cond = '' ) { $fname = 'WikiExporter::dumpFrom'; wfProfileIn( $fname ); @@ -308,7 +614,6 @@ * blob storage types will make queries to pull source data. * * @param ResultWrapper $resultset - * @access private */ function outputStream( $resultset ) { $last = null; @@ -335,7 +640,6 @@ * from the given database row. * * @param object $row - * @access private */ function openPage( $row ) { print "\n"; @@ -354,7 +658,6 @@ * and passed the last database row used for this page. * * @param object $row - * @access private */ function closePage( $row ) { print "\n"; @@ -368,7 +671,6 @@ * data filled in from the given database row. * * @param object $row - * @access private */ function dumpRev( $row ) { $fname = 'WikiExporter::dumpRev'; @@ -409,14 +711,111 @@ } } + /**#@-*/ } +/** +* @param string $ts +* @return string +*/ function wfTimestamp2ISO8601( $ts ) { #2003-08-05T18:30:02Z return preg_replace( '/^(....)(..)(..)(..)(..)(..)$/', '$1-$2-$3T$4:$5:$6Z', $ts ); } -function xmlsafe( $string ) { +/* +* ==== comment to be removed after patch acceptance ==== +* The next 3 global functions are, in my personal implementation, +* located, for reusability, in includes/GlobalFunctions.php rather than here +* because they can be usefull eveywhere there is a need to build a filename. +* Mind that we voluntary divide into 3 functions as to improve reability. +* I suggest to move these 3 functions into GlobalFunctions.php +*/ + +/** +* Generates a unique file name e.g. for a downloaded file +* +* @param string $szSuggestedPrefix +* @param boolean $zDate : use a timestamp (otherwise use a random value) +* @param unsigned $uMaxLength Maximum filename length. Default is 250 characters. +* @return string generated file name +*/ +function wfSuggestFileName( +$szPrefix = MW_EXPORT_DEFAULT_PREFIX +, $zDate = true +, $uMaxLength = 250 +) +{ + $szName = $szPrefix . '-' . ( $zDate ? date("Y-m-d-H-i-s") : mt_rand(1234567,9876543) ) ; + // still verify : illegal prefix ? length ? + return wfUnixFileName($szName,$uMaxLength) ; +} + +/** +* Generates an usefull Unix filename besides the original one. +* +* @param string $szName The filename to be processed. +* @param unsigned $uMaxLength Maximum filename length. Default is 250 characters. +* @param boolean $zGerman proceed also german characters +* @return string +*/ +function wfUnixFileName( +$szName +, $uMaxLength = 250 +, $zGerman= true +) +{ + $uMaxLength = max(1,min(250,$uMaxLength)) ; + // remove accents : + $szName = wfRemoveAccents($szName,$zGerman) ; + // Replace any remaining special characters with an underscore. + $szName = preg_replace('/[^a-z0-9.-]+/i', '_', $szName); + // Remove any useless underscores, e.g. _a_a_._a_ becomes a_a.a + $szName = preg_replace('/_*\b_*/', '', $szName); + // Crop the filename if it's too long. + while (strlen($szName) > $uMaxLength) + $szName = preg_replace('/.\b/', '', $szName, 1); + return $szName; +} + +/** +* wfRemoveAccents replaces some special 'european' characters +* so that a name may be used e.g. to build a file name. +* +* Mind that the returned string has the same length of the original +* if $zGerman==false, but may have a different length otherwise. +* +* @param string $szName a name with accents +* @param boolean $zGerman proceed also german characters +* @return string the name with accents removed (replacing) +*/ +function wfRemoveAccents($szName, $zGerman=true) +{ + if($zGerman) + { + // Replace German umlauts and other special characters. + $szName = str_replace("ß", "ss", $szName); + $szName = preg_replace('/[ÄÖÜäöü]/', '\0e', $szName); + } + // Proceed accents, etc + $szName = strtr($szName, + "ÀÁÂÃÄÅÆÈÉÊËÌÍÎÏÒÓÔÕÖØÙÚÛÜàáâãäåæèéêëìíîïòóôõöøùúûüçÇß", + "AAAAAAAEEEEIIIIOOOOOOUUUUaaaaaaaeeeeiiiioooooouuuucCs"); + /* + * Mind that the process of "ß", "ü", etc is different if 'german' is on or off! + */ + return $szName ; +} + +/** +* Function 'xmlsafe' UNUSED, renamed to wfXmlSafe to stick to mw conventions +* +* @param string $string +* @return string $string +* @todo move function into WikiExporter class or decide to rename as wfXmlSafe +* @todo this function is currently unused... +*/ +function wfXmlSafe( $string ) { $fname = 'xmlsafe'; wfProfileIn( $fname ); @@ -432,4 +831,11 @@ return $string; } +/** +* ==== comment to be removed after patch acceptance ==== +* I systematically destroy whitespaces after the final ? > +* These caracters are generally harmless. +* However, in some applications (cookies, headers) they are dangerous. +* So, it's a matter of principle : just delete them eveywhere +*/ ?>