Merge "Export::dumpFrom() doesn't return any values, and hence neither do any of...
[lhc/web/wiklou.git] / includes / Export.php
index dec604e..73b07c3 100644 (file)
@@ -56,7 +56,7 @@ class WikiExporter {
         * make additional queries to pull source data while the
         * main query is still running.
         *
-        * @param $db Database
+        * @param $db DatabaseBase
         * @param $history Mixed: one of WikiExporter::FULL, WikiExporter::CURRENT,
         *                 WikiExporter::RANGE or WikiExporter::STABLE,
         *                 or an associative array:
@@ -103,7 +103,7 @@ class WikiExporter {
         * the most recent version.
         */
        public function allPages() {
-               return $this->dumpFrom( '' );
+               $this->dumpFrom( '' );
        }
 
        /**
@@ -118,7 +118,7 @@ class WikiExporter {
                if ( $end ) {
                        $condition .= ' AND page_id < ' . intval( $end );
                }
-               return $this->dumpFrom( $condition );
+               $this->dumpFrom( $condition );
        }
 
        /**
@@ -133,14 +133,14 @@ class WikiExporter {
                if ( $end ) {
                        $condition .= ' AND rev_id < ' . intval( $end );
                }
-               return $this->dumpFrom( $condition );
+               $this->dumpFrom( $condition );
        }
 
        /**
         * @param $title Title
         */
        public function pageByTitle( $title ) {
-               return $this->dumpFrom(
+               $this->dumpFrom(
                        'page_namespace=' . $title->getNamespace() .
                        ' AND page_title=' . $this->db->addQuotes( $title->getDBkey() ) );
        }
@@ -150,7 +150,7 @@ class WikiExporter {
                if ( is_null( $title ) ) {
                        throw new MWException( "Can't export invalid title" );
                } else {
-                       return $this->pageByTitle( $title );
+                       $this->pageByTitle( $title );
                }
        }
 
@@ -161,7 +161,7 @@ class WikiExporter {
        }
 
        public function allLogs() {
-               return $this->dumpFrom( '' );
+               $this->dumpFrom( '' );
        }
 
        public function logsByRange( $start, $end ) {
@@ -169,7 +169,7 @@ class WikiExporter {
                if ( $end ) {
                        $condition .= ' AND log_id < ' . intval( $end );
                }
-               return $this->dumpFrom( $condition );
+               $this->dumpFrom( $condition );
        }
 
        # Generates the distinct list of authors of an article
@@ -209,9 +209,6 @@ class WikiExporter {
                wfProfileIn( __METHOD__ );
                # For logging dumps...
                if ( $this->history & self::LOGS ) {
-                       if ( $this->buffer == WikiExporter::STREAM ) {
-                               $prev = $this->db->bufferResults( false );
-                       }
                        $where = array( 'user_id = log_user' );
                        # Hide private logs
                        $hideLogs = LogEventsList::getExcludeClause( $this->db );
@@ -220,16 +217,49 @@ class WikiExporter {
                        if ( $cond ) $where[] = $cond;
                        # Get logging table name for logging.* clause
                        $logging = $this->db->tableName( 'logging' );
-                       $result = $this->db->select( array( 'logging', 'user' ),
-                               array( "{$logging}.*", 'user_name' ), // grab the user name
-                               $where,
-                               __METHOD__,
-                               array( 'ORDER BY' => 'log_id', 'USE INDEX' => array( 'logging' => 'PRIMARY' ) )
-                       );
-                       $wrapper = $this->db->resultObject( $result );
-                       $this->outputLogStream( $wrapper );
+
                        if ( $this->buffer == WikiExporter::STREAM ) {
-                               $this->db->bufferResults( $prev );
+                               $prev = $this->db->bufferResults( false );
+                       }
+                       $wrapper = null; // Assuring $wrapper is not undefined, if exception occurs early
+                       try {
+                               $result = $this->db->select( array( 'logging', 'user' ),
+                                       array( "{$logging}.*", 'user_name' ), // grab the user name
+                                       $where,
+                                       __METHOD__,
+                                       array( 'ORDER BY' => 'log_id', 'USE INDEX' => array( 'logging' => 'PRIMARY' ) )
+                               );
+                               $wrapper = $this->db->resultObject( $result );
+                               $this->outputLogStream( $wrapper );
+                               if ( $this->buffer == WikiExporter::STREAM ) {
+                                       $this->db->bufferResults( $prev );
+                               }
+                       } catch ( Exception $e ) {
+                               // Throwing the exception does not reliably free the resultset, and
+                               // would also leave the connection in unbuffered mode.
+
+                               // Freeing result
+                               try {
+                                       if ( $wrapper ) {
+                                               $wrapper->free();
+                                       }
+                               } catch ( Exception $e2 ) {
+                                       // Already in panic mode -> ignoring $e2 as $e has
+                                       // higher priority
+                               }
+
+                               // Putting database back in previous buffer mode
+                               try {
+                                       if ( $this->buffer == WikiExporter::STREAM ) {
+                                               $this->db->bufferResults( $prev );
+                                       }
+                               } catch ( Exception $e2 ) {
+                                       // Already in panic mode -> ignoring $e2 as $e has
+                                       // higher priority
+                               }
+
+                               // Inform caller about problem
+                               throw $e;
                        }
                # For page dumps...
                } else {
@@ -300,20 +330,46 @@ class WikiExporter {
                                $prev = $this->db->bufferResults( false );
                        }
 
-                       wfRunHooks( 'ModifyExportQuery',
+                       $wrapper = null; // Assuring $wrapper is not undefined, if exception occurs early
+                       try {
+                               wfRunHooks( 'ModifyExportQuery',
                                                array( $this->db, &$tables, &$cond, &$opts, &$join ) );
 
-                       # Do the query!
-                       $result = $this->db->select( $tables, '*', $cond, __METHOD__, $opts, $join );
-                       $wrapper = $this->db->resultObject( $result );
-                       # Output dump results
-                       $this->outputPageStream( $wrapper );
-                       if ( $this->list_authors ) {
+                               # Do the query!
+                               $result = $this->db->select( $tables, '*', $cond, __METHOD__, $opts, $join );
+                               $wrapper = $this->db->resultObject( $result );
+                               # Output dump results
                                $this->outputPageStream( $wrapper );
-                       }
 
-                       if ( $this->buffer == WikiExporter::STREAM ) {
-                               $this->db->bufferResults( $prev );
+                               if ( $this->buffer == WikiExporter::STREAM ) {
+                                       $this->db->bufferResults( $prev );
+                               }
+                       } catch ( Exception $e ) {
+                               // Throwing the exception does not reliably free the resultset, and
+                               // would also leave the connection in unbuffered mode.
+
+                               // Freeing result
+                               try {
+                                       if ( $wrapper ) {
+                                               $wrapper->free();
+                                       }
+                               } catch ( Exception $e2 ) {
+                                       // Already in panic mode -> ignoring $e2 as $e has
+                                       // higher priority
+                               }
+
+                               // Putting database back in previous buffer mode
+                               try {
+                                       if ( $this->buffer == WikiExporter::STREAM ) {
+                                               $this->db->bufferResults( $prev );
+                                       }
+                               } catch ( Exception $e2 ) {
+                                       // Already in panic mode -> ignoring $e2 as $e has
+                                       // higher priority
+                               }
+
+                               // Inform caller about problem
+                               throw $e;
                        }
                }
                wfProfileOut( __METHOD__ );
@@ -324,7 +380,7 @@ class WikiExporter {
         * The result set should be sorted/grouped by page to avoid duplicate
         * page records in the output.
         *
-        * The result set will be freed once complete. Should be safe for
+        * Should be safe for
         * streaming (non-buffered) queries, as long as it was made on a
         * separate database connection not managed by LoadBalancer; some
         * blob storage types will make queries to pull source data.
@@ -476,16 +532,17 @@ class XmlDumpWriter {
        function openPage( $row ) {
                $out = "  <page>\n";
                $title = Title::makeTitle( $row->page_namespace, $row->page_title );
-               $out .= '    ' . Xml::elementClean( 'title', array(), $title->getPrefixedText() ) . "\n";
+               $out .= '    ' . Xml::elementClean( 'title', array(), self::canonicalTitle( $title ) ) . "\n";
                $out .= '    ' . Xml::element( 'ns', array(), strval( $row->page_namespace) ) . "\n";
                $out .= '    ' . Xml::element( 'id', array(), strval( $row->page_id ) ) . "\n";
                if ( $row->page_is_redirect ) {
                        $page = WikiPage::factory( $title );
                        $redirect = $page->getRedirectTarget();
                        if ( $redirect instanceOf Title && $redirect->isValidRedirectTarget() ) {
-                               $out .= '    ' . Xml::element( 'redirect', array( 'title' => $redirect->getPrefixedText() ) ) . "\n";
+                               $out .= '    ' . Xml::element( 'redirect', array( 'title' => self::canonicalTitle( $redirect ) ) ) . "\n";
                        }
                }
+
                if ( $row->page_restrictions != '' ) {
                        $out .= '    ' . Xml::element( 'restrictions', array(),
                                strval( $row->page_restrictions ) ) . "\n";
@@ -500,6 +557,7 @@ class XmlDumpWriter {
         * Closes a <page> section on the output stream.
         *
         * @access private
+        * @return string
         */
        function closePage() {
                return "  </page>\n";
@@ -543,15 +601,21 @@ class XmlDumpWriter {
                        // Raw text from the database may have invalid chars
                        $text = strval( Revision::getRevisionText( $row ) );
                        $out .= "      " . Xml::elementClean( 'text',
-                               array( 'xml:space' => 'preserve', 'bytes' => $row->rev_len ),
+                               array( 'xml:space' => 'preserve', 'bytes' => intval( $row->rev_len ) ),
                                strval( $text ) ) . "\n";
                } else {
                        // Stub output
                        $out .= "      " . Xml::element( 'text',
-                               array( 'id' => $row->rev_text_id, 'bytes' => $row->rev_len ),
+                               array( 'id' => $row->rev_text_id, 'bytes' => intval( $row->rev_len ) ),
                                "" ) . "\n";
                }
 
+               if ( $row->rev_sha1 && !( $row->rev_deleted & Revision::DELETED_TEXT ) ) {
+                       $out .= "      " . Xml::element('sha1', null, strval( $row->rev_sha1 ) ) . "\n";
+               } else {
+                       $out .= "      <sha1/>\n";
+               }
+
                wfRunHooks( 'XmlDumpWriterWriteRevision', array( &$this, &$out, $row, $text ) );
 
                $out .= "    </revision>\n";
@@ -595,7 +659,7 @@ class XmlDumpWriter {
                        $out .= "      " . Xml::element( 'text', array( 'deleted' => 'deleted' ) ) . "\n";
                } else {
                        $title = Title::makeTitle( $row->log_namespace, $row->log_title );
-                       $out .= "      " . Xml::elementClean( 'logtitle', null, $title->getPrefixedText() ) . "\n";
+                       $out .= "      " . Xml::elementClean( 'logtitle', null, self::canonicalTitle( $title ) ) . "\n";
                        $out .= "      " . Xml::elementClean( 'params',
                                array( 'xml:space' => 'preserve' ),
                                strval( $row->log_params ) ) . "\n";
@@ -626,6 +690,7 @@ class XmlDumpWriter {
 
        /**
         * Warning! This data is potentially inconsistent. :(
+        * @return string
         */
        function writeUploads( $row, $dumpContents = false ) {
                if ( $row->page_namespace == NS_IMAGE ) {
@@ -677,6 +742,30 @@ class XmlDumpWriter {
                        "    </upload>\n";
        }
 
+       /**
+        * Return prefixed text form of title, but using the content language's
+        * canonical namespace. This skips any special-casing such as gendered
+        * user namespaces -- which while useful, are not yet listed in the
+        * XML <siteinfo> data so are unsafe in export.
+        *
+        * @param Title $title
+        * @return string
+        * @since 1.18
+        */
+       public static function canonicalTitle( Title $title ) {
+               if ( $title->getInterwiki() ) {
+                       return $title->getPrefixedText();
+               }
+
+               global $wgContLang;
+               $prefix = str_replace( '_', ' ', $wgContLang->getNsText( $title->getNamespace() ) );
+
+               if ( $prefix !== '' ) {
+                       $prefix .= ':';
+               }
+
+               return $prefix . $title->getText();
+       }
 }
 
 
@@ -742,6 +831,7 @@ class DumpOutput {
        /**
         * Returns the name of the file or files which are
         * being written to, if there are any.
+        * @return null
         */
        function getFilenames() {
                return NULL;
@@ -753,13 +843,21 @@ class DumpOutput {
  * @ingroup Dump
  */
 class DumpFileOutput extends DumpOutput {
-       protected $handle, $filename;
+       protected $handle = false, $filename;
 
        function __construct( $file ) {
                $this->handle = fopen( $file, "wt" );
                $this->filename = $file;
        }
 
+       function writeCloseStream( $string ) {
+               parent::writeCloseStream( $string );
+               if ( $this->handle ) {
+                       fclose( $this->handle );
+                       $this->handle = false;
+               }
+       }
+
        function write( $string ) {
                fputs( $this->handle, $string );
        }
@@ -788,7 +886,10 @@ class DumpFileOutput extends DumpOutput {
        function closeAndRename( $newname, $open = false ) {
                $newname = $this->checkRenameArgCount( $newname );
                if ( $newname ) {
-                       fclose( $this->handle );
+                       if ( $this->handle ) {
+                               fclose( $this->handle );
+                               $this->handle = false;
+                       }
                        $this->renameOrException( $newname );
                        if ( $open ) {
                                $this->handle = fopen( $this->filename, "wt" );
@@ -809,6 +910,7 @@ class DumpFileOutput extends DumpOutput {
  */
 class DumpPipeOutput extends DumpFileOutput {
        protected $command, $filename;
+       protected $procOpenResource = false;
 
        function __construct( $command, $file = null ) {
                if ( !is_null( $file ) ) {
@@ -820,6 +922,14 @@ class DumpPipeOutput extends DumpFileOutput {
                $this->filename = $file;
        }
 
+       function writeCloseStream( $string ) {
+               parent::writeCloseStream( $string );
+               if ( $this->procOpenResource ) {
+                       proc_close( $this->procOpenResource );
+                       $this->procOpenResource = false;
+               }
+       }
+
        function startCommand( $command ) {
                $spec = array(
                        0 => array( "pipe", "r" ),
@@ -836,8 +946,14 @@ class DumpPipeOutput extends DumpFileOutput {
        function closeAndRename( $newname, $open = false ) {
                $newname = $this->checkRenameArgCount( $newname );
                if ( $newname ) {
-                       fclose( $this->handle );
-                       proc_close( $this->procOpenResource );
+                       if ( $this->handle ) {
+                               fclose( $this->handle );
+                               $this->handle = false;
+                       }
+                       if ( $this->procOpenResource ) {
+                               proc_close( $this->procOpenResource );
+                               $this->procOpenResource = false;
+                       }
                        $this->renameOrException( $newname );
                        if ( $open ) {
                                $command = $this->command;
@@ -874,8 +990,6 @@ class DumpBZip2Output extends DumpPipeOutput {
  * @ingroup Dump
  */
 class Dump7ZipOutput extends DumpPipeOutput {
-       protected $filename;
-
        function __construct( $file ) {
                $command = $this->setup7zCommand( $file );
                parent::__construct( $command );
@@ -890,10 +1004,6 @@ class Dump7ZipOutput extends DumpPipeOutput {
                return( $command );
        }
 
-       function closeRenameAndReopen( $newname ) {
-               $this->closeAndRename( $newname, true );
-       }
-
        function closeAndRename( $newname, $open = false ) {
                $newname = $this->checkRenameArgCount( $newname );
                if ( $newname ) {
@@ -901,7 +1011,7 @@ class Dump7ZipOutput extends DumpPipeOutput {
                        proc_close( $this->procOpenResource );
                        $this->renameOrException( $newname );
                        if ( $open ) {
-                               $command = $this->setup7zCommand( $file );
+                               $command = $this->setup7zCommand( $this->filename );
                                $this->startCommand( $command );
                        }
                }