summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
Diffstat (limited to 'includes')
-rw-r--r--includes/db/DatabaseMssql.php1490
-rw-r--r--includes/db/DatabaseOracle.php1546
-rw-r--r--includes/installer/MssqlInstaller.php736
-rw-r--r--includes/installer/MssqlUpdater.php143
-rw-r--r--includes/installer/OracleInstaller.php344
-rw-r--r--includes/installer/OracleUpdater.php289
-rw-r--r--includes/libs/IEContentAnalyzer.php851
-rw-r--r--includes/libs/IEUrlExtension.php271
-rw-r--r--includes/search/SearchMssql.php210
-rw-r--r--includes/search/SearchOracle.php273
10 files changed, 0 insertions, 6153 deletions
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
deleted file mode 100644
index 85f1b96d..00000000
--- a/includes/db/DatabaseMssql.php
+++ /dev/null
@@ -1,1490 +0,0 @@
-<?php
-/**
- * This is the MS SQL Server Native database abstraction layer.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- * @author Joel Penner <a-joelpe at microsoft dot com>
- * @author Chris Pucci <a-cpucci at microsoft dot com>
- * @author Ryan Biesemeyer <v-ryanbi at microsoft dot com>
- * @author Ryan Schmidt <skizzerz at gmail dot com>
- */
-
-/**
- * @ingroup Database
- */
-class DatabaseMssql extends DatabaseBase {
- protected $mInsertId = null;
- protected $mLastResult = null;
- protected $mAffectedRows = null;
- protected $mSubqueryId = 0;
- protected $mScrollableCursor = true;
- protected $mPrepareStatements = true;
- protected $mBinaryColumnCache = null;
- protected $mBitColumnCache = null;
- protected $mIgnoreDupKeyErrors = false;
-
- protected $mPort;
-
- public function cascadingDeletes() {
- return true;
- }
-
- public function cleanupTriggers() {
- return false;
- }
-
- public function strictIPs() {
- return false;
- }
-
- public function realTimestamps() {
- return false;
- }
-
- public function implicitGroupby() {
- return false;
- }
-
- public function implicitOrderby() {
- return false;
- }
-
- public function functionalIndexes() {
- return true;
- }
-
- public function unionSupportsOrderAndLimit() {
- return false;
- }
-
- /**
- * Usually aborts on failure
- * @param string $server
- * @param string $user
- * @param string $password
- * @param string $dbName
- * @throws DBConnectionError
- * @return bool|DatabaseBase|null
- */
- public function open( $server, $user, $password, $dbName ) {
- # Test for driver support, to avoid suppressed fatal error
- if ( !function_exists( 'sqlsrv_connect' ) ) {
- throw new DBConnectionError(
- $this,
- "Microsoft SQL Server Native (sqlsrv) functions missing.
- You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n"
- );
- }
-
- global $wgDBport, $wgDBWindowsAuthentication;
-
- # e.g. the class is being loaded
- if ( !strlen( $user ) ) {
- return null;
- }
-
- $this->close();
- $this->mServer = $server;
- $this->mPort = $wgDBport;
- $this->mUser = $user;
- $this->mPassword = $password;
- $this->mDBname = $dbName;
-
- $connectionInfo = array();
-
- if ( $dbName ) {
- $connectionInfo['Database'] = $dbName;
- }
-
- // Decide which auth scenerio to use
- // if we are using Windows auth, don't add credentials to $connectionInfo
- if ( !$wgDBWindowsAuthentication ) {
- $connectionInfo['UID'] = $user;
- $connectionInfo['PWD'] = $password;
- }
-
- MediaWiki\suppressWarnings();
- $this->mConn = sqlsrv_connect( $server, $connectionInfo );
- MediaWiki\restoreWarnings();
-
- if ( $this->mConn === false ) {
- throw new DBConnectionError( $this, $this->lastError() );
- }
-
- $this->mOpened = true;
-
- return $this->mConn;
- }
-
- /**
- * Closes a database connection, if it is open
- * Returns success, true if already closed
- * @return bool
- */
- protected function closeConnection() {
- return sqlsrv_close( $this->mConn );
- }
-
- /**
- * @param bool|MssqlResultWrapper|resource $result
- * @return bool|MssqlResultWrapper
- */
- public function resultObject( $result ) {
- if ( empty( $result ) ) {
- return false;
- } elseif ( $result instanceof MssqlResultWrapper ) {
- return $result;
- } elseif ( $result === true ) {
- // Successful write query
- return $result;
- } else {
- return new MssqlResultWrapper( $this, $result );
- }
- }
-
- /**
- * @param string $sql
- * @return bool|MssqlResult
- * @throws DBUnexpectedError
- */
- protected function doQuery( $sql ) {
- if ( $this->debug() ) {
- wfDebug( "SQL: [$sql]\n" );
- }
- $this->offset = 0;
-
- // several extensions seem to think that all databases support limits
- // via LIMIT N after the WHERE clause well, MSSQL uses SELECT TOP N,
- // so to catch any of those extensions we'll do a quick check for a
- // LIMIT clause and pass $sql through $this->LimitToTopN() which parses
- // the limit clause and passes the result to $this->limitResult();
- if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) {
- // massage LIMIT -> TopN
- $sql = $this->LimitToTopN( $sql );
- }
-
- // MSSQL doesn't have EXTRACT(epoch FROM XXX)
- if ( preg_match( '#\bEXTRACT\s*?\(\s*?EPOCH\s+FROM\b#i', $sql, $matches ) ) {
- // This is same as UNIX_TIMESTAMP, we need to calc # of seconds from 1970
- $sql = str_replace( $matches[0], "DATEDIFF(s,CONVERT(datetime,'1/1/1970'),", $sql );
- }
-
- // perform query
-
- // SQLSRV_CURSOR_STATIC is slower than SQLSRV_CURSOR_CLIENT_BUFFERED (one of the two is
- // needed if we want to be able to seek around the result set), however CLIENT_BUFFERED
- // has a bug in the sqlsrv driver where wchar_t types (such as nvarchar) that are empty
- // strings make php throw a fatal error "Severe error translating Unicode"
- if ( $this->mScrollableCursor ) {
- $scrollArr = array( 'Scrollable' => SQLSRV_CURSOR_STATIC );
- } else {
- $scrollArr = array();
- }
-
- if ( $this->mPrepareStatements ) {
- // we do prepare + execute so we can get its field metadata for later usage if desired
- $stmt = sqlsrv_prepare( $this->mConn, $sql, array(), $scrollArr );
- $success = sqlsrv_execute( $stmt );
- } else {
- $stmt = sqlsrv_query( $this->mConn, $sql, array(), $scrollArr );
- $success = (bool)$stmt;
- }
-
- if ( $this->mIgnoreDupKeyErrors ) {
- // ignore duplicate key errors, but nothing else
- // this emulates INSERT IGNORE in MySQL
- if ( $success === false ) {
- $errors = sqlsrv_errors( SQLSRV_ERR_ERRORS );
- $success = true;
-
- foreach ( $errors as $err ) {
- if ( $err['SQLSTATE'] == '23000' && $err['code'] == '2601' ) {
- continue; // duplicate key error caused by unique index
- } elseif ( $err['SQLSTATE'] == '23000' && $err['code'] == '2627' ) {
- continue; // duplicate key error caused by primary key
- } elseif ( $err['SQLSTATE'] == '01000' && $err['code'] == '3621' ) {
- continue; // generic "the statement has been terminated" error
- }
-
- $success = false; // getting here means we got an error we weren't expecting
- break;
- }
-
- if ( $success ) {
- $this->mAffectedRows = 0;
- return $stmt;
- }
- }
- }
-
- if ( $success === false ) {
- return false;
- }
- // remember number of rows affected
- $this->mAffectedRows = sqlsrv_rows_affected( $stmt );
-
- return $stmt;
- }
-
- public function freeResult( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- sqlsrv_free_stmt( $res );
- }
-
- /**
- * @param MssqlResultWrapper $res
- * @return stdClass
- */
- public function fetchObject( $res ) {
- // $res is expected to be an instance of MssqlResultWrapper here
- return $res->fetchObject();
- }
-
- /**
- * @param MssqlResultWrapper $res
- * @return array
- */
- public function fetchRow( $res ) {
- return $res->fetchRow();
- }
-
- /**
- * @param mixed $res
- * @return int
- */
- public function numRows( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return sqlsrv_num_rows( $res );
- }
-
- /**
- * @param mixed $res
- * @return int
- */
- public function numFields( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return sqlsrv_num_fields( $res );
- }
-
- /**
- * @param mixed $res
- * @param int $n
- * @return int
- */
- public function fieldName( $res, $n ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- $metadata = sqlsrv_field_metadata( $res );
- return $metadata[$n]['Name'];
- }
-
- /**
- * This must be called after nextSequenceVal
- * @return int|null
- */
- public function insertId() {
- return $this->mInsertId;
- }
-
- /**
- * @param MssqlResultWrapper $res
- * @param int $row
- * @return bool
- */
- public function dataSeek( $res, $row ) {
- return $res->seek( $row );
- }
-
- /**
- * @return string
- */
- public function lastError() {
- $strRet = '';
- $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
- if ( $retErrors != null ) {
- foreach ( $retErrors as $arrError ) {
- $strRet .= $this->formatError( $arrError ) . "\n";
- }
- } else {
- $strRet = "No errors found";
- }
-
- return $strRet;
- }
-
- /**
- * @param array $err
- * @return string
- */
- private function formatError( $err ) {
- return '[SQLSTATE ' . $err['SQLSTATE'] . '][Error Code ' . $err['code'] . ']' . $err['message'];
- }
-
- /**
- * @return string
- */
- public function lastErrno() {
- $err = sqlsrv_errors( SQLSRV_ERR_ALL );
- if ( $err !== null && isset( $err[0] ) ) {
- return $err[0]['code'];
- } else {
- return 0;
- }
- }
-
- /**
- * @return int
- */
- public function affectedRows() {
- return $this->mAffectedRows;
- }
-
- /**
- * SELECT wrapper
- *
- * @param mixed $table Array or string, table name(s) (prefix auto-added)
- * @param mixed $vars Array or string, field name(s) to be retrieved
- * @param mixed $conds Array or string, condition(s) for WHERE
- * @param string $fname Calling function name (use __METHOD__) for logs/profiling
- * @param array $options Associative array of options (e.g.
- * array('GROUP BY' => 'page_title')), see Database::makeSelectOptions
- * code for list of supported stuff
- * @param array $join_conds Associative array of table join conditions
- * (optional) (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
- * @return mixed Database result resource (feed to Database::fetchObject
- * or whatever), or false on failure
- * @throws DBQueryError
- * @throws DBUnexpectedError
- * @throws Exception
- */
- public function select( $table, $vars, $conds = '', $fname = __METHOD__,
- $options = array(), $join_conds = array()
- ) {
- $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
- if ( isset( $options['EXPLAIN'] ) ) {
- try {
- $this->mScrollableCursor = false;
- $this->mPrepareStatements = false;
- $this->query( "SET SHOWPLAN_ALL ON" );
- $ret = $this->query( $sql, $fname );
- $this->query( "SET SHOWPLAN_ALL OFF" );
- } catch ( DBQueryError $dqe ) {
- if ( isset( $options['FOR COUNT'] ) ) {
- // likely don't have privs for SHOWPLAN, so run a select count instead
- $this->query( "SET SHOWPLAN_ALL OFF" );
- unset( $options['EXPLAIN'] );
- $ret = $this->select(
- $table,
- 'COUNT(*) AS EstimateRows',
- $conds,
- $fname,
- $options,
- $join_conds
- );
- } else {
- // someone actually wanted the query plan instead of an est row count
- // let them know of the error
- $this->mScrollableCursor = true;
- $this->mPrepareStatements = true;
- throw $dqe;
- }
- }
- $this->mScrollableCursor = true;
- $this->mPrepareStatements = true;
- return $ret;
- }
- return $this->query( $sql, $fname );
- }
-
- /**
- * SELECT wrapper
- *
- * @param mixed $table Array or string, table name(s) (prefix auto-added)
- * @param mixed $vars Array or string, field name(s) to be retrieved
- * @param mixed $conds Array or string, condition(s) for WHERE
- * @param string $fname Calling function name (use __METHOD__) for logs/profiling
- * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
- * @param array $join_conds Associative array of table join conditions (optional)
- * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
- * @return string The SQL text
- */
- public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
- $options = array(), $join_conds = array()
- ) {
- if ( isset( $options['EXPLAIN'] ) ) {
- unset( $options['EXPLAIN'] );
- }
-
- $sql = parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
-
- // try to rewrite aggregations of bit columns (currently MAX and MIN)
- if ( strpos( $sql, 'MAX(' ) !== false || strpos( $sql, 'MIN(' ) !== false ) {
- $bitColumns = array();
- if ( is_array( $table ) ) {
- foreach ( $table as $t ) {
- $bitColumns += $this->getBitColumns( $this->tableName( $t ) );
- }
- } else {
- $bitColumns = $this->getBitColumns( $this->tableName( $table ) );
- }
-
- foreach ( $bitColumns as $col => $info ) {
- $replace = array(
- "MAX({$col})" => "MAX(CAST({$col} AS tinyint))",
- "MIN({$col})" => "MIN(CAST({$col} AS tinyint))",
- );
- $sql = str_replace( array_keys( $replace ), array_values( $replace ), $sql );
- }
- }
-
- return $sql;
- }
-
- public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
- $fname = __METHOD__
- ) {
- $this->mScrollableCursor = false;
- try {
- parent::deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname );
- } catch ( Exception $e ) {
- $this->mScrollableCursor = true;
- throw $e;
- }
- $this->mScrollableCursor = true;
- }
-
- public function delete( $table, $conds, $fname = __METHOD__ ) {
- $this->mScrollableCursor = false;
- try {
- parent::delete( $table, $conds, $fname );
- } catch ( Exception $e ) {
- $this->mScrollableCursor = true;
- throw $e;
- }
- $this->mScrollableCursor = true;
- }
-
- /**
- * Estimate rows in dataset
- * Returns estimated count, based on SHOWPLAN_ALL output
- * This is not necessarily an accurate estimate, so use sparingly
- * Returns -1 if count cannot be found
- * Takes same arguments as Database::select()
- * @param string $table
- * @param string $vars
- * @param string $conds
- * @param string $fname
- * @param array $options
- * @return int
- */
- public function estimateRowCount( $table, $vars = '*', $conds = '',
- $fname = __METHOD__, $options = array()
- ) {
- // http://msdn2.microsoft.com/en-us/library/aa259203.aspx
- $options['EXPLAIN'] = true;
- $options['FOR COUNT'] = true;
- $res = $this->select( $table, $vars, $conds, $fname, $options );
-
- $rows = -1;
- if ( $res ) {
- $row = $this->fetchRow( $res );
-
- if ( isset( $row['EstimateRows'] ) ) {
- $rows = (int)$row['EstimateRows'];
- }
- }
-
- return $rows;
- }
-
- /**
- * Returns information about an index
- * If errors are explicitly ignored, returns NULL on failure
- * @param string $table
- * @param string $index
- * @param string $fname
- * @return array|bool|null
- */
- public function indexInfo( $table, $index, $fname = __METHOD__ ) {
- # This does not return the same info as MYSQL would, but that's OK
- # because MediaWiki never uses the returned value except to check for
- # the existance of indexes.
- $sql = "sp_helpindex '" . $table . "'";
- $res = $this->query( $sql, $fname );
- if ( !$res ) {
- return null;
- }
-
- $result = array();
- foreach ( $res as $row ) {
- if ( $row->index_name == $index ) {
- $row->Non_unique = !stristr( $row->index_description, "unique" );
- $cols = explode( ", ", $row->index_keys );
- foreach ( $cols as $col ) {
- $row->Column_name = trim( $col );
- $result[] = clone $row;
- }
- } elseif ( $index == 'PRIMARY' && stristr( $row->index_description, 'PRIMARY' ) ) {
- $row->Non_unique = 0;
- $cols = explode( ", ", $row->index_keys );
- foreach ( $cols as $col ) {
- $row->Column_name = trim( $col );
- $result[] = clone $row;
- }
- }
- }
-
- return empty( $result ) ? false : $result;
- }
-
- /**
- * INSERT wrapper, inserts an array into a table
- *
- * $arrToInsert may be a single associative array, or an array of these with numeric keys, for
- * multi-row insert.
- *
- * Usually aborts on failure
- * If errors are explicitly ignored, returns success
- * @param string $table
- * @param array $arrToInsert
- * @param string $fname
- * @param array $options
- * @return bool
- * @throws Exception
- */
- public function insert( $table, $arrToInsert, $fname = __METHOD__, $options = array() ) {
- # No rows to insert, easy just return now
- if ( !count( $arrToInsert ) ) {
- return true;
- }
-
- if ( !is_array( $options ) ) {
- $options = array( $options );
- }
-
- $table = $this->tableName( $table );
-
- if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) { // Not multi row
- $arrToInsert = array( 0 => $arrToInsert ); // make everything multi row compatible
- }
-
- // We know the table we're inserting into, get its identity column
- $identity = null;
- // strip matching square brackets and the db/schema from table name
- $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
- $tableRaw = array_pop( $tableRawArr );
- $res = $this->doQuery(
- "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS " .
- "WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'"
- );
- if ( $res && sqlsrv_has_rows( $res ) ) {
- // There is an identity for this table.
- $identityArr = sqlsrv_fetch_array( $res, SQLSRV_FETCH_ASSOC );
- $identity = array_pop( $identityArr );
- }
- sqlsrv_free_stmt( $res );
-
- // Determine binary/varbinary fields so we can encode data as a hex string like 0xABCDEF
- $binaryColumns = $this->getBinaryColumns( $table );
-
- // INSERT IGNORE is not supported by SQL Server
- // remove IGNORE from options list and set ignore flag to true
- if ( in_array( 'IGNORE', $options ) ) {
- $options = array_diff( $options, array( 'IGNORE' ) );
- $this->mIgnoreDupKeyErrors = true;
- }
-
- foreach ( $arrToInsert as $a ) {
- // start out with empty identity column, this is so we can return
- // it as a result of the insert logic
- $sqlPre = '';
- $sqlPost = '';
- $identityClause = '';
-
- // if we have an identity column
- if ( $identity ) {
- // iterate through
- foreach ( $a as $k => $v ) {
- if ( $k == $identity ) {
- if ( !is_null( $v ) ) {
- // there is a value being passed to us,
- // we need to turn on and off inserted identity
- $sqlPre = "SET IDENTITY_INSERT $table ON;";
- $sqlPost = ";SET IDENTITY_INSERT $table OFF;";
- } else {
- // we can't insert NULL into an identity column,
- // so remove the column from the insert.
- unset( $a[$k] );
- }
- }
- }
-
- // we want to output an identity column as result
- $identityClause = "OUTPUT INSERTED.$identity ";
- }
-
- $keys = array_keys( $a );
-
- // Build the actual query
- $sql = $sqlPre . 'INSERT ' . implode( ' ', $options ) .
- " INTO $table (" . implode( ',', $keys ) . ") $identityClause VALUES (";
-
- $first = true;
- foreach ( $a as $key => $value ) {
- if ( isset( $binaryColumns[$key] ) ) {
- $value = new MssqlBlob( $value );
- }
- if ( $first ) {
- $first = false;
- } else {
- $sql .= ',';
- }
- if ( is_null( $value ) ) {
- $sql .= 'null';
- } elseif ( is_array( $value ) || is_object( $value ) ) {
- if ( is_object( $value ) && $value instanceof Blob ) {
- $sql .= $this->addQuotes( $value );
- } else {
- $sql .= $this->addQuotes( serialize( $value ) );
- }
- } else {
- $sql .= $this->addQuotes( $value );
- }
- }
- $sql .= ')' . $sqlPost;
-
- // Run the query
- $this->mScrollableCursor = false;
- try {
- $ret = $this->query( $sql );
- } catch ( Exception $e ) {
- $this->mScrollableCursor = true;
- $this->mIgnoreDupKeyErrors = false;
- throw $e;
- }
- $this->mScrollableCursor = true;
-
- if ( !is_null( $identity ) ) {
- // then we want to get the identity column value we were assigned and save it off
- $row = $ret->fetchObject();
- if ( is_object( $row ) ) {
- $this->mInsertId = $row->$identity;
- }
- }
- }
- $this->mIgnoreDupKeyErrors = false;
- return $ret;
- }
-
- /**
- * INSERT SELECT wrapper
- * $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
- * Source items may be literals rather than field names, but strings should
- * be quoted with Database::addQuotes().
- * @param string $destTable
- * @param array|string $srcTable May be an array of tables.
- * @param array $varMap
- * @param array $conds May be "*" to copy the whole table.
- * @param string $fname
- * @param array $insertOptions
- * @param array $selectOptions
- * @return null|ResultWrapper
- * @throws Exception
- */
- public function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
- $insertOptions = array(), $selectOptions = array()
- ) {
- $this->mScrollableCursor = false;
- try {
- $ret = parent::insertSelect(
- $destTable,
- $srcTable,
- $varMap,
- $conds,
- $fname,
- $insertOptions,
- $selectOptions
- );
- } catch ( Exception $e ) {
- $this->mScrollableCursor = true;
- throw $e;
- }
- $this->mScrollableCursor = true;
-
- return $ret;
- }
-
- /**
- * UPDATE wrapper. Takes a condition array and a SET array.
- *
- * @param string $table Name of the table to UPDATE. This will be passed through
- * DatabaseBase::tableName().
- *
- * @param array $values An array of values to SET. For each array element,
- * the key gives the field name, and the value gives the data
- * to set that field to. The data will be quoted by
- * DatabaseBase::addQuotes().
- *
- * @param array $conds An array of conditions (WHERE). See
- * DatabaseBase::select() for the details of the format of
- * condition arrays. Use '*' to update all rows.
- *
- * @param string $fname The function name of the caller (from __METHOD__),
- * for logging and profiling.
- *
- * @param array $options An array of UPDATE options, can be:
- * - IGNORE: Ignore unique key conflicts
- * - LOW_PRIORITY: MySQL-specific, see MySQL manual.
- * @return bool
- * @throws DBUnexpectedError
- * @throws Exception
- * @throws MWException
- */
- function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
- $table = $this->tableName( $table );
- $binaryColumns = $this->getBinaryColumns( $table );
-
- $opts = $this->makeUpdateOptions( $options );
- $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET, $binaryColumns );
-
- if ( $conds !== array() && $conds !== '*' ) {
- $sql .= " WHERE " . $this->makeList( $conds, LIST_AND, $binaryColumns );
- }
-
- $this->mScrollableCursor = false;
- try {
- $ret = $this->query( $sql );
- } catch ( Exception $e ) {
- $this->mScrollableCursor = true;
- throw $e;
- }
- $this->mScrollableCursor = true;
- return true;
- }
-
- /**
- * Makes an encoded list of strings from an array
- * @param array $a Containing the data
- * @param int $mode Constant
- * - LIST_COMMA: comma separated, no field names
- * - LIST_AND: ANDed WHERE clause (without the WHERE). See
- * the documentation for $conds in DatabaseBase::select().
- * - LIST_OR: ORed WHERE clause (without the WHERE)
- * - LIST_SET: comma separated with field names, like a SET clause
- * - LIST_NAMES: comma separated field names
- * @param array $binaryColumns Contains a list of column names that are binary types
- * This is a custom parameter only present for MS SQL.
- *
- * @throws MWException|DBUnexpectedError
- * @return string
- */
- public function makeList( $a, $mode = LIST_COMMA, $binaryColumns = array() ) {
- if ( !is_array( $a ) ) {
- throw new DBUnexpectedError( $this,
- 'DatabaseBase::makeList called with incorrect parameters' );
- }
-
- if ( $mode != LIST_NAMES ) {
- // In MS SQL, values need to be specially encoded when they are
- // inserted into binary fields. Perform this necessary encoding
- // for the specified set of columns.
- foreach ( array_keys( $a ) as $field ) {
- if ( !isset( $binaryColumns[$field] ) ) {
- continue;
- }
-
- if ( is_array( $a[$field] ) ) {
- foreach ( $a[$field] as &$v ) {
- $v = new MssqlBlob( $v );
- }
- unset( $v );
- } else {
- $a[$field] = new MssqlBlob( $a[$field] );
- }
- }
- }
-
- return parent::makeList( $a, $mode );
- }
-
- /**
- * @param string $table
- * @param string $field
- * @return int Returns the size of a text field, or -1 for "unlimited"
- */
- public function textFieldSize( $table, $field ) {
- $table = $this->tableName( $table );
- $sql = "SELECT CHARACTER_MAXIMUM_LENGTH,DATA_TYPE FROM INFORMATION_SCHEMA.Columns
- WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'";
- $res = $this->query( $sql );
- $row = $this->fetchRow( $res );
- $size = -1;
- if ( strtolower( $row['DATA_TYPE'] ) != 'text' ) {
- $size = $row['CHARACTER_MAXIMUM_LENGTH'];
- }
-
- return $size;
- }
-
- /**
- * Construct a LIMIT query with optional offset
- * This is used for query pages
- *
- * @param string $sql SQL query we will append the limit too
- * @param int $limit The SQL limit
- * @param bool|int $offset The SQL offset (default false)
- * @return array|string
- * @throws DBUnexpectedError
- */
- public function limitResult( $sql, $limit, $offset = false ) {
- if ( $offset === false || $offset == 0 ) {
- if ( strpos( $sql, "SELECT" ) === false ) {
- return "TOP {$limit} " . $sql;
- } else {
- return preg_replace( '/\bSELECT(\s+DISTINCT)?\b/Dsi',
- 'SELECT$1 TOP ' . $limit, $sql, 1 );
- }
- } else {
- // This one is fun, we need to pull out the select list as well as any ORDER BY clause
- $select = $orderby = array();
- $s1 = preg_match( '#SELECT\s+(.+?)\s+FROM#Dis', $sql, $select );
- $s2 = preg_match( '#(ORDER BY\s+.+?)(\s*FOR XML .*)?$#Dis', $sql, $orderby );
- $overOrder = $postOrder = '';
- $first = $offset + 1;
- $last = $offset + $limit;
- $sub1 = 'sub_' . $this->mSubqueryId;
- $sub2 = 'sub_' . ( $this->mSubqueryId + 1 );
- $this->mSubqueryId += 2;
- if ( !$s1 ) {
- // wat
- throw new DBUnexpectedError( $this, "Attempting to LIMIT a non-SELECT query\n" );
- }
- if ( !$s2 ) {
- // no ORDER BY
- $overOrder = 'ORDER BY (SELECT 1)';
- } else {
- if ( !isset( $orderby[2] ) || !$orderby[2] ) {
- // don't need to strip it out if we're using a FOR XML clause
- $sql = str_replace( $orderby[1], '', $sql );
- }
- $overOrder = $orderby[1];
- $postOrder = ' ' . $overOrder;
- }
- $sql = "SELECT {$select[1]}
- FROM (
- SELECT ROW_NUMBER() OVER({$overOrder}) AS rowNumber, *
- FROM ({$sql}) {$sub1}
- ) {$sub2}
- WHERE rowNumber BETWEEN {$first} AND {$last}{$postOrder}";
-
- return $sql;
- }
- }
-
- /**
- * If there is a limit clause, parse it, strip it, and pass the remaining
- * SQL through limitResult() with the appropriate parameters. Not the
- * prettiest solution, but better than building a whole new parser. This
- * exists becase there are still too many extensions that don't use dynamic
- * sql generation.
- *
- * @param string $sql
- * @return array|mixed|string
- */
- public function LimitToTopN( $sql ) {
- // Matches: LIMIT {[offset,] row_count | row_count OFFSET offset}
- $pattern = '/\bLIMIT\s+((([0-9]+)\s*,\s*)?([0-9]+)(\s+OFFSET\s+([0-9]+))?)/i';
- if ( preg_match( $pattern, $sql, $matches ) ) {
- $row_count = $matches[4];
- $offset = $matches[3] ?: $matches[6] ?: false;
-
- // strip the matching LIMIT clause out
- $sql = str_replace( $matches[0], '', $sql );
-
- return $this->limitResult( $sql, $row_count, $offset );
- }
-
- return $sql;
- }
-
- /**
- * @return string Wikitext of a link to the server software's web site
- */
- public function getSoftwareLink() {
- return "[{{int:version-db-mssql-url}} MS SQL Server]";
- }
-
- /**
- * @return string Version information from the database
- */
- public function getServerVersion() {
- $server_info = sqlsrv_server_info( $this->mConn );
- $version = 'Error';
- if ( isset( $server_info['SQLServerVersion'] ) ) {
- $version = $server_info['SQLServerVersion'];
- }
-
- return $version;
- }
-
- /**
- * @param string $table
- * @param string $fname
- * @return bool
- */
- public function tableExists( $table, $fname = __METHOD__ ) {
- list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
-
- if ( $db !== false ) {
- // remote database
- wfDebug( "Attempting to call tableExists on a remote table" );
- return false;
- }
-
- if ( $schema === false ) {
- global $wgDBmwschema;
- $schema = $wgDBmwschema;
- }
-
- $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.TABLES
- WHERE TABLE_TYPE = 'BASE TABLE'
- AND TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table'" );
-
- if ( $res->numRows() ) {
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Query whether a given column exists in the mediawiki schema
- * @param string $table
- * @param string $field
- * @param string $fname
- * @return bool
- */
- public function fieldExists( $table, $field, $fname = __METHOD__ ) {
- list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
-
- if ( $db !== false ) {
- // remote database
- wfDebug( "Attempting to call fieldExists on a remote table" );
- return false;
- }
-
- $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
- WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
-
- if ( $res->numRows() ) {
- return true;
- } else {
- return false;
- }
- }
-
- public function fieldInfo( $table, $field ) {
- list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
-
- if ( $db !== false ) {
- // remote database
- wfDebug( "Attempting to call fieldInfo on a remote table" );
- return false;
- }
-
- $res = $this->query( "SELECT * FROM INFORMATION_SCHEMA.COLUMNS
- WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
-
- $meta = $res->fetchRow();
- if ( $meta ) {
- return new MssqlField( $meta );
- }
-
- return false;
- }
-
- /**
- * Begin a transaction, committing any previously open transaction
- * @param string $fname
- */
- protected function doBegin( $fname = __METHOD__ ) {
- sqlsrv_begin_transaction( $this->mConn );
- $this->mTrxLevel = 1;
- }
-
- /**
- * End a transaction
- * @param string $fname
- */
- protected function doCommit( $fname = __METHOD__ ) {
- sqlsrv_commit( $this->mConn );
- $this->mTrxLevel = 0;
- }
-
- /**
- * Rollback a transaction.
- * No-op on non-transactional databases.
- * @param string $fname
- */
- protected function doRollback( $fname = __METHOD__ ) {
- sqlsrv_rollback( $this->mConn );
- $this->mTrxLevel = 0;
- }
-
- /**
- * Escapes a identifier for use inm SQL.
- * Throws an exception if it is invalid.
- * Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx
- * @param string $identifier
- * @throws MWException
- * @return string
- */
- private function escapeIdentifier( $identifier ) {
- if ( strlen( $identifier ) == 0 ) {
- throw new MWException( "An identifier must not be empty" );
- }
- if ( strlen( $identifier ) > 128 ) {
- throw new MWException( "The identifier '$identifier' is too long (max. 128)" );
- }
- if ( ( strpos( $identifier, '[' ) !== false )
- || ( strpos( $identifier, ']' ) !== false )
- ) {
- // It may be allowed if you quoted with double quotation marks, but
- // that would break if QUOTED_IDENTIFIER is OFF
- throw new MWException( "Square brackets are not allowed in '$identifier'" );
- }
-
- return "[$identifier]";
- }
-
- /**
- * @param string $s
- * @return string
- */
- public function strencode( $s ) {
- // Should not be called by us
-
- return str_replace( "'", "''", $s );
- }
-
- /**
- * @param string|Blob $s
- * @return string
- */
- public function addQuotes( $s ) {
- if ( $s instanceof MssqlBlob ) {
- return $s->fetch();
- } elseif ( $s instanceof Blob ) {
- // this shouldn't really ever be called, but it's here if needed
- // (and will quite possibly make the SQL error out)
- $blob = new MssqlBlob( $s->fetch() );
- return $blob->fetch();
- } else {
- if ( is_bool( $s ) ) {
- $s = $s ? 1 : 0;
- }
- return parent::addQuotes( $s );
- }
- }
-
- /**
- * @param string $s
- * @return string
- */
- public function addIdentifierQuotes( $s ) {
- // http://msdn.microsoft.com/en-us/library/aa223962.aspx
- return '[' . $s . ']';
- }
-
- /**
- * @param string $name
- * @return bool
- */
- public function isQuotedIdentifier( $name ) {
- return strlen( $name ) && $name[0] == '[' && substr( $name, -1, 1 ) == ']';
- }
-
- /**
- * @param string $db
- * @return bool
- */
- public function selectDB( $db ) {
- try {
- $this->mDBname = $db;
- $this->query( "USE $db" );
- return true;
- } catch ( Exception $e ) {
- return false;
- }
- }
-
- /**
- * @param array $options An associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
- * @return array
- */
- public function makeSelectOptions( $options ) {
- $tailOpts = '';
- $startOpts = '';
-
- $noKeyOptions = array();
- foreach ( $options as $key => $option ) {
- if ( is_numeric( $key ) ) {
- $noKeyOptions[$option] = true;
- }
- }
-
- $tailOpts .= $this->makeGroupByWithHaving( $options );
-
- $tailOpts .= $this->makeOrderBy( $options );
-
- if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
- $startOpts .= 'DISTINCT';
- }
-
- if ( isset( $noKeyOptions['FOR XML'] ) ) {
- // used in group concat field emulation
- $tailOpts .= " FOR XML PATH('')";
- }
-
- // we want this to be compatible with the output of parent::makeSelectOptions()
- return array( $startOpts, '', $tailOpts, '' );
- }
-
- /**
- * Get the type of the DBMS, as it appears in $wgDBtype.
- * @return string
- */
- public function getType() {
- return 'mssql';
- }
-
- /**
- * @param array $stringList
- * @return string
- */
- public function buildConcat( $stringList ) {
- return implode( ' + ', $stringList );
- }
-
- /**
- * Build a GROUP_CONCAT or equivalent statement for a query.
- * MS SQL doesn't have GROUP_CONCAT so we emulate it with other stuff (and boy is it nasty)
- *
- * This is useful for combining a field for several rows into a single string.
- * NULL values will not appear in the output, duplicated values will appear,
- * and the resulting delimiter-separated values have no defined sort order.
- * Code using the results may need to use the PHP unique() or sort() methods.
- *
- * @param string $delim Glue to bind the results together
- * @param string|array $table Table name
- * @param string $field Field name
- * @param string|array $conds Conditions
- * @param string|array $join_conds Join conditions
- * @return string SQL text
- * @since 1.23
- */
- public function buildGroupConcatField( $delim, $table, $field, $conds = '',
- $join_conds = array()
- ) {
- $gcsq = 'gcsq_' . $this->mSubqueryId;
- $this->mSubqueryId++;
-
- $delimLen = strlen( $delim );
- $fld = "{$field} + {$this->addQuotes( $delim )}";
- $sql = "(SELECT LEFT({$field}, LEN({$field}) - {$delimLen}) FROM ("
- . $this->selectSQLText( $table, $fld, $conds, null, array( 'FOR XML' ), $join_conds )
- . ") {$gcsq} ({$field}))";
-
- return $sql;
- }
-
- /**
- * @return string
- */
- public function getSearchEngine() {
- return "SearchMssql";
- }
-
- /**
- * Returns an associative array for fields that are of type varbinary, binary, or image
- * $table can be either a raw table name or passed through tableName() first
- * @param string $table
- * @return array
- */
- private function getBinaryColumns( $table ) {
- $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
- $tableRaw = array_pop( $tableRawArr );
-
- if ( $this->mBinaryColumnCache === null ) {
- $this->populateColumnCaches();
- }
-
- return isset( $this->mBinaryColumnCache[$tableRaw] )
- ? $this->mBinaryColumnCache[$tableRaw]
- : array();
- }
-
- /**
- * @param string $table
- * @return array
- */
- private function getBitColumns( $table ) {
- $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
- $tableRaw = array_pop( $tableRawArr );
-
- if ( $this->mBitColumnCache === null ) {
- $this->populateColumnCaches();
- }
-
- return isset( $this->mBitColumnCache[$tableRaw] )
- ? $this->mBitColumnCache[$tableRaw]
- : array();
- }
-
- private function populateColumnCaches() {
- $res = $this->select( 'INFORMATION_SCHEMA.COLUMNS', '*',
- array(
- 'TABLE_CATALOG' => $this->mDBname,
- 'TABLE_SCHEMA' => $this->mSchema,
- 'DATA_TYPE' => array( 'varbinary', 'binary', 'image', 'bit' )
- ) );
-
- $this->mBinaryColumnCache = array();
- $this->mBitColumnCache = array();
- foreach ( $res as $row ) {
- if ( $row->DATA_TYPE == 'bit' ) {
- $this->mBitColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
- } else {
- $this->mBinaryColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
- }
- }
- }
-
- /**
- * @param string $name
- * @param string $format
- * @return string
- */
- function tableName( $name, $format = 'quoted' ) {
- # Replace reserved words with better ones
- switch ( $name ) {
- case 'user':
- return $this->realTableName( 'mwuser', $format );
- default:
- return $this->realTableName( $name, $format );
- }
- }
-
- /**
- * call this instead of tableName() in the updater when renaming tables
- * @param string $name
- * @param string $format One of quoted, raw, or split
- * @return string
- */
- function realTableName( $name, $format = 'quoted' ) {
- $table = parent::tableName( $name, $format );
- if ( $format == 'split' ) {
- // Used internally, we want the schema split off from the table name and returned
- // as a list with 3 elements (database, schema, table)
- $table = explode( '.', $table );
- while ( count( $table ) < 3 ) {
- array_unshift( $table, false );
- }
- }
- return $table;
- }
-
- /**
- * Called in the installer and updater.
- * Probably doesn't need to be called anywhere else in the codebase.
- * @param bool|null $value
- * @return bool|null
- */
- public function prepareStatements( $value = null ) {
- return wfSetVar( $this->mPrepareStatements, $value );
- }
-
- /**
- * Called in the installer and updater.
- * Probably doesn't need to be called anywhere else in the codebase.
- * @param bool|null $value
- * @return bool|null
- */
- public function scrollableCursor( $value = null ) {
- return wfSetVar( $this->mScrollableCursor, $value );
- }
-} // end DatabaseMssql class
-
-/**
- * Utility class.
- *
- * @ingroup Database
- */
-class MssqlField implements Field {
- private $name, $tableName, $default, $max_length, $nullable, $type;
-
- function __construct( $info ) {
- $this->name = $info['COLUMN_NAME'];
- $this->tableName = $info['TABLE_NAME'];
- $this->default = $info['COLUMN_DEFAULT'];
- $this->max_length = $info['CHARACTER_MAXIMUM_LENGTH'];
- $this->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' );
- $this->type = $info['DATA_TYPE'];
- }
-
- function name() {
- return $this->name;
- }
-
- function tableName() {
- return $this->tableName;
- }
-
- function defaultValue() {
- return $this->default;
- }
-
- function maxLength() {
- return $this->max_length;
- }
-
- function isNullable() {
- return $this->nullable;
- }
-
- function type() {
- return $this->type;
- }
-}
-
-class MssqlBlob extends Blob {
- public function __construct( $data ) {
- if ( $data instanceof MssqlBlob ) {
- return $data;
- } elseif ( $data instanceof Blob ) {
- $this->mData = $data->fetch();
- } elseif ( is_array( $data ) && is_object( $data ) ) {
- $this->mData = serialize( $data );
- } else {
- $this->mData = $data;
- }
- }
-
- /**
- * Returns an unquoted hex representation of a binary string
- * for insertion into varbinary-type fields
- * @return string
- */
- public function fetch() {
- if ( $this->mData === null ) {
- return 'null';
- }
-
- $ret = '0x';
- $dataLength = strlen( $this->mData );
- for ( $i = 0; $i < $dataLength; $i++ ) {
- $ret .= bin2hex( pack( 'C', ord( $this->mData[$i] ) ) );
- }
-
- return $ret;
- }
-}
-
-class MssqlResultWrapper extends ResultWrapper {
- private $mSeekTo = null;
-
- /**
- * @return stdClass|bool
- */
- public function fetchObject() {
- $res = $this->result;
-
- if ( $this->mSeekTo !== null ) {
- $result = sqlsrv_fetch_object( $res, 'stdClass', array(),
- SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
- $this->mSeekTo = null;
- } else {
- $result = sqlsrv_fetch_object( $res );
- }
-
- // MediaWiki expects us to return boolean false when there are no more rows instead of null
- if ( $result === null ) {
- return false;
- }
-
- return $result;
- }
-
- /**
- * @return array|bool
- */
- public function fetchRow() {
- $res = $this->result;
-
- if ( $this->mSeekTo !== null ) {
- $result = sqlsrv_fetch_array( $res, SQLSRV_FETCH_BOTH,
- SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
- $this->mSeekTo = null;
- } else {
- $result = sqlsrv_fetch_array( $res );
- }
-
- // MediaWiki expects us to return boolean false when there are no more rows instead of null
- if ( $result === null ) {
- return false;
- }
-
- return $result;
- }
-
- /**
- * @param int $row
- * @return bool
- */
- public function seek( $row ) {
- $res = $this->result;
-
- // check bounds
- $numRows = $this->db->numRows( $res );
- $row = intval( $row );
-
- if ( $numRows === 0 ) {
- return false;
- } elseif ( $row < 0 || $row > $numRows - 1 ) {
- return false;
- }
-
- // Unlike MySQL, the seek actually happens on the next access
- $this->mSeekTo = $row;
- return true;
- }
-}
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
deleted file mode 100644
index 87c31646..00000000
--- a/includes/db/DatabaseOracle.php
+++ /dev/null
@@ -1,1546 +0,0 @@
-<?php
-/**
- * This is the Oracle database abstraction layer.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-
-/**
- * The oci8 extension is fairly weak and doesn't support oci_num_rows, among
- * other things. We use a wrapper class to handle that and other
- * Oracle-specific bits, like converting column names back to lowercase.
- * @ingroup Database
- */
-class ORAResult {
- private $rows;
- private $cursor;
- private $nrows;
-
- private $columns = array();
-
- private function array_unique_md( $array_in ) {
- $array_out = array();
- $array_hashes = array();
-
- foreach ( $array_in as $item ) {
- $hash = md5( serialize( $item ) );
- if ( !isset( $array_hashes[$hash] ) ) {
- $array_hashes[$hash] = $hash;
- $array_out[] = $item;
- }
- }
-
- return $array_out;
- }
-
- /**
- * @param DatabaseBase $db
- * @param resource $stmt A valid OCI statement identifier
- * @param bool $unique
- */
- function __construct( &$db, $stmt, $unique = false ) {
- $this->db =& $db;
-
- $this->nrows = oci_fetch_all( $stmt, $this->rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM );
- if ( $this->nrows === false ) {
- $e = oci_error( $stmt );
- $db->reportQueryError( $e['message'], $e['code'], '', __METHOD__ );
- $this->free();
-
- return;
- }
-
- if ( $unique ) {
- $this->rows = $this->array_unique_md( $this->rows );
- $this->nrows = count( $this->rows );
- }
-
- if ( $this->nrows > 0 ) {
- foreach ( $this->rows[0] as $k => $v ) {
- $this->columns[$k] = strtolower( oci_field_name( $stmt, $k + 1 ) );
- }
- }
-
- $this->cursor = 0;
- oci_free_statement( $stmt );
- }
-
- public function free() {
- unset( $this->db );
- }
-
- public function seek( $row ) {
- $this->cursor = min( $row, $this->nrows );
- }
-
- public function numRows() {
- return $this->nrows;
- }
-
- public function numFields() {
- return count( $this->columns );
- }
-
- public function fetchObject() {
- if ( $this->cursor >= $this->nrows ) {
- return false;
- }
- $row = $this->rows[$this->cursor++];
- $ret = new stdClass();
- foreach ( $row as $k => $v ) {
- $lc = $this->columns[$k];
- $ret->$lc = $v;
- }
-
- return $ret;
- }
-
- public function fetchRow() {
- if ( $this->cursor >= $this->nrows ) {
- return false;
- }
-
- $row = $this->rows[$this->cursor++];
- $ret = array();
- foreach ( $row as $k => $v ) {
- $lc = $this->columns[$k];
- $ret[$lc] = $v;
- $ret[$k] = $v;
- }
-
- return $ret;
- }
-}
-
-/**
- * Utility class.
- * @ingroup Database
- */
-class ORAField implements Field {
- private $name, $tablename, $default, $max_length, $nullable,
- $is_pk, $is_unique, $is_multiple, $is_key, $type;
-
- function __construct( $info ) {
- $this->name = $info['column_name'];
- $this->tablename = $info['table_name'];
- $this->default = $info['data_default'];
- $this->max_length = $info['data_length'];
- $this->nullable = $info['not_null'];
- $this->is_pk = isset( $info['prim'] ) && $info['prim'] == 1 ? 1 : 0;
- $this->is_unique = isset( $info['uniq'] ) && $info['uniq'] == 1 ? 1 : 0;
- $this->is_multiple = isset( $info['nonuniq'] ) && $info['nonuniq'] == 1 ? 1 : 0;
- $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
- $this->type = $info['data_type'];
- }
-
- function name() {
- return $this->name;
- }
-
- function tableName() {
- return $this->tablename;
- }
-
- function defaultValue() {
- return $this->default;
- }
-
- function maxLength() {
- return $this->max_length;
- }
-
- function isNullable() {
- return $this->nullable;
- }
-
- function isKey() {
- return $this->is_key;
- }
-
- function isMultipleKey() {
- return $this->is_multiple;
- }
-
- function type() {
- return $this->type;
- }
-}
-
-/**
- * @ingroup Database
- */
-class DatabaseOracle extends DatabaseBase {
- /** @var resource */
- protected $mLastResult = null;
-
- /** @var int The number of rows affected as an integer */
- protected $mAffectedRows;
-
- /** @var int */
- private $mInsertId = null;
-
- /** @var bool */
- private $ignoreDupValOnIndex = false;
-
- /** @var bool|array */
- private $sequenceData = null;
-
- /** @var string Character set for Oracle database */
- private $defaultCharset = 'AL32UTF8';
-
- /** @var array */
- private $mFieldInfoCache = array();
-
- function __construct( array $p ) {
- global $wgDBprefix;
-
- if ( $p['tablePrefix'] == 'get from global' ) {
- $p['tablePrefix'] = $wgDBprefix;
- }
- $p['tablePrefix'] = strtoupper( $p['tablePrefix'] );
- parent::__construct( $p );
- Hooks::run( 'DatabaseOraclePostInit', array( $this ) );
- }
-
- function __destruct() {
- if ( $this->mOpened ) {
- MediaWiki\suppressWarnings();
- $this->close();
- MediaWiki\restoreWarnings();
- }
- }
-
- function getType() {
- return 'oracle';
- }
-
- function cascadingDeletes() {
- return true;
- }
-
- function cleanupTriggers() {
- return true;
- }
-
- function strictIPs() {
- return true;
- }
-
- function realTimestamps() {
- return true;
- }
-
- function implicitGroupby() {
- return false;
- }
-
- function implicitOrderby() {
- return false;
- }
-
- function searchableIPs() {
- return true;
- }
-
- /**
- * Usually aborts on failure
- * @param string $server
- * @param string $user
- * @param string $password
- * @param string $dbName
- * @throws DBConnectionError
- * @return DatabaseBase|null
- */
- function open( $server, $user, $password, $dbName ) {
- global $wgDBOracleDRCP;
- if ( !function_exists( 'oci_connect' ) ) {
- throw new DBConnectionError(
- $this,
- "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n " .
- "(Note: if you recently installed PHP, you may need to restart your webserver\n " .
- "and database)\n" );
- }
-
- $this->close();
- $this->mUser = $user;
- $this->mPassword = $password;
- // changed internal variables functions
- // mServer now holds the TNS endpoint
- // mDBname is schema name if different from username
- if ( !$server ) {
- // backward compatibillity (server used to be null and TNS was supplied in dbname)
- $this->mServer = $dbName;
- $this->mDBname = $user;
- } else {
- $this->mServer = $server;
- if ( !$dbName ) {
- $this->mDBname = $user;
- } else {
- $this->mDBname = $dbName;
- }
- }
-
- if ( !strlen( $user ) ) { # e.g. the class is being loaded
- return null;
- }
-
- if ( $wgDBOracleDRCP ) {
- $this->setFlag( DBO_PERSISTENT );
- }
-
- $session_mode = $this->mFlags & DBO_SYSDBA ? OCI_SYSDBA : OCI_DEFAULT;
-
- MediaWiki\suppressWarnings();
- if ( $this->mFlags & DBO_PERSISTENT ) {
- $this->mConn = oci_pconnect(
- $this->mUser,
- $this->mPassword,
- $this->mServer,
- $this->defaultCharset,
- $session_mode
- );
- } elseif ( $this->mFlags & DBO_DEFAULT ) {
- $this->mConn = oci_new_connect(
- $this->mUser,
- $this->mPassword,
- $this->mServer,
- $this->defaultCharset,
- $session_mode
- );
- } else {
- $this->mConn = oci_connect(
- $this->mUser,
- $this->mPassword,
- $this->mServer,
- $this->defaultCharset,
- $session_mode
- );
- }
- MediaWiki\restoreWarnings();
-
- if ( $this->mUser != $this->mDBname ) {
- //change current schema in session
- $this->selectDB( $this->mDBname );
- }
-
- if ( !$this->mConn ) {
- throw new DBConnectionError( $this, $this->lastError() );
- }
-
- $this->mOpened = true;
-
- # removed putenv calls because they interfere with the system globaly
- $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
- $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
- $this->doQuery( 'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=\'.,\'' );
-
- return $this->mConn;
- }
-
- /**
- * Closes a database connection, if it is open
- * Returns success, true if already closed
- * @return bool
- */
- protected function closeConnection() {
- return oci_close( $this->mConn );
- }
-
- function execFlags() {
- return $this->mTrxLevel ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
- }
-
- protected function doQuery( $sql ) {
- wfDebug( "SQL: [$sql]\n" );
- if ( !StringUtils::isUtf8( $sql ) ) {
- throw new MWException( "SQL encoding is invalid\n$sql" );
- }
-
- // handle some oracle specifics
- // remove AS column/table/subquery namings
- if ( !$this->getFlag( DBO_DDLMODE ) ) {
- $sql = preg_replace( '/ as /i', ' ', $sql );
- }
-
- // Oracle has issues with UNION clause if the statement includes LOB fields
- // So we do a UNION ALL and then filter the results array with array_unique
- $union_unique = ( preg_match( '/\/\* UNION_UNIQUE \*\/ /', $sql ) != 0 );
- // EXPLAIN syntax in Oracle is EXPLAIN PLAN FOR and it return nothing
- // you have to select data from plan table after explain
- $explain_id = MWTimestamp::getLocalInstance()->format( 'dmYHis' );
-
- $sql = preg_replace(
- '/^EXPLAIN /',
- 'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR',
- $sql,
- 1,
- $explain_count
- );
-
- MediaWiki\suppressWarnings();
-
- if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
- $e = oci_error( $this->mConn );
- $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
-
- return false;
- }
-
- if ( !oci_execute( $stmt, $this->execFlags() ) ) {
- $e = oci_error( $stmt );
- if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
- $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
-
- return false;
- }
- }
-
- MediaWiki\restoreWarnings();
-
- if ( $explain_count > 0 ) {
- return $this->doQuery( 'SELECT id, cardinality "ROWS" FROM plan_table ' .
- 'WHERE statement_id = \'' . $explain_id . '\'' );
- } elseif ( oci_statement_type( $stmt ) == 'SELECT' ) {
- return new ORAResult( $this, $stmt, $union_unique );
- } else {
- $this->mAffectedRows = oci_num_rows( $stmt );
-
- return true;
- }
- }
-
- function queryIgnore( $sql, $fname = '' ) {
- return $this->query( $sql, $fname, true );
- }
-
- /**
- * Frees resources associated with the LOB descriptor
- * @param ResultWrapper|resource $res
- */
- function freeResult( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- $res->free();
- }
-
- /**
- * @param ResultWrapper|stdClass $res
- * @return mixed
- */
- function fetchObject( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $res->fetchObject();
- }
-
- function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $res->fetchRow();
- }
-
- function numRows( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $res->numRows();
- }
-
- function numFields( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $res->numFields();
- }
-
- function fieldName( $stmt, $n ) {
- return oci_field_name( $stmt, $n );
- }
-
- /**
- * This must be called after nextSequenceVal
- * @return null|int
- */
- function insertId() {
- return $this->mInsertId;
- }
-
- /**
- * @param mixed $res
- * @param int $row
- */
- function dataSeek( $res, $row ) {
- if ( $res instanceof ORAResult ) {
- $res->seek( $row );
- } else {
- $res->result->seek( $row );
- }
- }
-
- function lastError() {
- if ( $this->mConn === false ) {
- $e = oci_error();
- } else {
- $e = oci_error( $this->mConn );
- }
-
- return $e['message'];
- }
-
- function lastErrno() {
- if ( $this->mConn === false ) {
- $e = oci_error();
- } else {
- $e = oci_error( $this->mConn );
- }
-
- return $e['code'];
- }
-
- function affectedRows() {
- return $this->mAffectedRows;
- }
-
- /**
- * Returns information about an index
- * If errors are explicitly ignored, returns NULL on failure
- * @param string $table
- * @param string $index
- * @param string $fname
- * @return bool
- */
- function indexInfo( $table, $index, $fname = __METHOD__ ) {
- return false;
- }
-
- function indexUnique( $table, $index, $fname = __METHOD__ ) {
- return false;
- }
-
- function insert( $table, $a, $fname = __METHOD__, $options = array() ) {
- if ( !count( $a ) ) {
- return true;
- }
-
- if ( !is_array( $options ) ) {
- $options = array( $options );
- }
-
- if ( in_array( 'IGNORE', $options ) ) {
- $this->ignoreDupValOnIndex = true;
- }
-
- if ( !is_array( reset( $a ) ) ) {
- $a = array( $a );
- }
-
- foreach ( $a as &$row ) {
- $this->insertOneRow( $table, $row, $fname );
- }
- $retVal = true;
-
- if ( in_array( 'IGNORE', $options ) ) {
- $this->ignoreDupValOnIndex = false;
- }
-
- return $retVal;
- }
-
- private function fieldBindStatement( $table, $col, &$val, $includeCol = false ) {
- $col_info = $this->fieldInfoMulti( $table, $col );
- $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
-
- $bind = '';
- if ( is_numeric( $col ) ) {
- $bind = $val;
- $val = null;
-
- return $bind;
- } elseif ( $includeCol ) {
- $bind = "$col = ";
- }
-
- if ( $val == '' && $val !== 0 && $col_type != 'BLOB' && $col_type != 'CLOB' ) {
- $val = null;
- }
-
- if ( $val === 'NULL' ) {
- $val = null;
- }
-
- if ( $val === null ) {
- if ( $col_info != false && $col_info->isNullable() == 0 && $col_info->defaultValue() != null ) {
- $bind .= 'DEFAULT';
- } else {
- $bind .= 'NULL';
- }
- } else {
- $bind .= ':' . $col;
- }
-
- return $bind;
- }
-
- /**
- * @param string $table
- * @param array $row
- * @param string $fname
- * @return bool
- * @throws DBUnexpectedError
- */
- private function insertOneRow( $table, $row, $fname ) {
- global $wgContLang;
-
- $table = $this->tableName( $table );
- // "INSERT INTO tables (a, b, c)"
- $sql = "INSERT INTO " . $table . " (" . join( ',', array_keys( $row ) ) . ')';
- $sql .= " VALUES (";
-
- // for each value, append ":key"
- $first = true;
- foreach ( $row as $col => &$val ) {
- if ( !$first ) {
- $sql .= ', ';
- } else {
- $first = false;
- }
- if ( $this->isQuotedIdentifier( $val ) ) {
- $sql .= $this->removeIdentifierQuotes( $val );
- unset( $row[$col] );
- } else {
- $sql .= $this->fieldBindStatement( $table, $col, $val );
- }
- }
- $sql .= ')';
-
- if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
- $e = oci_error( $this->mConn );
- $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
-
- return false;
- }
- foreach ( $row as $col => &$val ) {
- $col_info = $this->fieldInfoMulti( $table, $col );
- $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
-
- if ( $val === null ) {
- // do nothing ... null was inserted in statement creation
- } elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
- if ( is_object( $val ) ) {
- $val = $val->fetch();
- }
-
- // backward compatibility
- if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
- $val = $this->getInfinity();
- }
-
- $val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
- if ( oci_bind_by_name( $stmt, ":$col", $val, -1, SQLT_CHR ) === false ) {
- $e = oci_error( $stmt );
- $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
-
- return false;
- }
- } else {
- /** @var OCI_Lob[] $lob */
- if ( ( $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB ) ) === false ) {
- $e = oci_error( $stmt );
- throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
- }
-
- if ( is_object( $val ) ) {
- $val = $val->fetch();
- }
-
- if ( $col_type == 'BLOB' ) {
- $lob[$col]->writeTemporary( $val, OCI_TEMP_BLOB );
- oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_BLOB );
- } else {
- $lob[$col]->writeTemporary( $val, OCI_TEMP_CLOB );
- oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
- }
- }
- }
-
- MediaWiki\suppressWarnings();
-
- if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
- $e = oci_error( $stmt );
- if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
- $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
-
- return false;
- } else {
- $this->mAffectedRows = oci_num_rows( $stmt );
- }
- } else {
- $this->mAffectedRows = oci_num_rows( $stmt );
- }
-
- MediaWiki\restoreWarnings();
-
- if ( isset( $lob ) ) {
- foreach ( $lob as $lob_v ) {
- $lob_v->free();
- }
- }
-
- if ( !$this->mTrxLevel ) {
- oci_commit( $this->mConn );
- }
-
- return oci_free_statement( $stmt );
- }
-
- function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
- $insertOptions = array(), $selectOptions = array()
- ) {
- $destTable = $this->tableName( $destTable );
- if ( !is_array( $selectOptions ) ) {
- $selectOptions = array( $selectOptions );
- }
- list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
- if ( is_array( $srcTable ) ) {
- $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
- } else {
- $srcTable = $this->tableName( $srcTable );
- }
-
- if ( ( $sequenceData = $this->getSequenceData( $destTable ) ) !== false &&
- !isset( $varMap[$sequenceData['column']] )
- ) {
- $varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
- }
-
- // count-alias subselect fields to avoid abigious definition errors
- $i = 0;
- foreach ( $varMap as &$val ) {
- $val = $val . ' field' . ( $i++ );
- }
-
- $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
- " SELECT $startOpts " . implode( ',', $varMap ) .
- " FROM $srcTable $useIndex ";
- if ( $conds != '*' ) {
- $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
- }
- $sql .= " $tailOpts";
-
- if ( in_array( 'IGNORE', $insertOptions ) ) {
- $this->ignoreDupValOnIndex = true;
- }
-
- $retval = $this->query( $sql, $fname );
-
- if ( in_array( 'IGNORE', $insertOptions ) ) {
- $this->ignoreDupValOnIndex = false;
- }
-
- return $retval;
- }
-
- public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
- $fname = __METHOD__
- ) {
- if ( !count( $rows ) ) {
- return true; // nothing to do
- }
-
- if ( !is_array( reset( $rows ) ) ) {
- $rows = array( $rows );
- }
-
- $sequenceData = $this->getSequenceData( $table );
- if ( $sequenceData !== false ) {
- // add sequence column to each list of columns, when not set
- foreach ( $rows as &$row ) {
- if ( !isset( $row[$sequenceData['column']] ) ) {
- $row[$sequenceData['column']] =
- $this->addIdentifierQuotes( 'GET_SEQUENCE_VALUE(\'' .
- $sequenceData['sequence'] . '\')' );
- }
- }
- }
-
- return parent::upsert( $table, $rows, $uniqueIndexes, $set, $fname );
- }
-
- function tableName( $name, $format = 'quoted' ) {
- /*
- Replace reserved words with better ones
- Using uppercase because that's the only way Oracle can handle
- quoted tablenames
- */
- switch ( $name ) {
- case 'user':
- $name = 'MWUSER';
- break;
- case 'text':
- $name = 'PAGECONTENT';
- break;
- }
-
- return strtoupper( parent::tableName( $name, $format ) );
- }
-
- function tableNameInternal( $name ) {
- $name = $this->tableName( $name );
-
- return preg_replace( '/.*\.(.*)/', '$1', $name );
- }
-
- /**
- * Return the next in a sequence, save the value for retrieval via insertId()
- *
- * @param string $seqName
- * @return null|int
- */
- function nextSequenceValue( $seqName ) {
- $res = $this->query( "SELECT $seqName.nextval FROM dual" );
- $row = $this->fetchRow( $res );
- $this->mInsertId = $row[0];
-
- return $this->mInsertId;
- }
-
- /**
- * Return sequence_name if table has a sequence
- *
- * @param string $table
- * @return bool
- */
- private function getSequenceData( $table ) {
- if ( $this->sequenceData == null ) {
- $result = $this->doQuery( "SELECT lower(asq.sequence_name),
- lower(atc.table_name),
- lower(atc.column_name)
- FROM all_sequences asq, all_tab_columns atc
- WHERE decode(
- atc.table_name,
- '{$this->mTablePrefix}MWUSER',
- '{$this->mTablePrefix}USER',
- atc.table_name
- ) || '_' ||
- atc.column_name || '_SEQ' = '{$this->mTablePrefix}' || asq.sequence_name
- AND asq.sequence_owner = upper('{$this->mDBname}')
- AND atc.owner = upper('{$this->mDBname}')" );
-
- while ( ( $row = $result->fetchRow() ) !== false ) {
- $this->sequenceData[$row[1]] = array(
- 'sequence' => $row[0],
- 'column' => $row[2]
- );
- }
- }
- $table = strtolower( $this->removeIdentifierQuotes( $this->tableName( $table ) ) );
-
- return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false;
- }
-
- /**
- * Returns the size of a text field, or -1 for "unlimited"
- *
- * @param string $table
- * @param string $field
- * @return mixed
- */
- function textFieldSize( $table, $field ) {
- $fieldInfoData = $this->fieldInfo( $table, $field );
-
- return $fieldInfoData->maxLength();
- }
-
- function limitResult( $sql, $limit, $offset = false ) {
- if ( $offset === false ) {
- $offset = 0;
- }
-
- return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < (1 + $limit + $offset)";
- }
-
- function encodeBlob( $b ) {
- return new Blob( $b );
- }
-
- function decodeBlob( $b ) {
- if ( $b instanceof Blob ) {
- $b = $b->fetch();
- }
-
- return $b;
- }
-
- function unionQueries( $sqls, $all ) {
- $glue = ' UNION ALL ';
-
- return 'SELECT * ' . ( $all ? '' : '/* UNION_UNIQUE */ ' ) .
- 'FROM (' . implode( $glue, $sqls ) . ')';
- }
-
- function wasDeadlock() {
- return $this->lastErrno() == 'OCI-00060';
- }
-
- function duplicateTableStructure( $oldName, $newName, $temporary = false,
- $fname = __METHOD__
- ) {
- $temporary = $temporary ? 'TRUE' : 'FALSE';
-
- $newName = strtoupper( $newName );
- $oldName = strtoupper( $oldName );
-
- $tabName = substr( $newName, strlen( $this->mTablePrefix ) );
- $oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
- $newPrefix = strtoupper( $this->mTablePrefix );
-
- return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', " .
- "'$oldPrefix', '$newPrefix', $temporary ); END;" );
- }
-
- function listTables( $prefix = null, $fname = __METHOD__ ) {
- $listWhere = '';
- if ( !empty( $prefix ) ) {
- $listWhere = ' AND table_name LIKE \'' . strtoupper( $prefix ) . '%\'';
- }
-
- $owner = strtoupper( $this->mDBname );
- $result = $this->doQuery( "SELECT table_name FROM all_tables " .
- "WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
-
- // dirty code ... i know
- $endArray = array();
- $endArray[] = strtoupper( $prefix . 'MWUSER' );
- $endArray[] = strtoupper( $prefix . 'PAGE' );
- $endArray[] = strtoupper( $prefix . 'IMAGE' );
- $fixedOrderTabs = $endArray;
- while ( ( $row = $result->fetchRow() ) !== false ) {
- if ( !in_array( $row['table_name'], $fixedOrderTabs ) ) {
- $endArray[] = $row['table_name'];
- }
- }
-
- return $endArray;
- }
-
- public function dropTable( $tableName, $fName = __METHOD__ ) {
- $tableName = $this->tableName( $tableName );
- if ( !$this->tableExists( $tableName ) ) {
- return false;
- }
-
- return $this->doQuery( "DROP TABLE $tableName CASCADE CONSTRAINTS PURGE" );
- }
-
- function timestamp( $ts = 0 ) {
- return wfTimestamp( TS_ORACLE, $ts );
- }
-
- /**
- * Return aggregated value function call
- *
- * @param array $valuedata
- * @param string $valuename
- * @return mixed
- */
- public function aggregateValue( $valuedata, $valuename = 'value' ) {
- return $valuedata;
- }
-
- /**
- * @return string Wikitext of a link to the server software's web site
- */
- public function getSoftwareLink() {
- return '[{{int:version-db-oracle-url}} Oracle]';
- }
-
- /**
- * @return string Version information from the database
- */
- function getServerVersion() {
- //better version number, fallback on driver
- $rset = $this->doQuery(
- 'SELECT version FROM product_component_version ' .
- 'WHERE UPPER(product) LIKE \'ORACLE DATABASE%\''
- );
- if ( !( $row = $rset->fetchRow() ) ) {
- return oci_server_version( $this->mConn );
- }
-
- return $row['version'];
- }
-
- /**
- * Query whether a given index exists
- * @param string $table
- * @param string $index
- * @param string $fname
- * @return bool
- */
- function indexExists( $table, $index, $fname = __METHOD__ ) {
- $table = $this->tableName( $table );
- $table = strtoupper( $this->removeIdentifierQuotes( $table ) );
- $index = strtoupper( $index );
- $owner = strtoupper( $this->mDBname );
- $sql = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
- $res = $this->doQuery( $sql );
- if ( $res ) {
- $count = $res->numRows();
- $res->free();
- } else {
- $count = 0;
- }
-
- return $count != 0;
- }
-
- /**
- * Query whether a given table exists (in the given schema, or the default mw one if not given)
- * @param string $table
- * @param string $fname
- * @return bool
- */
- function tableExists( $table, $fname = __METHOD__ ) {
- $table = $this->tableName( $table );
- $table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) );
- $owner = $this->addQuotes( strtoupper( $this->mDBname ) );
- $sql = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
- $res = $this->doQuery( $sql );
- if ( $res && $res->numRows() > 0 ) {
- $exists = true;
- } else {
- $exists = false;
- }
-
- $res->free();
-
- return $exists;
- }
-
- /**
- * Function translates mysql_fetch_field() functionality on ORACLE.
- * Caching is present for reducing query time.
- * For internal calls. Use fieldInfo for normal usage.
- * Returns false if the field doesn't exist
- *
- * @param array|string $table
- * @param string $field
- * @return ORAField|ORAResult
- */
- private function fieldInfoMulti( $table, $field ) {
- $field = strtoupper( $field );
- if ( is_array( $table ) ) {
- $table = array_map( array( &$this, 'tableNameInternal' ), $table );
- $tableWhere = 'IN (';
- foreach ( $table as &$singleTable ) {
- $singleTable = $this->removeIdentifierQuotes( $singleTable );
- if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) {
- return $this->mFieldInfoCache["$singleTable.$field"];
- }
- $tableWhere .= '\'' . $singleTable . '\',';
- }
- $tableWhere = rtrim( $tableWhere, ',' ) . ')';
- } else {
- $table = $this->removeIdentifierQuotes( $this->tableNameInternal( $table ) );
- if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) {
- return $this->mFieldInfoCache["$table.$field"];
- }
- $tableWhere = '= \'' . $table . '\'';
- }
-
- $fieldInfoStmt = oci_parse(
- $this->mConn,
- 'SELECT * FROM wiki_field_info_full WHERE table_name ' .
- $tableWhere . ' and column_name = \'' . $field . '\''
- );
- if ( oci_execute( $fieldInfoStmt, $this->execFlags() ) === false ) {
- $e = oci_error( $fieldInfoStmt );
- $this->reportQueryError( $e['message'], $e['code'], 'fieldInfo QUERY', __METHOD__ );
-
- return false;
- }
- $res = new ORAResult( $this, $fieldInfoStmt );
- if ( $res->numRows() == 0 ) {
- if ( is_array( $table ) ) {
- foreach ( $table as &$singleTable ) {
- $this->mFieldInfoCache["$singleTable.$field"] = false;
- }
- } else {
- $this->mFieldInfoCache["$table.$field"] = false;
- }
- $fieldInfoTemp = null;
- } else {
- $fieldInfoTemp = new ORAField( $res->fetchRow() );
- $table = $fieldInfoTemp->tableName();
- $this->mFieldInfoCache["$table.$field"] = $fieldInfoTemp;
- }
- $res->free();
-
- return $fieldInfoTemp;
- }
-
- /**
- * @throws DBUnexpectedError
- * @param string $table
- * @param string $field
- * @return ORAField
- */
- function fieldInfo( $table, $field ) {
- if ( is_array( $table ) ) {
- throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
- }
-
- return $this->fieldInfoMulti( $table, $field );
- }
-
- protected function doBegin( $fname = __METHOD__ ) {
- $this->mTrxLevel = 1;
- $this->doQuery( 'SET CONSTRAINTS ALL DEFERRED' );
- }
-
- protected function doCommit( $fname = __METHOD__ ) {
- if ( $this->mTrxLevel ) {
- $ret = oci_commit( $this->mConn );
- if ( !$ret ) {
- throw new DBUnexpectedError( $this, $this->lastError() );
- }
- $this->mTrxLevel = 0;
- $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
- }
- }
-
- protected function doRollback( $fname = __METHOD__ ) {
- if ( $this->mTrxLevel ) {
- oci_rollback( $this->mConn );
- $this->mTrxLevel = 0;
- $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
- }
- }
-
- /**
- * defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}';
- *
- * @param resource $fp
- * @param bool|string $lineCallback
- * @param bool|callable $resultCallback
- * @param string $fname
- * @param bool|callable $inputCallback
- * @return bool|string
- */
- function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
- $fname = __METHOD__, $inputCallback = false ) {
- $cmd = '';
- $done = false;
- $dollarquote = false;
-
- $replacements = array();
-
- while ( !feof( $fp ) ) {
- if ( $lineCallback ) {
- call_user_func( $lineCallback );
- }
- $line = trim( fgets( $fp, 1024 ) );
- $sl = strlen( $line ) - 1;
-
- if ( $sl < 0 ) {
- continue;
- }
- if ( '-' == $line[0] && '-' == $line[1] ) {
- continue;
- }
-
- // Allow dollar quoting for function declarations
- if ( substr( $line, 0, 8 ) == '/*$mw$*/' ) {
- if ( $dollarquote ) {
- $dollarquote = false;
- $line = str_replace( '/*$mw$*/', '', $line ); // remove dollarquotes
- $done = true;
- } else {
- $dollarquote = true;
- }
- } elseif ( !$dollarquote ) {
- if ( ';' == $line[$sl] && ( $sl < 2 || ';' != $line[$sl - 1] ) ) {
- $done = true;
- $line = substr( $line, 0, $sl );
- }
- }
-
- if ( $cmd != '' ) {
- $cmd .= ' ';
- }
- $cmd .= "$line\n";
-
- if ( $done ) {
- $cmd = str_replace( ';;', ";", $cmd );
- if ( strtolower( substr( $cmd, 0, 6 ) ) == 'define' ) {
- if ( preg_match( '/^define\s*([^\s=]*)\s*=\s*\'\{\$([^\}]*)\}\'/', $cmd, $defines ) ) {
- $replacements[$defines[2]] = $defines[1];
- }
- } else {
- foreach ( $replacements as $mwVar => $scVar ) {
- $cmd = str_replace( '&' . $scVar . '.', '`{$' . $mwVar . '}`', $cmd );
- }
-
- $cmd = $this->replaceVars( $cmd );
- if ( $inputCallback ) {
- call_user_func( $inputCallback, $cmd );
- }
- $res = $this->doQuery( $cmd );
- if ( $resultCallback ) {
- call_user_func( $resultCallback, $res, $this );
- }
-
- if ( false === $res ) {
- $err = $this->lastError();
-
- return "Query \"{$cmd}\" failed with error code \"$err\".\n";
- }
- }
-
- $cmd = '';
- $done = false;
- }
- }
-
- return true;
- }
-
- function selectDB( $db ) {
- $this->mDBname = $db;
- if ( $db == null || $db == $this->mUser ) {
- return true;
- }
- $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper( $db );
- $stmt = oci_parse( $this->mConn, $sql );
- MediaWiki\suppressWarnings();
- $success = oci_execute( $stmt );
- MediaWiki\restoreWarnings();
- if ( !$success ) {
- $e = oci_error( $stmt );
- if ( $e['code'] != '1435' ) {
- $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
- }
-
- return false;
- }
-
- return true;
- }
-
- function strencode( $s ) {
- return str_replace( "'", "''", $s );
- }
-
- function addQuotes( $s ) {
- global $wgContLang;
- if ( isset( $wgContLang->mLoaded ) && $wgContLang->mLoaded ) {
- $s = $wgContLang->checkTitleEncoding( $s );
- }
-
- return "'" . $this->strencode( $s ) . "'";
- }
-
- public function addIdentifierQuotes( $s ) {
- if ( !$this->getFlag( DBO_DDLMODE ) ) {
- $s = '/*Q*/' . $s;
- }
-
- return $s;
- }
-
- public function removeIdentifierQuotes( $s ) {
- return strpos( $s, '/*Q*/' ) === false ? $s : substr( $s, 5 );
- }
-
- public function isQuotedIdentifier( $s ) {
- return strpos( $s, '/*Q*/' ) !== false;
- }
-
- private function wrapFieldForWhere( $table, &$col, &$val ) {
- global $wgContLang;
-
- $col_info = $this->fieldInfoMulti( $table, $col );
- $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
- if ( $col_type == 'CLOB' ) {
- $col = 'TO_CHAR(' . $col . ')';
- $val = $wgContLang->checkTitleEncoding( $val );
- } elseif ( $col_type == 'VARCHAR2' ) {
- $val = $wgContLang->checkTitleEncoding( $val );
- }
- }
-
- private function wrapConditionsForWhere( $table, $conds, $parentCol = null ) {
- $conds2 = array();
- foreach ( $conds as $col => $val ) {
- if ( is_array( $val ) ) {
- $conds2[$col] = $this->wrapConditionsForWhere( $table, $val, $col );
- } else {
- if ( is_numeric( $col ) && $parentCol != null ) {
- $this->wrapFieldForWhere( $table, $parentCol, $val );
- } else {
- $this->wrapFieldForWhere( $table, $col, $val );
- }
- $conds2[$col] = $val;
- }
- }
-
- return $conds2;
- }
-
- function selectRow( $table, $vars, $conds, $fname = __METHOD__,
- $options = array(), $join_conds = array()
- ) {
- if ( is_array( $conds ) ) {
- $conds = $this->wrapConditionsForWhere( $table, $conds );
- }
-
- return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds );
- }
-
- /**
- * Returns an optional USE INDEX clause to go after the table, and a
- * string to go at the end of the query
- *
- * @param array $options An associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
- * @return array
- */
- function makeSelectOptions( $options ) {
- $preLimitTail = $postLimitTail = '';
- $startOpts = '';
-
- $noKeyOptions = array();
- foreach ( $options as $key => $option ) {
- if ( is_numeric( $key ) ) {
- $noKeyOptions[$option] = true;
- }
- }
-
- $preLimitTail .= $this->makeGroupByWithHaving( $options );
-
- $preLimitTail .= $this->makeOrderBy( $options );
-
- if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
- $postLimitTail .= ' FOR UPDATE';
- }
-
- if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
- $startOpts .= 'DISTINCT';
- }
-
- if ( isset( $options['USE INDEX'] ) && !is_array( $options['USE INDEX'] ) ) {
- $useIndex = $this->useIndexClause( $options['USE INDEX'] );
- } else {
- $useIndex = '';
- }
-
- return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
- }
-
- public function delete( $table, $conds, $fname = __METHOD__ ) {
- if ( is_array( $conds ) ) {
- $conds = $this->wrapConditionsForWhere( $table, $conds );
- }
- // a hack for deleting pages, users and images (which have non-nullable FKs)
- // all deletions on these tables have transactions so final failure rollbacks these updates
- $table = $this->tableName( $table );
- if ( $table == $this->tableName( 'user' ) ) {
- $this->update( 'archive', array( 'ar_user' => 0 ),
- array( 'ar_user' => $conds['user_id'] ), $fname );
- $this->update( 'ipblocks', array( 'ipb_user' => 0 ),
- array( 'ipb_user' => $conds['user_id'] ), $fname );
- $this->update( 'image', array( 'img_user' => 0 ),
- array( 'img_user' => $conds['user_id'] ), $fname );
- $this->update( 'oldimage', array( 'oi_user' => 0 ),
- array( 'oi_user' => $conds['user_id'] ), $fname );
- $this->update( 'filearchive', array( 'fa_deleted_user' => 0 ),
- array( 'fa_deleted_user' => $conds['user_id'] ), $fname );
- $this->update( 'filearchive', array( 'fa_user' => 0 ),
- array( 'fa_user' => $conds['user_id'] ), $fname );
- $this->update( 'uploadstash', array( 'us_user' => 0 ),
- array( 'us_user' => $conds['user_id'] ), $fname );
- $this->update( 'recentchanges', array( 'rc_user' => 0 ),
- array( 'rc_user' => $conds['user_id'] ), $fname );
- $this->update( 'logging', array( 'log_user' => 0 ),
- array( 'log_user' => $conds['user_id'] ), $fname );
- } elseif ( $table == $this->tableName( 'image' ) ) {
- $this->update( 'oldimage', array( 'oi_name' => 0 ),
- array( 'oi_name' => $conds['img_name'] ), $fname );
- }
-
- return parent::delete( $table, $conds, $fname );
- }
-
- /**
- * @param string $table
- * @param array $values
- * @param array $conds
- * @param string $fname
- * @param array $options
- * @return bool
- * @throws DBUnexpectedError
- */
- function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
- global $wgContLang;
-
- $table = $this->tableName( $table );
- $opts = $this->makeUpdateOptions( $options );
- $sql = "UPDATE $opts $table SET ";
-
- $first = true;
- foreach ( $values as $col => &$val ) {
- $sqlSet = $this->fieldBindStatement( $table, $col, $val, true );
-
- if ( !$first ) {
- $sqlSet = ', ' . $sqlSet;
- } else {
- $first = false;
- }
- $sql .= $sqlSet;
- }
-
- if ( $conds !== array() && $conds !== '*' ) {
- $conds = $this->wrapConditionsForWhere( $table, $conds );
- $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
- }
-
- if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
- $e = oci_error( $this->mConn );
- $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
-
- return false;
- }
- foreach ( $values as $col => &$val ) {
- $col_info = $this->fieldInfoMulti( $table, $col );
- $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
-
- if ( $val === null ) {
- // do nothing ... null was inserted in statement creation
- } elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
- if ( is_object( $val ) ) {
- $val = $val->getData();
- }
-
- if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
- $val = '31-12-2030 12:00:00.000000';
- }
-
- $val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
- if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
- $e = oci_error( $stmt );
- $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
-
- return false;
- }
- } else {
- /** @var OCI_Lob[] $lob */
- if ( ( $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB ) ) === false ) {
- $e = oci_error( $stmt );
- throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
- }
-
- if ( is_object( $val ) ) {
- $val = $val->getData();
- }
-
- if ( $col_type == 'BLOB' ) {
- $lob[$col]->writeTemporary( $val );
- oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, SQLT_BLOB );
- } else {
- $lob[$col]->writeTemporary( $val );
- oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
- }
- }
- }
-
- MediaWiki\suppressWarnings();
-
- if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
- $e = oci_error( $stmt );
- if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
- $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
-
- return false;
- } else {
- $this->mAffectedRows = oci_num_rows( $stmt );
- }
- } else {
- $this->mAffectedRows = oci_num_rows( $stmt );
- }
-
- MediaWiki\restoreWarnings();
-
- if ( isset( $lob ) ) {
- foreach ( $lob as $lob_v ) {
- $lob_v->free();
- }
- }
-
- if ( !$this->mTrxLevel ) {
- oci_commit( $this->mConn );
- }
-
- return oci_free_statement( $stmt );
- }
-
- function bitNot( $field ) {
- // expecting bit-fields smaller than 4bytes
- return 'BITNOT(' . $field . ')';
- }
-
- function bitAnd( $fieldLeft, $fieldRight ) {
- return 'BITAND(' . $fieldLeft . ', ' . $fieldRight . ')';
- }
-
- function bitOr( $fieldLeft, $fieldRight ) {
- return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
- }
-
- function getDBname() {
- return $this->mDBname;
- }
-
- function getServer() {
- return $this->mServer;
- }
-
- public function buildGroupConcatField(
- $delim, $table, $field, $conds = '', $join_conds = array()
- ) {
- $fld = "LISTAGG($field," . $this->addQuotes( $delim ) . ") WITHIN GROUP (ORDER BY $field)";
-
- return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
- }
-
- public function getSearchEngine() {
- return 'SearchOracle';
- }
-
- public function getInfinity() {
- return '31-12-2030 12:00:00.000000';
- }
-}
diff --git a/includes/installer/MssqlInstaller.php b/includes/installer/MssqlInstaller.php
deleted file mode 100644
index 4d79d966..00000000
--- a/includes/installer/MssqlInstaller.php
+++ /dev/null
@@ -1,736 +0,0 @@
-<?php
-/**
- * Microsoft SQL Server-specific installer.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Deployment
- */
-
-/**
- * Class for setting up the MediaWiki database using Microsoft SQL Server.
- *
- * @ingroup Deployment
- * @since 1.23
- */
-class MssqlInstaller extends DatabaseInstaller {
-
- protected $globalNames = array(
- 'wgDBserver',
- 'wgDBname',
- 'wgDBuser',
- 'wgDBpassword',
- 'wgDBmwschema',
- 'wgDBprefix',
- 'wgDBWindowsAuthentication',
- );
-
- protected $internalDefaults = array(
- '_InstallUser' => 'sa',
- '_InstallWindowsAuthentication' => 'sqlauth',
- '_WebWindowsAuthentication' => 'sqlauth',
- );
-
- // SQL Server 2005 RTM
- // @todo Are SQL Express version numbers different?)
- public $minimumVersion = '9.00.1399';
-
- // These are schema-level privs
- // Note: the web user will be created will full permissions if possible, this permission
- // list is only used if we are unable to grant full permissions.
- public $webUserPrivs = array(
- 'DELETE',
- 'INSERT',
- 'SELECT',
- 'UPDATE',
- 'EXECUTE',
- );
-
- /**
- * @return string
- */
- public function getName() {
- return 'mssql';
- }
-
- /**
- * @return bool
- */
- public function isCompiled() {
- return self::checkExtension( 'sqlsrv' );
- }
-
- /**
- * @return string
- */
- public function getConnectForm() {
- if ( $this->getVar( '_InstallWindowsAuthentication' ) == 'windowsauth' ) {
- $displayStyle = 'display: none;';
- } else {
- $displayStyle = 'display: block;';
- }
-
- return $this->getTextBox(
- 'wgDBserver',
- 'config-db-host',
- array(),
- $this->parent->getHelpBox( 'config-db-host-help' )
- ) .
- Html::openElement( 'fieldset' ) .
- Html::element( 'legend', array(), wfMessage( 'config-db-wiki-settings' )->text() ) .
- $this->getTextBox( 'wgDBname', 'config-db-name', array( 'dir' => 'ltr' ),
- $this->parent->getHelpBox( 'config-db-name-help' ) ) .
- $this->getTextBox( 'wgDBmwschema', 'config-db-schema', array( 'dir' => 'ltr' ),
- $this->parent->getHelpBox( 'config-db-schema-help' ) ) .
- $this->getTextBox( 'wgDBprefix', 'config-db-prefix', array( 'dir' => 'ltr' ),
- $this->parent->getHelpBox( 'config-db-prefix-help' ) ) .
- Html::closeElement( 'fieldset' ) .
- Html::openElement( 'fieldset' ) .
- Html::element( 'legend', array(), wfMessage( 'config-db-install-account' )->text() ) .
- $this->getRadioSet( array(
- 'var' => '_InstallWindowsAuthentication',
- 'label' => 'config-mssql-auth',
- 'itemLabelPrefix' => 'config-mssql-',
- 'values' => array( 'sqlauth', 'windowsauth' ),
- 'itemAttribs' => array(
- 'sqlauth' => array(
- 'class' => 'showHideRadio',
- 'rel' => 'dbCredentialBox',
- ),
- 'windowsauth' => array(
- 'class' => 'hideShowRadio',
- 'rel' => 'dbCredentialBox',
- )
- ),
- 'help' => $this->parent->getHelpBox( 'config-mssql-install-auth' )
- ) ) .
- Html::openElement( 'div', array( 'id' => 'dbCredentialBox', 'style' => $displayStyle ) ) .
- $this->getTextBox(
- '_InstallUser',
- 'config-db-username',
- array( 'dir' => 'ltr' ),
- $this->parent->getHelpBox( 'config-db-install-username' )
- ) .
- $this->getPasswordBox(
- '_InstallPassword',
- 'config-db-password',
- array( 'dir' => 'ltr' ),
- $this->parent->getHelpBox( 'config-db-install-password' )
- ) .
- Html::closeElement( 'div' ) .
- Html::closeElement( 'fieldset' );
- }
-
- public function submitConnectForm() {
- // Get variables from the request.
- $newValues = $this->setVarsFromRequest( array(
- 'wgDBserver',
- 'wgDBname',
- 'wgDBmwschema',
- 'wgDBprefix'
- ) );
-
- // Validate them.
- $status = Status::newGood();
- if ( !strlen( $newValues['wgDBserver'] ) ) {
- $status->fatal( 'config-missing-db-host' );
- }
- if ( !strlen( $newValues['wgDBname'] ) ) {
- $status->fatal( 'config-missing-db-name' );
- } elseif ( !preg_match( '/^[a-z0-9_]+$/i', $newValues['wgDBname'] ) ) {
- $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
- }
- if ( !preg_match( '/^[a-z0-9_]*$/i', $newValues['wgDBmwschema'] ) ) {
- $status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
- }
- if ( !preg_match( '/^[a-z0-9_]*$/i', $newValues['wgDBprefix'] ) ) {
- $status->fatal( 'config-invalid-db-prefix', $newValues['wgDBprefix'] );
- }
- if ( !$status->isOK() ) {
- return $status;
- }
-
- // Check for blank schema and remap to dbo
- if ( $newValues['wgDBmwschema'] === '' ) {
- $this->setVar( 'wgDBmwschema', 'dbo' );
- }
-
- // User box
- $this->setVarsFromRequest( array(
- '_InstallUser',
- '_InstallPassword',
- '_InstallWindowsAuthentication'
- ) );
-
- // Try to connect
- $status = $this->getConnection();
- if ( !$status->isOK() ) {
- return $status;
- }
- /**
- * @var $conn DatabaseBase
- */
- $conn = $status->value;
-
- // Check version
- $version = $conn->getServerVersion();
- if ( version_compare( $version, $this->minimumVersion ) < 0 ) {
- return Status::newFatal( 'config-mssql-old', $this->minimumVersion, $version );
- }
-
- return $status;
- }
-
- /**
- * @return Status
- */
- public function openConnection() {
- global $wgDBWindowsAuthentication;
- $status = Status::newGood();
- $user = $this->getVar( '_InstallUser' );
- $password = $this->getVar( '_InstallPassword' );
-
- if ( $this->getVar( '_InstallWindowsAuthentication' ) == 'windowsauth' ) {
- // Use Windows authentication for this connection
- $wgDBWindowsAuthentication = true;
- } else {
- $wgDBWindowsAuthentication = false;
- }
-
- try {
- $db = DatabaseBase::factory( 'mssql', array(
- 'host' => $this->getVar( 'wgDBserver' ),
- 'user' => $user,
- 'password' => $password,
- 'dbname' => false,
- 'flags' => 0,
- 'schema' => $this->getVar( 'wgDBmwschema' ),
- 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ) );
- $db->prepareStatements( false );
- $db->scrollableCursor( false );
- $status->value = $db;
- } catch ( DBConnectionError $e ) {
- $status->fatal( 'config-connection-error', $e->getMessage() );
- }
-
- return $status;
- }
-
- public function preUpgrade() {
- global $wgDBuser, $wgDBpassword;
-
- $status = $this->getConnection();
- if ( !$status->isOK() ) {
- $this->parent->showStatusError( $status );
-
- return;
- }
- /**
- * @var $conn DatabaseBase
- */
- $conn = $status->value;
- $conn->selectDB( $this->getVar( 'wgDBname' ) );
-
- # Normal user and password are selected after this step, so for now
- # just copy these two
- $wgDBuser = $this->getVar( '_InstallUser' );
- $wgDBpassword = $this->getVar( '_InstallPassword' );
- }
-
- /**
- * Return true if the install user can create accounts
- *
- * @return bool
- */
- public function canCreateAccounts() {
- $status = $this->getConnection();
- if ( !$status->isOK() ) {
- return false;
- }
- /** @var $conn DatabaseBase */
- $conn = $status->value;
-
- // We need the server-level ALTER ANY LOGIN permission to create new accounts
- $res = $conn->query( "SELECT permission_name FROM sys.fn_my_permissions( NULL, 'SERVER' )" );
- $serverPrivs = array(
- 'ALTER ANY LOGIN' => false,
- 'CONTROL SERVER' => false,
- );
-
- foreach ( $res as $row ) {
- $serverPrivs[$row->permission_name] = true;
- }
-
- if ( !$serverPrivs['ALTER ANY LOGIN'] ) {
- return false;
- }
-
- // Check to ensure we can grant everything needed as well
- // We can't actually tell if we have WITH GRANT OPTION for a given permission, so we assume we do
- // and just check for the permission
- // http://technet.microsoft.com/en-us/library/ms178569.aspx
- // The following array sets up which permissions imply whatever permissions we specify
- $implied = array(
- // schema database server
- 'DELETE' => array( 'DELETE', 'CONTROL SERVER' ),
- 'EXECUTE' => array( 'EXECUTE', 'CONTROL SERVER' ),
- 'INSERT' => array( 'INSERT', 'CONTROL SERVER' ),
- 'SELECT' => array( 'SELECT', 'CONTROL SERVER' ),
- 'UPDATE' => array( 'UPDATE', 'CONTROL SERVER' ),
- );
-
- $grantOptions = array_flip( $this->webUserPrivs );
-
- // Check for schema and db-level permissions, but only if the schema/db exists
- $schemaPrivs = $dbPrivs = array(
- 'DELETE' => false,
- 'EXECUTE' => false,
- 'INSERT' => false,
- 'SELECT' => false,
- 'UPDATE' => false,
- );
-
- $dbPrivs['ALTER ANY USER'] = false;
-
- if ( $this->databaseExists( $this->getVar( 'wgDBname' ) ) ) {
- $conn->selectDB( $this->getVar( 'wgDBname' ) );
- $res = $conn->query( "SELECT permission_name FROM sys.fn_my_permissions( NULL, 'DATABASE' )" );
-
- foreach ( $res as $row ) {
- $dbPrivs[$row->permission_name] = true;
- }
-
- // If the db exists, we need ALTER ANY USER privs on it to make a new user
- if ( !$dbPrivs['ALTER ANY USER'] ) {
- return false;
- }
-
- if ( $this->schemaExists( $this->getVar( 'wgDBmwschema' ) ) ) {
- // wgDBmwschema is validated to only contain alphanumeric + underscore, so this is safe
- $res = $conn->query( "SELECT permission_name FROM sys.fn_my_permissions( "
- . "'{$this->getVar( 'wgDBmwschema' )}', 'SCHEMA' )" );
-
- foreach ( $res as $row ) {
- $schemaPrivs[$row->permission_name] = true;
- }
- }
- }
-
- // Now check all the grants we'll need to be doing to see if we can
- foreach ( $this->webUserPrivs as $permission ) {
- if ( ( isset( $schemaPrivs[$permission] ) && $schemaPrivs[$permission] )
- || ( isset( $dbPrivs[$implied[$permission][0]] )
- && $dbPrivs[$implied[$permission][0]] )
- || ( isset( $serverPrivs[$implied[$permission][1]] )
- && $serverPrivs[$implied[$permission][1]] )
- ) {
- unset( $grantOptions[$permission] );
- }
- }
-
- if ( count( $grantOptions ) ) {
- // Can't grant everything
- return false;
- }
-
- return true;
- }
-
- /**
- * @return string
- */
- public function getSettingsForm() {
- if ( $this->canCreateAccounts() ) {
- $noCreateMsg = false;
- } else {
- $noCreateMsg = 'config-db-web-no-create-privs';
- }
-
- $wrapperStyle = $this->getVar( '_SameAccount' ) ? 'display: none' : '';
- $displayStyle = $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth'
- ? 'display: none'
- : '';
- $s = Html::openElement( 'fieldset' ) .
- Html::element( 'legend', array(), wfMessage( 'config-db-web-account' )->text() ) .
- $this->getCheckBox(
- '_SameAccount', 'config-db-web-account-same',
- array( 'class' => 'hideShowRadio', 'rel' => 'dbOtherAccount' )
- ) .
- Html::openElement( 'div', array( 'id' => 'dbOtherAccount', 'style' => $wrapperStyle ) ) .
- $this->getRadioSet( array(
- 'var' => '_WebWindowsAuthentication',
- 'label' => 'config-mssql-auth',
- 'itemLabelPrefix' => 'config-mssql-',
- 'values' => array( 'sqlauth', 'windowsauth' ),
- 'itemAttribs' => array(
- 'sqlauth' => array(
- 'class' => 'showHideRadio',
- 'rel' => 'dbCredentialBox',
- ),
- 'windowsauth' => array(
- 'class' => 'hideShowRadio',
- 'rel' => 'dbCredentialBox',
- )
- ),
- 'help' => $this->parent->getHelpBox( 'config-mssql-web-auth' )
- ) ) .
- Html::openElement( 'div', array( 'id' => 'dbCredentialBox', 'style' => $displayStyle ) ) .
- $this->getTextBox( 'wgDBuser', 'config-db-username' ) .
- $this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
- Html::closeElement( 'div' );
-
- if ( $noCreateMsg ) {
- $s .= $this->parent->getWarningBox( wfMessage( $noCreateMsg )->plain() );
- } else {
- $s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
- }
-
- $s .= Html::closeElement( 'div' ) . Html::closeElement( 'fieldset' );
-
- return $s;
- }
-
- /**
- * @return Status
- */
- public function submitSettingsForm() {
- $this->setVarsFromRequest( array(
- 'wgDBuser',
- 'wgDBpassword',
- '_SameAccount',
- '_CreateDBAccount',
- '_WebWindowsAuthentication'
- ) );
-
- if ( $this->getVar( '_SameAccount' ) ) {
- $this->setVar( '_WebWindowsAuthentication', $this->getVar( '_InstallWindowsAuthentication' ) );
- $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
- $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
- }
-
- if ( $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) {
- $this->setVar( 'wgDBuser', '' );
- $this->setVar( 'wgDBpassword', '' );
- $this->setVar( 'wgDBWindowsAuthentication', true );
- } else {
- $this->setVar( 'wgDBWindowsAuthentication', false );
- }
-
- if ( $this->getVar( '_CreateDBAccount' )
- && $this->getVar( '_WebWindowsAuthentication' ) == 'sqlauth'
- && strval( $this->getVar( 'wgDBpassword' ) ) == ''
- ) {
- return Status::newFatal( 'config-db-password-empty', $this->getVar( 'wgDBuser' ) );
- }
-
- // Validate the create checkbox
- $canCreate = $this->canCreateAccounts();
- if ( !$canCreate ) {
- $this->setVar( '_CreateDBAccount', false );
- $create = false;
- } else {
- $create = $this->getVar( '_CreateDBAccount' );
- }
-
- if ( !$create ) {
- // Test the web account
- $user = $this->getVar( 'wgDBuser' );
- $password = $this->getVar( 'wgDBpassword' );
-
- if ( $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) {
- $user = 'windowsauth';
- $password = 'windowsauth';
- }
-
- try {
- DatabaseBase::factory( 'mssql', array(
- 'host' => $this->getVar( 'wgDBserver' ),
- 'user' => $user,
- 'password' => $password,
- 'dbname' => false,
- 'flags' => 0,
- 'tablePrefix' => $this->getVar( 'wgDBprefix' ),
- 'schema' => $this->getVar( 'wgDBmwschema' ),
- ) );
- } catch ( DBConnectionError $e ) {
- return Status::newFatal( 'config-connection-error', $e->getMessage() );
- }
- }
-
- return Status::newGood();
- }
-
- public function preInstall() {
- # Add our user callback to installSteps, right before the tables are created.
- $callback = array(
- 'name' => 'user',
- 'callback' => array( $this, 'setupUser' ),
- );
- $this->parent->addInstallStep( $callback, 'tables' );
- }
-
- /**
- * @return Status
- */
- public function setupDatabase() {
- $status = $this->getConnection();
- if ( !$status->isOK() ) {
- return $status;
- }
- /** @var DatabaseBase $conn */
- $conn = $status->value;
- $dbName = $this->getVar( 'wgDBname' );
- $schemaName = $this->getVar( 'wgDBmwschema' );
- if ( !$this->databaseExists( $dbName ) ) {
- $conn->query(
- "CREATE DATABASE " . $conn->addIdentifierQuotes( $dbName ),
- __METHOD__
- );
- $conn->selectDB( $dbName );
- if ( !$this->schemaExists( $schemaName ) ) {
- $conn->query(
- "CREATE SCHEMA " . $conn->addIdentifierQuotes( $schemaName ),
- __METHOD__
- );
- }
- if ( !$this->catalogExists( $schemaName ) ) {
- $conn->query(
- "CREATE FULLTEXT CATALOG " . $conn->addIdentifierQuotes( $schemaName ),
- __METHOD__
- );
- }
- }
- $this->setupSchemaVars();
-
- return $status;
- }
-
- /**
- * @return Status
- */
- public function setupUser() {
- $dbUser = $this->getVar( 'wgDBuser' );
- if ( $dbUser == $this->getVar( '_InstallUser' )
- || ( $this->getVar( '_InstallWindowsAuthentication' ) == 'windowsauth'
- && $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) ) {
- return Status::newGood();
- }
- $status = $this->getConnection();
- if ( !$status->isOK() ) {
- return $status;
- }
-
- $this->setupSchemaVars();
- $dbName = $this->getVar( 'wgDBname' );
- $this->db->selectDB( $dbName );
- $password = $this->getVar( 'wgDBpassword' );
- $schemaName = $this->getVar( 'wgDBmwschema' );
-
- if ( $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) {
- $dbUser = 'windowsauth';
- $password = 'windowsauth';
- }
-
- if ( $this->getVar( '_CreateDBAccount' ) ) {
- $tryToCreate = true;
- } else {
- $tryToCreate = false;
- }
-
- $escUser = $this->db->addIdentifierQuotes( $dbUser );
- $escDb = $this->db->addIdentifierQuotes( $dbName );
- $escSchema = $this->db->addIdentifierQuotes( $schemaName );
- $grantableNames = array();
- if ( $tryToCreate ) {
- $escPass = $this->db->addQuotes( $password );
-
- if ( !$this->loginExists( $dbUser ) ) {
- try {
- $this->db->begin();
- $this->db->selectDB( 'master' );
- $logintype = $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth'
- ? 'FROM WINDOWS'
- : "WITH PASSWORD = $escPass";
- $this->db->query( "CREATE LOGIN $escUser $logintype" );
- $this->db->selectDB( $dbName );
- $this->db->query( "CREATE USER $escUser FOR LOGIN $escUser WITH DEFAULT_SCHEMA = $escSchema" );
- $this->db->commit();
- $grantableNames[] = $dbUser;
- } catch ( DBQueryError $dqe ) {
- $this->db->rollback();
- $status->warning( 'config-install-user-create-failed', $dbUser, $dqe->getText() );
- }
- } elseif ( !$this->userExists( $dbUser ) ) {
- try {
- $this->db->begin();
- $this->db->selectDB( $dbName );
- $this->db->query( "CREATE USER $escUser FOR LOGIN $escUser WITH DEFAULT_SCHEMA = $escSchema" );
- $this->db->commit();
- $grantableNames[] = $dbUser;
- } catch ( DBQueryError $dqe ) {
- $this->db->rollback();
- $status->warning( 'config-install-user-create-failed', $dbUser, $dqe->getText() );
- }
- } else {
- $status->warning( 'config-install-user-alreadyexists', $dbUser );
- $grantableNames[] = $dbUser;
- }
- }
-
- // Try to grant to all the users we know exist or we were able to create
- $this->db->selectDB( $dbName );
- foreach ( $grantableNames as $name ) {
- try {
- // First try to grant full permissions
- $fullPrivArr = array(
- 'BACKUP DATABASE', 'BACKUP LOG', 'CREATE FUNCTION', 'CREATE PROCEDURE',
- 'CREATE TABLE', 'CREATE VIEW', 'CREATE FULLTEXT CATALOG', 'SHOWPLAN'
- );
- $fullPrivList = implode( ', ', $fullPrivArr );
- $this->db->begin();
- $this->db->query( "GRANT $fullPrivList ON DATABASE :: $escDb TO $escUser", __METHOD__ );
- $this->db->query( "GRANT CONTROL ON SCHEMA :: $escSchema TO $escUser", __METHOD__ );
- $this->db->commit();
- } catch ( DBQueryError $dqe ) {
- // If that fails, try to grant the limited subset specified in $this->webUserPrivs
- try {
- $privList = implode( ', ', $this->webUserPrivs );
- $this->db->rollback();
- $this->db->begin();
- $this->db->query( "GRANT $privList ON SCHEMA :: $escSchema TO $escUser", __METHOD__ );
- $this->db->commit();
- } catch ( DBQueryError $dqe ) {
- $this->db->rollback();
- $status->fatal( 'config-install-user-grant-failed', $dbUser, $dqe->getText() );
- }
- // Also try to grant SHOWPLAN on the db, but don't fail if we can't
- // (just makes a couple things in mediawiki run slower since
- // we have to run SELECT COUNT(*) instead of getting the query plan)
- try {
- $this->db->query( "GRANT SHOWPLAN ON DATABASE :: $escDb TO $escUser", __METHOD__ );
- } catch ( DBQueryError $dqe ) {
- }
- }
- }
-
- return $status;
- }
-
- public function createTables() {
- $status = parent::createTables();
-
- // Do last-minute stuff like fulltext indexes (since they can't be inside a transaction)
- if ( $status->isOk() ) {
- $searchindex = $this->db->tableName( 'searchindex' );
- $schema = $this->db->addIdentifierQuotes( $this->getVar( 'wgDBmwschema' ) );
- try {
- $this->db->query( "CREATE FULLTEXT INDEX ON $searchindex (si_title, si_text) "
- . "KEY INDEX si_page ON $schema" );
- } catch ( DBQueryError $dqe ) {
- $status->fatal( 'config-install-tables-failed', $dqe->getText() );
- }
- }
-
- return $status;
- }
-
- public function getGlobalDefaults() {
- // The default $wgDBmwschema is null, which breaks Postgres and other DBMSes that require
- // the use of a schema, so we need to set it here
- return array_merge( parent::getGlobalDefaults(), array(
- 'wgDBmwschema' => 'mediawiki',
- ) );
- }
-
- /**
- * Try to see if the login exists
- * @param string $user Username to check
- * @return bool
- */
- private function loginExists( $user ) {
- $res = $this->db->selectField( 'sys.sql_logins', 1, array( 'name' => $user ) );
- return (bool)$res;
- }
-
- /**
- * Try to see if the user account exists
- * We assume we already have the appropriate database selected
- * @param string $user Username to check
- * @return bool
- */
- private function userExists( $user ) {
- $res = $this->db->selectField( 'sys.sysusers', 1, array( 'name' => $user ) );
- return (bool)$res;
- }
-
- /**
- * Try to see if a given database exists
- * @param string $dbName Database name to check
- * @return bool
- */
- private function databaseExists( $dbName ) {
- $res = $this->db->selectField( 'sys.databases', 1, array( 'name' => $dbName ) );
- return (bool)$res;
- }
-
- /**
- * Try to see if a given schema exists
- * We assume we already have the appropriate database selected
- * @param string $schemaName Schema name to check
- * @return bool
- */
- private function schemaExists( $schemaName ) {
- $res = $this->db->selectField( 'sys.schemas', 1, array( 'name' => $schemaName ) );
- return (bool)$res;
- }
-
- /**
- * Try to see if a given fulltext catalog exists
- * We assume we already have the appropriate database selected
- * @param string $catalogName Catalog name to check
- * @return bool
- */
- private function catalogExists( $catalogName ) {
- $res = $this->db->selectField( 'sys.fulltext_catalogs', 1, array( 'name' => $catalogName ) );
- return (bool)$res;
- }
-
- /**
- * Get variables to substitute into tables.sql and the SQL patch files.
- *
- * @return array
- */
- public function getSchemaVars() {
- return array(
- 'wgDBname' => $this->getVar( 'wgDBname' ),
- 'wgDBmwschema' => $this->getVar( 'wgDBmwschema' ),
- 'wgDBuser' => $this->getVar( 'wgDBuser' ),
- 'wgDBpassword' => $this->getVar( 'wgDBpassword' ),
- );
- }
-
- public function getLocalSettings() {
- $schema = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBmwschema' ) );
- $prefix = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBprefix' ) );
- $windowsauth = $this->getVar( 'wgDBWindowsAuthentication' ) ? 'true' : 'false';
-
- return "# MSSQL specific settings
-\$wgDBWindowsAuthentication = {$windowsauth};
-\$wgDBmwschema = \"{$schema}\";
-\$wgDBprefix = \"{$prefix}\";";
- }
-}
diff --git a/includes/installer/MssqlUpdater.php b/includes/installer/MssqlUpdater.php
deleted file mode 100644
index 164cfab4..00000000
--- a/includes/installer/MssqlUpdater.php
+++ /dev/null
@@ -1,143 +0,0 @@
-<?php
-/**
- * Microsoft SQL Server-specific installer.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Deployment
- */
-
-/**
- * Class for setting up the MediaWiki database using Microsoft SQL Server.
- *
- * @ingroup Deployment
- * @since 1.23
- */
-
-class MssqlUpdater extends DatabaseUpdater {
-
- /**
- * @var DatabaseMssql
- */
- protected $db;
-
- protected function getCoreUpdateList() {
- return array(
- // 1.23
- array( 'addField', 'mwuser', 'user_password_expires', 'patch-user_password_expires.sql' ),
-
- // 1.24
- array( 'addField', 'page', 'page_lang', 'patch-page-page_lang.sql' ),
-
- // 1.25
- array( 'dropTable', 'hitcounter' ),
- array( 'dropField', 'site_stats', 'ss_total_views', 'patch-drop-ss_total_views.sql' ),
- array( 'dropField', 'page', 'page_counter', 'patch-drop-page_counter.sql' ),
- // Constraint updates
- array( 'updateConstraints', 'category_types', 'categorylinks', 'cl_type' ),
- array( 'updateConstraints', 'major_mime', 'filearchive', 'fa_major_mime' ),
- array( 'updateConstraints', 'media_type', 'filearchive', 'fa_media_type' ),
- array( 'updateConstraints', 'major_mime', 'oldimage', 'oi_major_mime' ),
- array( 'updateConstraints', 'media_type', 'oldimage', 'oi_media_type' ),
- array( 'updateConstraints', 'major_mime', 'image', 'img_major_mime' ),
- array( 'updateConstraints', 'media_type', 'image', 'img_media_type' ),
- array( 'updateConstraints', 'media_type', 'uploadstash', 'us_media_type' ),
- // END: Constraint updates
-
- array( 'modifyField', 'image', 'img_major_mime',
- 'patch-img_major_mime-chemical.sql' ),
- array( 'modifyField', 'oldimage', 'oi_major_mime',
- 'patch-oi_major_mime-chemical.sql' ),
- array( 'modifyField', 'filearchive', 'fa_major_mime',
- 'patch-fa_major_mime-chemical.sql' ),
- );
- }
-
- /**
- * Drops unnamed and creates named constraints following the pattern
- * <column>_ckc
- *
- * @param string $constraintType
- * @param string $table Name of the table to which the field belongs
- * @param string $field Name of the field to modify
- * @return bool False if patch is skipped.
- */
- protected function updateConstraints( $constraintType, $table, $field ) {
- global $wgDBname, $wgDBmwschema;
-
- if ( !$this->doTable( $table ) ) {
- return true;
- }
-
- $this->output( "...updating constraints on [$table].[$field] ..." );
- $updateKey = "$field-$constraintType-ck";
- if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
- $this->output( "...$table table does not exist, skipping modify field patch.\n" );
- return true;
- } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
- $this->output( "...$field field does not exist in $table table, " .
- "skipping modify field patch.\n" );
- return true;
- } elseif ( $this->updateRowExists( $updateKey ) ) {
- $this->output( "...$field in table $table already patched.\n" );
- return true;
- }
-
- # After all checks passed, start the update
- $this->insertUpdateRow( $updateKey );
- $path = 'named_constraints.sql';
- $constraintMap = array(
- 'category_types' =>
- "($field in('page', 'subcat', 'file'))",
- 'major_mime' =>
- "($field in('unknown', 'application', 'audio', 'image', 'text', 'video'," .
- " 'message', 'model', 'multipart'))",
- 'media_type' =>
- "($field in('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA'," .
- "'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE'))"
- );
- $constraint = $constraintMap[$constraintType];
-
- # and hack-in those variables that should be replaced
- # in our template file right now
- $this->db->setSchemaVars( array(
- 'tableName' => $table,
- 'fieldName' => $field,
- 'checkConstraint' => $constraint,
- 'wgDBname' => $wgDBname,
- 'wgDBmwschema' => $wgDBmwschema,
- ) );
-
- # Full path from file name
- $path = $this->db->patchPath( $path );
-
- # No need for a cursor allowing result-iteration; just apply a patch
- # store old value for re-setting later
- $wasScrollable = $this->db->scrollableCursor( false );
-
- # Apply patch
- $this->db->sourceFile( $path );
-
- # Reset DB instance to have original state
- $this->db->setSchemaVars( false );
- $this->db->scrollableCursor( $wasScrollable );
-
- $this->output( "done.\n" );
-
- return true;
- }
-}
diff --git a/includes/installer/OracleInstaller.php b/includes/installer/OracleInstaller.php
deleted file mode 100644
index 9e692ea4..00000000
--- a/includes/installer/OracleInstaller.php
+++ /dev/null
@@ -1,344 +0,0 @@
-<?php
-/**
- * Oracle-specific installer.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Deployment
- */
-
-/**
- * Class for setting up the MediaWiki database using Oracle.
- *
- * @ingroup Deployment
- * @since 1.17
- */
-class OracleInstaller extends DatabaseInstaller {
-
- protected $globalNames = array(
- 'wgDBserver',
- 'wgDBname',
- 'wgDBuser',
- 'wgDBpassword',
- 'wgDBprefix',
- );
-
- protected $internalDefaults = array(
- '_OracleDefTS' => 'USERS',
- '_OracleTempTS' => 'TEMP',
- '_InstallUser' => 'SYSTEM',
- );
-
- public $minimumVersion = '9.0.1'; // 9iR1
-
- protected $connError = null;
-
- public function getName() {
- return 'oracle';
- }
-
- public function isCompiled() {
- return self::checkExtension( 'oci8' );
- }
-
- public function getConnectForm() {
- if ( $this->getVar( 'wgDBserver' ) == 'localhost' ) {
- $this->parent->setVar( 'wgDBserver', '' );
- }
-
- return $this->getTextBox(
- 'wgDBserver',
- 'config-db-host-oracle',
- array(),
- $this->parent->getHelpBox( 'config-db-host-oracle-help' )
- ) .
- Html::openElement( 'fieldset' ) .
- Html::element( 'legend', array(), wfMessage( 'config-db-wiki-settings' )->text() ) .
- $this->getTextBox( 'wgDBprefix', 'config-db-prefix' ) .
- $this->getTextBox( '_OracleDefTS', 'config-oracle-def-ts' ) .
- $this->getTextBox(
- '_OracleTempTS',
- 'config-oracle-temp-ts',
- array(),
- $this->parent->getHelpBox( 'config-db-oracle-help' )
- ) .
- Html::closeElement( 'fieldset' ) .
- $this->parent->getWarningBox( wfMessage( 'config-db-account-oracle-warn' )->text() ) .
- $this->getInstallUserBox() .
- $this->getWebUserBox();
- }
-
- public function submitInstallUserBox() {
- parent::submitInstallUserBox();
- $this->parent->setVar( '_InstallDBname', $this->getVar( '_InstallUser' ) );
-
- return Status::newGood();
- }
-
- public function submitConnectForm() {
- // Get variables from the request
- $newValues = $this->setVarsFromRequest( array(
- 'wgDBserver',
- 'wgDBprefix',
- 'wgDBuser',
- 'wgDBpassword'
- ) );
- $this->parent->setVar( 'wgDBname', $this->getVar( 'wgDBuser' ) );
-
- // Validate them
- $status = Status::newGood();
- if ( !strlen( $newValues['wgDBserver'] ) ) {
- $status->fatal( 'config-missing-db-server-oracle' );
- } elseif ( !self::checkConnectStringFormat( $newValues['wgDBserver'] ) ) {
- $status->fatal( 'config-invalid-db-server-oracle', $newValues['wgDBserver'] );
- }
- if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBprefix'] ) ) {
- $status->fatal( 'config-invalid-schema', $newValues['wgDBprefix'] );
- }
- if ( !$status->isOK() ) {
- return $status;
- }
-
- // Submit user box
- $status = $this->submitInstallUserBox();
- if ( !$status->isOK() ) {
- return $status;
- }
-
- // Try to connect trough multiple scenarios
- // Scenario 1: Install with a manually created account
- $status = $this->getConnection();
- if ( !$status->isOK() ) {
- if ( $this->connError == 28009 ) {
- // _InstallUser seems to be a SYSDBA
- // Scenario 2: Create user with SYSDBA and install with new user
- $status = $this->submitWebUserBox();
- if ( !$status->isOK() ) {
- return $status;
- }
- $status = $this->openSYSDBAConnection();
- if ( !$status->isOK() ) {
- return $status;
- }
- if ( !$this->getVar( '_CreateDBAccount' ) ) {
- $status->fatal( 'config-db-sys-create-oracle' );
- }
- } else {
- return $status;
- }
- } else {
- // check for web user credentials
- // Scenario 3: Install with a priviliged user but use a restricted user
- $statusIS3 = $this->submitWebUserBox();
- if ( !$statusIS3->isOK() ) {
- return $statusIS3;
- }
- }
-
- /**
- * @var $conn DatabaseBase
- */
- $conn = $status->value;
-
- // Check version
- $version = $conn->getServerVersion();
- if ( version_compare( $version, $this->minimumVersion ) < 0 ) {
- return Status::newFatal( 'config-oracle-old', $this->minimumVersion, $version );
- }
-
- return $status;
- }
-
- public function openConnection() {
- $status = Status::newGood();
- try {
- $db = new DatabaseOracle(
- $this->getVar( 'wgDBserver' ),
- $this->getVar( '_InstallUser' ),
- $this->getVar( '_InstallPassword' ),
- $this->getVar( '_InstallDBname' ),
- 0,
- $this->getVar( 'wgDBprefix' )
- );
- $status->value = $db;
- } catch ( DBConnectionError $e ) {
- $this->connError = $e->db->lastErrno();
- $status->fatal( 'config-connection-error', $e->getMessage() );
- }
-
- return $status;
- }
-
- public function openSYSDBAConnection() {
- $status = Status::newGood();
- try {
- $db = new DatabaseOracle(
- $this->getVar( 'wgDBserver' ),
- $this->getVar( '_InstallUser' ),
- $this->getVar( '_InstallPassword' ),
- $this->getVar( '_InstallDBname' ),
- DBO_SYSDBA,
- $this->getVar( 'wgDBprefix' )
- );
- $status->value = $db;
- } catch ( DBConnectionError $e ) {
- $this->connError = $e->db->lastErrno();
- $status->fatal( 'config-connection-error', $e->getMessage() );
- }
-
- return $status;
- }
-
- public function needsUpgrade() {
- $tempDBname = $this->getVar( 'wgDBname' );
- $this->parent->setVar( 'wgDBname', $this->getVar( 'wgDBuser' ) );
- $retVal = parent::needsUpgrade();
- $this->parent->setVar( 'wgDBname', $tempDBname );
-
- return $retVal;
- }
-
- public function preInstall() {
- # Add our user callback to installSteps, right before the tables are created.
- $callback = array(
- 'name' => 'user',
- 'callback' => array( $this, 'setupUser' )
- );
- $this->parent->addInstallStep( $callback, 'database' );
- }
-
- public function setupDatabase() {
- $status = Status::newGood();
-
- return $status;
- }
-
- public function setupUser() {
- global $IP;
-
- if ( !$this->getVar( '_CreateDBAccount' ) ) {
- return Status::newGood();
- }
-
- // normaly only SYSDBA users can create accounts
- $status = $this->openSYSDBAConnection();
- if ( !$status->isOK() ) {
- if ( $this->connError == 1031 ) {
- // insufficient privileges (looks like a normal user)
- $status = $this->openConnection();
- if ( !$status->isOK() ) {
- return $status;
- }
- } else {
- return $status;
- }
- }
-
- $this->db = $status->value;
- $this->setupSchemaVars();
-
- if ( !$this->db->selectDB( $this->getVar( 'wgDBuser' ) ) ) {
- $this->db->setFlag( DBO_DDLMODE );
- $error = $this->db->sourceFile( "$IP/maintenance/oracle/user.sql" );
- if ( $error !== true || !$this->db->selectDB( $this->getVar( 'wgDBuser' ) ) ) {
- $status->fatal( 'config-install-user-failed', $this->getVar( 'wgDBuser' ), $error );
- }
- } elseif ( $this->db->getFlag( DBO_SYSDBA ) ) {
- $status->fatal( 'config-db-sys-user-exists-oracle', $this->getVar( 'wgDBuser' ) );
- }
-
- if ( $status->isOK() ) {
- // user created or already existing, switching back to a normal connection
- // as the new user has all needed privileges to setup the rest of the schema
- // i will be using that user as _InstallUser from this point on
- $this->db->close();
- $this->db = false;
- $this->parent->setVar( '_InstallUser', $this->getVar( 'wgDBuser' ) );
- $this->parent->setVar( '_InstallPassword', $this->getVar( 'wgDBpassword' ) );
- $this->parent->setVar( '_InstallDBname', $this->getVar( 'wgDBuser' ) );
- $status = $this->getConnection();
- }
-
- return $status;
- }
-
- /**
- * Overload: after this action field info table has to be rebuilt
- * @return Status
- */
- public function createTables() {
- $this->setupSchemaVars();
- $this->db->setFlag( DBO_DDLMODE );
- $this->parent->setVar( 'wgDBname', $this->getVar( 'wgDBuser' ) );
- $status = parent::createTables();
- $this->db->clearFlag( DBO_DDLMODE );
-
- $this->db->query( 'BEGIN fill_wiki_info; END;' );
-
- return $status;
- }
-
- public function getSchemaVars() {
- $varNames = array(
- # These variables are used by maintenance/oracle/user.sql
- '_OracleDefTS',
- '_OracleTempTS',
- 'wgDBuser',
- 'wgDBpassword',
-
- # These are used by tables.sql
- 'wgDBprefix',
- );
- $vars = array();
- foreach ( $varNames as $name ) {
- $vars[$name] = $this->getVar( $name );
- }
-
- return $vars;
- }
-
- public function getLocalSettings() {
- $prefix = $this->getVar( 'wgDBprefix' );
-
- return "# Oracle specific settings
-\$wgDBprefix = \"{$prefix}\";
-";
- }
-
- /**
- * Function checks the format of Oracle connect string
- * The actual validity of the string is checked by attempting to connect
- *
- * Regex should be able to validate all connect string formats
- * [//](host|tns_name)[:port][/service_name][:POOLED]
- * http://www.orafaq.com/wiki/EZCONNECT
- *
- * @since 1.22
- *
- * @param string $connect_string
- *
- * @return bool Whether the connection string is valid.
- */
- public static function checkConnectStringFormat( $connect_string ) {
- // @@codingStandardsIgnoreStart Long lines with regular expressions.
- // @todo Very long regular expression. Make more readable?
- $isValid = preg_match( '/^[[:alpha:]][\w\-]*(?:\.[[:alpha:]][\w\-]*){0,2}$/', $connect_string ); // TNS name
- $isValid |= preg_match( '/^(?:\/\/)?[\w\-\.]+(?::[\d]+)?(?:\/(?:[\w\-\.]+(?::(pooled|dedicated|shared))?)?(?:\/[\w\-\.]+)?)?$/', $connect_string ); // EZConnect
- // @@codingStandardsIgnoreEnd
- return (bool)$isValid;
- }
-}
diff --git a/includes/installer/OracleUpdater.php b/includes/installer/OracleUpdater.php
deleted file mode 100644
index 03dbd1ce..00000000
--- a/includes/installer/OracleUpdater.php
+++ /dev/null
@@ -1,289 +0,0 @@
-<?php
-/**
- * Oracle-specific updater.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Deployment
- */
-
-/**
- * Class for handling updates to Oracle databases.
- *
- * @ingroup Deployment
- * @since 1.17
- */
-class OracleUpdater extends DatabaseUpdater {
-
- /**
- * Handle to the database subclass
- *
- * @var DatabaseOracle
- */
- protected $db;
-
- protected function getCoreUpdateList() {
- return array(
- array( 'disableContentHandlerUseDB' ),
-
- // 1.17
- array( 'doNamespaceDefaults' ),
- array( 'doFKRenameDeferr' ),
- array( 'doFunctions17' ),
- array( 'doSchemaUpgrade17' ),
- array( 'doInsertPage0' ),
- array( 'doRemoveNotNullEmptyDefaults' ),
- array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql' ),
-
- // 1.18
- array( 'addIndex', 'user', 'i02', 'patch-user_email_index.sql' ),
- array( 'modifyField', 'user_properties', 'up_property', 'patch-up_property.sql' ),
- array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
- array( 'doRecentchangesFK2Cascade' ),
-
- // 1.19
- array( 'addIndex', 'logging', 'i05', 'patch-logging_type_action_index.sql' ),
- array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1_field.sql' ),
- array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1_field.sql' ),
- array( 'doRemoveNotNullEmptyDefaults2' ),
- array( 'addIndex', 'page', 'i03', 'patch-page_redirect_namespace_len.sql' ),
- array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-us_chunk_inx_field.sql' ),
- array( 'addField', 'job', 'job_timestamp', 'patch-job_timestamp_field.sql' ),
- array( 'addIndex', 'job', 'i02', 'patch-job_timestamp_index.sql' ),
- array( 'doPageRestrictionsPKUKFix' ),
-
- // 1.20
- array( 'addIndex', 'ipblocks', 'i05', 'patch-ipblocks_i05_index.sql' ),
- array( 'addIndex', 'revision', 'i05', 'patch-revision_i05_index.sql' ),
- array( 'dropField', 'category', 'cat_hidden', 'patch-cat_hidden.sql' ),
-
- // 1.21
- array( 'addField', 'revision', 'rev_content_format',
- 'patch-revision-rev_content_format.sql' ),
- array( 'addField', 'revision', 'rev_content_model',
- 'patch-revision-rev_content_model.sql' ),
- array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
- array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
- array( 'addField', 'archive', 'ar_id', 'patch-archive-ar_id.sql' ),
- array( 'addField', 'externallinks', 'el_id', 'patch-externallinks-el_id.sql' ),
- array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
- array( 'enableContentHandlerUseDB' ),
- array( 'dropField', 'site_stats', 'ss_admins', 'patch-ss_admins.sql' ),
- array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
- array( 'addTable', 'sites', 'patch-sites.sql' ),
- array( 'addField', 'filearchive', 'fa_sha1', 'patch-fa_sha1.sql' ),
- array( 'addField', 'job', 'job_token', 'patch-job_token.sql' ),
- array( 'addField', 'job', 'job_attempts', 'patch-job_attempts.sql' ),
- array( 'addField', 'uploadstash', 'us_props', 'patch-uploadstash-us_props.sql' ),
- array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase-255.sql' ),
- array( 'modifyField', 'user_former_groups', 'ufg_group',
- 'patch-ufg_group-length-increase-255.sql' ),
-
- // 1.23
- array( 'addIndex', 'logging', 'i06', 'patch-logging_user_text_type_time_index.sql' ),
- array( 'addIndex', 'logging', 'i07', 'patch-logging_user_text_time_index.sql' ),
- array( 'addField', 'user', 'user_password_expires', 'patch-user_password_expire.sql' ),
- array( 'addField', 'page', 'page_links_updated', 'patch-page_links_updated.sql' ),
- array( 'addField', 'recentchanges', 'rc_source', 'patch-rc_source.sql' ),
-
- // 1.24
- array( 'addField', 'page', 'page_lang', 'patch-page-page_lang.sql' ),
-
- // 1.25
- array( 'dropTable', 'hitcounter' ),
- array( 'dropField', 'site_stats', 'ss_total_views', 'patch-drop-ss_total_views.sql' ),
- array( 'dropField', 'page', 'page_counter', 'patch-drop-page_counter.sql' ),
-
- // KEEP THIS AT THE BOTTOM!!
- array( 'doRebuildDuplicateFunction' ),
-
- );
- }
-
- /**
- * MySQL uses datatype defaults for NULL inserted into NOT NULL fields
- * In namespace case that results into insert of 0 which is default namespace
- * Oracle inserts NULL, so namespace fields should have a default value
- */
- protected function doNamespaceDefaults() {
- $meta = $this->db->fieldInfo( 'page', 'page_namespace' );
- if ( $meta->defaultValue() != null ) {
- return;
- }
-
- $this->applyPatch(
- 'patch_namespace_defaults.sql',
- false,
- 'Altering namespace fields with default value'
- );
- }
-
- /**
- * Uniform FK names + deferrable state
- */
- protected function doFKRenameDeferr() {
- $meta = $this->db->query( '
- SELECT COUNT(*) cnt
- FROM user_constraints
- WHERE constraint_type = \'R\' AND deferrable = \'DEFERRABLE\''
- );
- $row = $meta->fetchRow();
- if ( $row && $row['cnt'] > 0 ) {
- return;
- }
-
- $this->applyPatch( 'patch_fk_rename_deferred.sql', false, "Altering foreign keys ... " );
- }
-
- /**
- * Recreate functions to 17 schema layout
- */
- protected function doFunctions17() {
- $this->applyPatch( 'patch_create_17_functions.sql', false, "Recreating functions" );
- }
-
- /**
- * Schema upgrade 16->17
- * there are no incremental patches prior to this
- */
- protected function doSchemaUpgrade17() {
- // check if iwlinks table exists which was added in 1.17
- if ( $this->db->tableExists( 'iwlinks' ) ) {
- return;
- }
- $this->applyPatch( 'patch_16_17_schema_changes.sql', false, "Updating schema to 17" );
- }
-
- /**
- * Insert page (page_id = 0) to prevent FK constraint violation
- */
- protected function doInsertPage0() {
- $this->output( "Inserting page 0 if missing ... " );
- $row = array(
- 'page_id' => 0,
- 'page_namespace' => 0,
- 'page_title' => ' ',
- 'page_is_redirect' => 0,
- 'page_is_new' => 0,
- 'page_random' => 0,
- 'page_touched' => $this->db->timestamp(),
- 'page_latest' => 0,
- 'page_len' => 0
- );
- $this->db->insert( 'page', $row, 'OracleUpdater:doInserPage0', array( 'IGNORE' ) );
- $this->output( "ok\n" );
- }
-
- /**
- * Remove DEFAULT '' NOT NULL constraints from fields as '' is internally
- * converted to NULL in Oracle
- */
- protected function doRemoveNotNullEmptyDefaults() {
- $meta = $this->db->fieldInfo( 'categorylinks', 'cl_sortkey_prefix' );
- if ( $meta->isNullable() ) {
- return;
- }
- $this->applyPatch(
- 'patch_remove_not_null_empty_defs.sql',
- false,
- 'Removing not null empty constraints'
- );
- }
-
- protected function doRemoveNotNullEmptyDefaults2() {
- $meta = $this->db->fieldInfo( 'ipblocks', 'ipb_by_text' );
- if ( $meta->isNullable() ) {
- return;
- }
- $this->applyPatch(
- 'patch_remove_not_null_empty_defs2.sql',
- false,
- 'Removing not null empty constraints'
- );
- }
-
- /**
- * Removed forcing of invalid state on recentchanges_fk2.
- * cascading taken in account in the deleting function
- */
- protected function doRecentchangesFK2Cascade() {
- $meta = $this->db->query( 'SELECT 1 FROM all_constraints WHERE owner = \'' .
- strtoupper( $this->db->getDBname() ) .
- '\' AND constraint_name = \'' .
- $this->db->tablePrefix() .
- 'RECENTCHANGES_FK2\' AND delete_rule = \'CASCADE\''
- );
- $row = $meta->fetchRow();
- if ( $row ) {
- return;
- }
-
- $this->applyPatch( 'patch_recentchanges_fk2_cascade.sql', false, "Altering RECENTCHANGES_FK2" );
- }
-
- /**
- * Fixed wrong PK, UK definition
- */
- protected function doPageRestrictionsPKUKFix() {
- $this->output( "Altering PAGE_RESTRICTIONS keys ... " );
-
- $meta = $this->db->query( 'SELECT column_name FROM all_cons_columns WHERE owner = \'' .
- strtoupper( $this->db->getDBname() ) .
- '\' AND constraint_name = \'' .
- $this->db->tablePrefix() .
- 'PAGE_RESTRICTIONS_PK\' AND rownum = 1'
- );
- $row = $meta->fetchRow();
- if ( $row['column_name'] == 'PR_ID' ) {
- $this->output( "seems to be up to date.\n" );
-
- return;
- }
-
- $this->applyPatch( 'patch-page_restrictions_pkuk_fix.sql', false );
- $this->output( "ok\n" );
- }
-
- /**
- * rebuilding of the function that duplicates tables for tests
- */
- protected function doRebuildDuplicateFunction() {
- $this->applyPatch( 'patch_rebuild_dupfunc.sql', false, "Rebuilding duplicate function" );
- }
-
- /**
- * Overload: after this action field info table has to be rebuilt
- *
- * @param array $what
- */
- public function doUpdates( $what = array( 'core', 'extensions', 'purge', 'stats' ) ) {
- parent::doUpdates( $what );
-
- $this->db->query( 'BEGIN fill_wiki_info; END;' );
- }
-
- /**
- * Overload: because of the DDL_MODE tablename escaping is a bit dodgy
- */
- public function purgeCache() {
- # We can't guarantee that the user will be able to use TRUNCATE,
- # but we know that DELETE is available to us
- $this->output( "Purging caches..." );
- $this->db->delete( '/*Q*/' . $this->db->tableName( 'objectcache' ), '*', __METHOD__ );
- $this->output( "done.\n" );
- }
-}
diff --git a/includes/libs/IEContentAnalyzer.php b/includes/libs/IEContentAnalyzer.php
deleted file mode 100644
index c31a3527..00000000
--- a/includes/libs/IEContentAnalyzer.php
+++ /dev/null
@@ -1,851 +0,0 @@
-<?php
-/**
- * Simulation of Microsoft Internet Explorer's MIME type detection algorithm.
- *
- * @file
- * @todo Define the exact license of this file.
- */
-
-/**
- * This class simulates Microsoft Internet Explorer's terribly broken and
- * insecure MIME type detection algorithm. It can be used to check web uploads
- * with an apparently safe type, to see if IE will reinterpret them to produce
- * something dangerous.
- *
- * It is full of bugs and strange design choices should not under any
- * circumstances be used to determine a MIME type to present to a user or
- * client. (Apple Safari developers, this means you too.)
- *
- * This class is based on a disassembly of IE 5.0, 6.0 and 7.0. Although I have
- * attempted to ensure that this code works in exactly the same way as Internet
- * Explorer, it does not share any source code, or creative choices such as
- * variable names, thus I (Tim Starling) claim copyright on it.
- *
- * It may be redistributed without restriction. To aid reuse, this class does
- * not depend on any MediaWiki module.
- */
-class IEContentAnalyzer {
- /**
- * Relevant data taken from the type table in IE 5
- */
- protected $baseTypeTable = array(
- 'ambiguous' /*1*/ => array(
- 'text/plain',
- 'application/octet-stream',
- 'application/x-netcdf', // [sic]
- ),
- 'text' /*3*/ => array(
- 'text/richtext', 'image/x-bitmap', 'application/postscript', 'application/base64',
- 'application/macbinhex40', 'application/x-cdf', 'text/scriptlet'
- ),
- 'binary' /*4*/ => array(
- 'application/pdf', 'audio/x-aiff', 'audio/basic', 'audio/wav', 'image/gif',
- 'image/pjpeg', 'image/jpeg', 'image/tiff', 'image/x-png', 'image/png', 'image/bmp',
- 'image/x-jg', 'image/x-art', 'image/x-emf', 'image/x-wmf', 'video/avi',
- 'video/x-msvideo', 'video/mpeg', 'application/x-compressed',
- 'application/x-zip-compressed', 'application/x-gzip-compressed', 'application/java',
- 'application/x-msdownload'
- ),
- 'html' /*5*/ => array( 'text/html' ),
- );
-
- /**
- * Changes to the type table in later versions of IE
- */
- protected $addedTypes = array(
- 'ie07' => array(
- 'text' => array( 'text/xml', 'application/xml' )
- ),
- );
-
- /**
- * An approximation of the "Content Type" values in HKEY_CLASSES_ROOT in a
- * typical Windows installation.
- *
- * Used for extension to MIME type mapping if detection fails.
- */
- protected $registry = array(
- '.323' => 'text/h323',
- '.3g2' => 'video/3gpp2',
- '.3gp' => 'video/3gpp',
- '.3gp2' => 'video/3gpp2',
- '.3gpp' => 'video/3gpp',
- '.aac' => 'audio/aac',
- '.ac3' => 'audio/ac3',
- '.accda' => 'application/msaccess',
- '.accdb' => 'application/msaccess',
- '.accdc' => 'application/msaccess',
- '.accde' => 'application/msaccess',
- '.accdr' => 'application/msaccess',
- '.accdt' => 'application/msaccess',
- '.ade' => 'application/msaccess',
- '.adp' => 'application/msaccess',
- '.adts' => 'audio/aac',
- '.ai' => 'application/postscript',
- '.aif' => 'audio/aiff',
- '.aifc' => 'audio/aiff',
- '.aiff' => 'audio/aiff',
- '.amc' => 'application/x-mpeg',
- '.application' => 'application/x-ms-application',
- '.asf' => 'video/x-ms-asf',
- '.asx' => 'video/x-ms-asf',
- '.au' => 'audio/basic',
- '.avi' => 'video/avi',
- '.bmp' => 'image/bmp',
- '.caf' => 'audio/x-caf',
- '.cat' => 'application/vnd.ms-pki.seccat',
- '.cbo' => 'application/sha',
- '.cdda' => 'audio/aiff',
- '.cer' => 'application/x-x509-ca-cert',
- '.conf' => 'text/plain',
- '.crl' => 'application/pkix-crl',
- '.crt' => 'application/x-x509-ca-cert',
- '.css' => 'text/css',
- '.csv' => 'application/vnd.ms-excel',
- '.der' => 'application/x-x509-ca-cert',
- '.dib' => 'image/bmp',
- '.dif' => 'video/x-dv',
- '.dll' => 'application/x-msdownload',
- '.doc' => 'application/msword',
- '.docm' => 'application/vnd.ms-word.document.macroEnabled.12',
- '.docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
- '.dot' => 'application/msword',
- '.dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
- '.dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
- '.dv' => 'video/x-dv',
- '.dwfx' => 'model/vnd.dwfx+xps',
- '.edn' => 'application/vnd.adobe.edn',
- '.eml' => 'message/rfc822',
- '.eps' => 'application/postscript',
- '.etd' => 'application/x-ebx',
- '.exe' => 'application/x-msdownload',
- '.fdf' => 'application/vnd.fdf',
- '.fif' => 'application/fractals',
- '.gif' => 'image/gif',
- '.gsm' => 'audio/x-gsm',
- '.hqx' => 'application/mac-binhex40',
- '.hta' => 'application/hta',
- '.htc' => 'text/x-component',
- '.htm' => 'text/html',
- '.html' => 'text/html',
- '.htt' => 'text/webviewhtml',
- '.hxa' => 'application/xml',
- '.hxc' => 'application/xml',
- '.hxd' => 'application/octet-stream',
- '.hxe' => 'application/xml',
- '.hxf' => 'application/xml',
- '.hxh' => 'application/octet-stream',
- '.hxi' => 'application/octet-stream',
- '.hxk' => 'application/xml',
- '.hxq' => 'application/octet-stream',
- '.hxr' => 'application/octet-stream',
- '.hxs' => 'application/octet-stream',
- '.hxt' => 'application/xml',
- '.hxv' => 'application/xml',
- '.hxw' => 'application/octet-stream',
- '.ico' => 'image/x-icon',
- '.iii' => 'application/x-iphone',
- '.ins' => 'application/x-internet-signup',
- '.iqy' => 'text/x-ms-iqy',
- '.isp' => 'application/x-internet-signup',
- '.jfif' => 'image/jpeg',
- '.jnlp' => 'application/x-java-jnlp-file',
- '.jpe' => 'image/jpeg',
- '.jpeg' => 'image/jpeg',
- '.jpg' => 'image/jpeg',
- '.jtx' => 'application/x-jtx+xps',
- '.latex' => 'application/x-latex',
- '.log' => 'text/plain',
- '.m1v' => 'video/mpeg',
- '.m2v' => 'video/mpeg',
- '.m3u' => 'audio/x-mpegurl',
- '.mac' => 'image/x-macpaint',
- '.man' => 'application/x-troff-man',
- '.mda' => 'application/msaccess',
- '.mdb' => 'application/msaccess',
- '.mde' => 'application/msaccess',
- '.mfp' => 'application/x-shockwave-flash',
- '.mht' => 'message/rfc822',
- '.mhtml' => 'message/rfc822',
- '.mid' => 'audio/mid',
- '.midi' => 'audio/mid',
- '.mod' => 'video/mpeg',
- '.mov' => 'video/quicktime',
- '.mp2' => 'video/mpeg',
- '.mp2v' => 'video/mpeg',
- '.mp3' => 'audio/mpeg',
- '.mp4' => 'video/mp4',
- '.mpa' => 'video/mpeg',
- '.mpe' => 'video/mpeg',
- '.mpeg' => 'video/mpeg',
- '.mpf' => 'application/vnd.ms-mediapackage',
- '.mpg' => 'video/mpeg',
- '.mpv2' => 'video/mpeg',
- '.mqv' => 'video/quicktime',
- '.NMW' => 'application/nmwb',
- '.nws' => 'message/rfc822',
- '.odc' => 'text/x-ms-odc',
- '.ols' => 'application/vnd.ms-publisher',
- '.p10' => 'application/pkcs10',
- '.p12' => 'application/x-pkcs12',
- '.p7b' => 'application/x-pkcs7-certificates',
- '.p7c' => 'application/pkcs7-mime',
- '.p7m' => 'application/pkcs7-mime',
- '.p7r' => 'application/x-pkcs7-certreqresp',
- '.p7s' => 'application/pkcs7-signature',
- '.pct' => 'image/pict',
- '.pdf' => 'application/pdf',
- '.pdx' => 'application/vnd.adobe.pdx',
- '.pfx' => 'application/x-pkcs12',
- '.pic' => 'image/pict',
- '.pict' => 'image/pict',
- '.pinstall' => 'application/x-picasa-detect',
- '.pko' => 'application/vnd.ms-pki.pko',
- '.png' => 'image/png',
- '.pnt' => 'image/x-macpaint',
- '.pntg' => 'image/x-macpaint',
- '.pot' => 'application/vnd.ms-powerpoint',
- '.potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
- '.potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
- '.ppa' => 'application/vnd.ms-powerpoint',
- '.ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
- '.pps' => 'application/vnd.ms-powerpoint',
- '.ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
- '.ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
- '.ppt' => 'application/vnd.ms-powerpoint',
- '.pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
- '.pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
- '.prf' => 'application/pics-rules',
- '.ps' => 'application/postscript',
- '.pub' => 'application/vnd.ms-publisher',
- '.pwz' => 'application/vnd.ms-powerpoint',
- '.py' => 'text/plain',
- '.pyw' => 'text/plain',
- '.qht' => 'text/x-html-insertion',
- '.qhtm' => 'text/x-html-insertion',
- '.qt' => 'video/quicktime',
- '.qti' => 'image/x-quicktime',
- '.qtif' => 'image/x-quicktime',
- '.qtl' => 'application/x-quicktimeplayer',
- '.rat' => 'application/rat-file',
- '.rmf' => 'application/vnd.adobe.rmf',
- '.rmi' => 'audio/mid',
- '.rqy' => 'text/x-ms-rqy',
- '.rtf' => 'application/msword',
- '.sct' => 'text/scriptlet',
- '.sd2' => 'audio/x-sd2',
- '.sdp' => 'application/sdp',
- '.shtml' => 'text/html',
- '.sit' => 'application/x-stuffit',
- '.sldm' => 'application/vnd.ms-powerpoint.slide.macroEnabled.12',
- '.sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
- '.slk' => 'application/vnd.ms-excel',
- '.snd' => 'audio/basic',
- '.so' => 'application/x-apachemodule',
- '.sol' => 'text/plain',
- '.sor' => 'text/plain',
- '.spc' => 'application/x-pkcs7-certificates',
- '.spl' => 'application/futuresplash',
- '.sst' => 'application/vnd.ms-pki.certstore',
- '.stl' => 'application/vnd.ms-pki.stl',
- '.swf' => 'application/x-shockwave-flash',
- '.thmx' => 'application/vnd.ms-officetheme',
- '.tif' => 'image/tiff',
- '.tiff' => 'image/tiff',
- '.txt' => 'text/plain',
- '.uls' => 'text/iuls',
- '.vcf' => 'text/x-vcard',
- '.vdx' => 'application/vnd.ms-visio.viewer',
- '.vsd' => 'application/vnd.ms-visio.viewer',
- '.vss' => 'application/vnd.ms-visio.viewer',
- '.vst' => 'application/vnd.ms-visio.viewer',
- '.vsx' => 'application/vnd.ms-visio.viewer',
- '.vtx' => 'application/vnd.ms-visio.viewer',
- '.wav' => 'audio/wav',
- '.wax' => 'audio/x-ms-wax',
- '.wbk' => 'application/msword',
- '.wdp' => 'image/vnd.ms-photo',
- '.wiz' => 'application/msword',
- '.wm' => 'video/x-ms-wm',
- '.wma' => 'audio/x-ms-wma',
- '.wmd' => 'application/x-ms-wmd',
- '.wmv' => 'video/x-ms-wmv',
- '.wmx' => 'video/x-ms-wmx',
- '.wmz' => 'application/x-ms-wmz',
- '.wpl' => 'application/vnd.ms-wpl',
- '.wsc' => 'text/scriptlet',
- '.wvx' => 'video/x-ms-wvx',
- '.xaml' => 'application/xaml+xml',
- '.xbap' => 'application/x-ms-xbap',
- '.xdp' => 'application/vnd.adobe.xdp+xml',
- '.xfdf' => 'application/vnd.adobe.xfdf',
- '.xht' => 'application/xhtml+xml',
- '.xhtml' => 'application/xhtml+xml',
- '.xla' => 'application/vnd.ms-excel',
- '.xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
- '.xlk' => 'application/vnd.ms-excel',
- '.xll' => 'application/vnd.ms-excel',
- '.xlm' => 'application/vnd.ms-excel',
- '.xls' => 'application/vnd.ms-excel',
- '.xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
- '.xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
- '.xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
- '.xlt' => 'application/vnd.ms-excel',
- '.xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
- '.xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
- '.xlw' => 'application/vnd.ms-excel',
- '.xml' => 'text/xml',
- '.xps' => 'application/vnd.ms-xpsdocument',
- '.xsl' => 'text/xml',
- );
-
- /**
- * IE versions which have been analysed to bring you this class, and for
- * which some substantive difference exists. These will appear as keys
- * in the return value of getRealMimesFromData(). The names are chosen to sort correctly.
- */
- protected $versions = array( 'ie05', 'ie06', 'ie07', 'ie07.strict', 'ie07.nohtml' );
-
- /**
- * Type table with versions expanded
- */
- protected $typeTable = array();
-
- /** constructor */
- function __construct() {
- // Construct versioned type arrays from the base type array plus additions
- $types = $this->baseTypeTable;
- foreach ( $this->versions as $version ) {
- if ( isset( $this->addedTypes[$version] ) ) {
- foreach ( $this->addedTypes[$version] as $format => $addedTypes ) {
- $types[$format] = array_merge( $types[$format], $addedTypes );
- }
- }
- $this->typeTable[$version] = $types;
- }
- }
-
- /**
- * Get the MIME types from getMimesFromData(), but convert the result from IE's
- * idiosyncratic private types into something other apps will understand.
- *
- * @param string $fileName the file name (unused at present)
- * @param string $chunk the first 256 bytes of the file
- * @param string $proposed the MIME type proposed by the server
- *
- * @return Array: map of IE version to detected MIME type
- */
- public function getRealMimesFromData( $fileName, $chunk, $proposed ) {
- $types = $this->getMimesFromData( $fileName, $chunk, $proposed );
- $types = array_map( array( $this, 'translateMimeType' ), $types );
- return $types;
- }
-
- /**
- * Translate a MIME type from IE's idiosyncratic private types into
- * more commonly understood type strings
- * @param $type
- * @return string
- */
- public function translateMimeType( $type ) {
- static $table = array(
- 'image/pjpeg' => 'image/jpeg',
- 'image/x-png' => 'image/png',
- 'image/x-wmf' => 'application/x-msmetafile',
- 'image/bmp' => 'image/x-bmp',
- 'application/x-zip-compressed' => 'application/zip',
- 'application/x-compressed' => 'application/x-compress',
- 'application/x-gzip-compressed' => 'application/x-gzip',
- 'audio/mid' => 'audio/midi',
- );
- if ( isset( $table[$type] ) ) {
- $type = $table[$type];
- }
- return $type;
- }
-
- /**
- * Get the untranslated MIME types for all known versions
- *
- * @param string $fileName the file name (unused at present)
- * @param string $chunk the first 256 bytes of the file
- * @param string $proposed the MIME type proposed by the server
- *
- * @return Array: map of IE version to detected MIME type
- */
- public function getMimesFromData( $fileName, $chunk, $proposed ) {
- $types = array();
- foreach ( $this->versions as $version ) {
- $types[$version] = $this->getMimeTypeForVersion( $version, $fileName, $chunk, $proposed );
- }
- return $types;
- }
-
- /**
- * Get the MIME type for a given named version
- * @param $version
- * @param $fileName
- * @param $chunk
- * @param $proposed
- * @return bool|string
- */
- protected function getMimeTypeForVersion( $version, $fileName, $chunk, $proposed ) {
- // Strip text after a semicolon
- $semiPos = strpos( $proposed, ';' );
- if ( $semiPos !== false ) {
- $proposed = substr( $proposed, 0, $semiPos );
- }
-
- $proposedFormat = $this->getDataFormat( $version, $proposed );
- if ( $proposedFormat == 'unknown'
- && $proposed != 'multipart/mixed'
- && $proposed != 'multipart/x-mixed-replace' )
- {
- return $proposed;
- }
- if ( strval( $chunk ) === '' ) {
- return $proposed;
- }
-
- // Truncate chunk at 255 bytes
- $chunk = substr( $chunk, 0, 255 );
-
- // IE does the Check*Headers() calls last, and instead does the following image
- // type checks by directly looking for the magic numbers. What I do here should
- // have the same effect since the magic number checks are identical in both cases.
- $result = $this->sampleData( $version, $chunk );
- $sampleFound = $result['found'];
- $counters = $result['counters'];
- $binaryType = $this->checkBinaryHeaders( $version, $chunk );
- $textType = $this->checkTextHeaders( $version, $chunk );
-
- if ( $proposed == 'text/html' && isset( $sampleFound['html'] ) ) {
- return 'text/html';
- }
- if ( $proposed == 'image/gif' && $binaryType == 'image/gif' ) {
- return 'image/gif';
- }
- if ( ( $proposed == 'image/pjpeg' || $proposed == 'image/jpeg' )
- && $binaryType == 'image/pjpeg' )
- {
- return $proposed;
- }
- // PNG check added in IE 7
- if ( $version >= 'ie07'
- && ( $proposed == 'image/x-png' || $proposed == 'image/png' )
- && $binaryType == 'image/x-png' )
- {
- return $proposed;
- }
-
- // CDF was removed in IE 7 so it won't be in $sampleFound for later versions
- if ( isset( $sampleFound['cdf'] ) ) {
- return 'application/x-cdf';
- }
-
- // RSS and Atom were added in IE 7 so they won't be in $sampleFound for
- // previous versions
- if ( isset( $sampleFound['rss'] ) ) {
- return 'application/rss+xml';
- }
- if ( isset( $sampleFound['rdf-tag'] )
- && isset( $sampleFound['rdf-url'] )
- && isset( $sampleFound['rdf-purl'] ) )
- {
- return 'application/rss+xml';
- }
- if ( isset( $sampleFound['atom'] ) ) {
- return 'application/atom+xml';
- }
-
- if ( isset( $sampleFound['xml'] ) ) {
- // TODO: I'm not sure under what circumstances this flag is enabled
- if ( strpos( $version, 'strict' ) !== false ) {
- if ( $proposed == 'text/html' || $proposed == 'text/xml' ) {
- return 'text/xml';
- }
- } else {
- return 'text/xml';
- }
- }
- if ( isset( $sampleFound['html'] ) ) {
- // TODO: I'm not sure under what circumstances this flag is enabled
- if ( strpos( $version, 'nohtml' ) !== false ) {
- if ( $proposed == 'text/plain' ) {
- return 'text/html';
- }
- } else {
- return 'text/html';
- }
- }
- if ( isset( $sampleFound['xbm'] ) ) {
- return 'image/x-bitmap';
- }
- if ( isset( $sampleFound['binhex'] ) ) {
- return 'application/macbinhex40';
- }
- if ( isset( $sampleFound['scriptlet'] ) ) {
- if ( strpos( $version, 'strict' ) !== false ) {
- if ( $proposed == 'text/plain' || $proposed == 'text/scriptlet' ) {
- return 'text/scriptlet';
- }
- } else {
- return 'text/scriptlet';
- }
- }
-
- // Freaky heuristics to determine if the data is text or binary
- // The heuristic is of course broken for non-ASCII text
- if ( $counters['ctrl'] != 0 && ( $counters['ff'] + $counters['low'] )
- < ( $counters['ctrl'] + $counters['high'] ) * 16 )
- {
- $kindOfBinary = true;
- $type = $binaryType ? $binaryType : $textType;
- if ( $type === false ) {
- $type = 'application/octet-stream';
- }
- } else {
- $kindOfBinary = false;
- $type = $textType ? $textType : $binaryType;
- if ( $type === false ) {
- $type = 'text/plain';
- }
- }
-
- // Check if the output format is ambiguous
- // This generally means that detection failed, real types aren't ambiguous
- $detectedFormat = $this->getDataFormat( $version, $type );
- if ( $detectedFormat != 'ambiguous' ) {
- return $type;
- }
-
- if ( $proposedFormat != 'ambiguous' ) {
- // FormatAgreesWithData()
- if ( $proposedFormat == 'text' && !$kindOfBinary ) {
- return $proposed;
- }
- if ( $proposedFormat == 'binary' && $kindOfBinary ) {
- return $proposed;
- }
- if ( $proposedFormat == 'html' ) {
- return $proposed;
- }
- }
-
- // Find a MIME type by searching the registry for the file extension.
- $dotPos = strrpos( $fileName, '.' );
- if ( $dotPos === false ) {
- return $type;
- }
- $ext = substr( $fileName, $dotPos );
- if ( isset( $this->registry[$ext] ) ) {
- return $this->registry[$ext];
- }
-
- // TODO: If the extension has an application registered to it, IE will return
- // application/octet-stream. We'll skip that, so we could erroneously
- // return text/plain or application/x-netcdf where application/octet-stream
- // would be correct.
-
- return $type;
- }
-
- /**
- * Check for text headers at the start of the chunk
- * Confirmed same in 5 and 7.
- * @param $version
- * @param $chunk
- * @return bool|string
- */
- private function checkTextHeaders( $version, $chunk ) {
- $chunk2 = substr( $chunk, 0, 2 );
- $chunk4 = substr( $chunk, 0, 4 );
- $chunk5 = substr( $chunk, 0, 5 );
- if ( $chunk4 == '%PDF' ) {
- return 'application/pdf';
- }
- if ( $chunk2 == '%!' ) {
- return 'application/postscript';
- }
- if ( $chunk5 == '{\\rtf' ) {
- return 'text/richtext';
- }
- if ( $chunk5 == 'begin' ) {
- return 'application/base64';
- }
- return false;
- }
-
- /**
- * Check for binary headers at the start of the chunk
- * Confirmed same in 5 and 7.
- * @param $version
- * @param $chunk
- * @return bool|string
- */
- private function checkBinaryHeaders( $version, $chunk ) {
- $chunk2 = substr( $chunk, 0, 2 );
- $chunk3 = substr( $chunk, 0, 3 );
- $chunk4 = substr( $chunk, 0, 4 );
- $chunk5 = substr( $chunk, 0, 5 );
- $chunk5uc = strtoupper( $chunk5 );
- $chunk8 = substr( $chunk, 0, 8 );
- if ( $chunk5uc == 'GIF87' || $chunk5uc == 'GIF89' ) {
- return 'image/gif';
- }
- if ( $chunk2 == "\xff\xd8" ) {
- return 'image/pjpeg'; // actually plain JPEG but this is what IE returns
- }
-
- if ( $chunk2 == 'BM'
- && substr( $chunk, 6, 2 ) == "\000\000"
- && substr( $chunk, 8, 2 ) == "\000\000" )
- {
- return 'image/bmp'; // another non-standard MIME
- }
- if ( $chunk4 == 'RIFF'
- && substr( $chunk, 8, 4 ) == 'WAVE' )
- {
- return 'audio/wav';
- }
- // These were integer literals in IE
- // Perhaps the author was not sure what the target endianness was
- if ( $chunk4 == ".sd\000"
- || $chunk4 == ".snd"
- || $chunk4 == "\000ds."
- || $chunk4 == "dns." )
- {
- return 'audio/basic';
- }
- if ( $chunk3 == "MM\000" ) {
- return 'image/tiff';
- }
- if ( $chunk2 == 'MZ' ) {
- return 'application/x-msdownload';
- }
- if ( $chunk8 == "\x89PNG\x0d\x0a\x1a\x0a" ) {
- return 'image/x-png'; // [sic]
- }
- if ( strlen( $chunk ) >= 5 ) {
- $byte2 = ord( $chunk[2] );
- $byte4 = ord( $chunk[4] );
- if ( $byte2 >= 3 && $byte2 <= 31 && $byte4 == 0 && $chunk2 == 'JG' ) {
- return 'image/x-jg';
- }
- }
- // More endian confusion?
- if ( $chunk4 == 'MROF' ) {
- return 'audio/x-aiff';
- }
- $chunk4_8 = substr( $chunk, 8, 4 );
- if ( $chunk4 == 'FORM' && ( $chunk4_8 == 'AIFF' || $chunk4_8 == 'AIFC' ) ) {
- return 'audio/x-aiff';
- }
- if ( $chunk4 == 'RIFF' && $chunk4_8 == 'AVI ' ) {
- return 'video/avi';
- }
- if ( $chunk4 == "\x00\x00\x01\xb3" || $chunk4 == "\x00\x00\x01\xba" ) {
- return 'video/mpeg';
- }
- if ( $chunk4 == "\001\000\000\000"
- && substr( $chunk, 40, 4 ) == ' EMF' )
- {
- return 'image/x-emf';
- }
- if ( $chunk4 == "\xd7\xcd\xc6\x9a" ) {
- return 'image/x-wmf';
- }
- if ( $chunk4 == "\xca\xfe\xba\xbe" ) {
- return 'application/java';
- }
- if ( $chunk2 == 'PK' ) {
- return 'application/x-zip-compressed';
- }
- if ( $chunk2 == "\x1f\x9d" ) {
- return 'application/x-compressed';
- }
- if ( $chunk2 == "\x1f\x8b" ) {
- return 'application/x-gzip-compressed';
- }
- // Skip redundant check for ZIP
- if ( $chunk5 == "MThd\000" ) {
- return 'audio/mid';
- }
- if ( $chunk4 == '%PDF' ) {
- return 'application/pdf';
- }
- return false;
- }
-
- /**
- * Do heuristic checks on the bulk of the data sample.
- * Search for HTML tags.
- * @param $version
- * @param $chunk
- * @return array
- */
- protected function sampleData( $version, $chunk ) {
- $found = array();
- $counters = array(
- 'ctrl' => 0,
- 'high' => 0,
- 'low' => 0,
- 'lf' => 0,
- 'cr' => 0,
- 'ff' => 0
- );
- $htmlTags = array(
- 'html',
- 'head',
- 'title',
- 'body',
- 'script',
- 'a href',
- 'pre',
- 'img',
- 'plaintext',
- 'table'
- );
- $rdfUrl = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
- $rdfPurl = 'http://purl.org/rss/1.0/';
- $xbmMagic1 = '#define';
- $xbmMagic2 = '_width';
- $xbmMagic3 = '_bits';
- $binhexMagic = 'converted with BinHex';
- $chunkLength = strlen( $chunk );
-
- for ( $offset = 0; $offset < $chunkLength; $offset++ ) {
- $curChar = $chunk[$offset];
- if ( $curChar == "\x0a" ) {
- $counters['lf']++;
- continue;
- } elseif ( $curChar == "\x0d" ) {
- $counters['cr']++;
- continue;
- } elseif ( $curChar == "\x0c" ) {
- $counters['ff']++;
- continue;
- } elseif ( $curChar == "\t" ) {
- $counters['low']++;
- continue;
- } elseif ( ord( $curChar ) < 32 ) {
- $counters['ctrl']++;
- continue;
- } elseif ( ord( $curChar ) >= 128 ) {
- $counters['high']++;
- continue;
- }
-
- $counters['low']++;
- if ( $curChar == '<' ) {
- // XML
- $remainder = substr( $chunk, $offset + 1 );
- if ( !strncasecmp( $remainder, '?XML', 4 ) ) {
- $nextChar = substr( $chunk, $offset + 5, 1 );
- if ( $nextChar == ':' || $nextChar == ' ' || $nextChar == "\t" ) {
- $found['xml'] = true;
- }
- }
- // Scriptlet (JSP)
- if ( !strncasecmp( $remainder, 'SCRIPTLET', 9 ) ) {
- $found['scriptlet'] = true;
- break;
- }
- // HTML
- foreach ( $htmlTags as $tag ) {
- if ( !strncasecmp( $remainder, $tag, strlen( $tag ) ) ) {
- $found['html'] = true;
- }
- }
- // Skip broken check for additional tags (HR etc.)
-
- // CHANNEL replaced by RSS, RDF and FEED in IE 7
- if ( $version < 'ie07' ) {
- if ( !strncasecmp( $remainder, 'CHANNEL', 7 ) ) {
- $found['cdf'] = true;
- }
- } else {
- // RSS
- if ( !strncasecmp( $remainder, 'RSS', 3 ) ) {
- $found['rss'] = true;
- break; // return from SampleData
- }
- if ( !strncasecmp( $remainder, 'rdf:RDF', 7 ) ) {
- $found['rdf-tag'] = true;
- // no break
- }
- if ( !strncasecmp( $remainder, 'FEED', 4 ) ) {
- $found['atom'] = true;
- break;
- }
- }
- continue;
- }
- // Skip broken check for -->
-
- // RSS URL checks
- // For some reason both URLs must appear before it is recognised
- $remainder = substr( $chunk, $offset );
- if ( !strncasecmp( $remainder, $rdfUrl, strlen( $rdfUrl ) ) ) {
- $found['rdf-url'] = true;
- if ( isset( $found['rdf-tag'] )
- && isset( $found['rdf-purl'] ) ) // [sic]
- {
- break;
- }
- continue;
- }
-
- if ( !strncasecmp( $remainder, $rdfPurl, strlen( $rdfPurl ) ) ) {
- if ( isset( $found['rdf-tag'] )
- && isset( $found['rdf-url'] ) ) // [sic]
- {
- break;
- }
- continue;
- }
-
- // XBM checks
- if ( !strncasecmp( $remainder, $xbmMagic1, strlen( $xbmMagic1 ) ) ) {
- $found['xbm1'] = true;
- continue;
- }
- if ( $curChar == '_' ) {
- if ( isset( $found['xbm2'] ) ) {
- if ( !strncasecmp( $remainder, $xbmMagic3, strlen( $xbmMagic3 ) ) ) {
- $found['xbm'] = true;
- break;
- }
- } elseif ( isset( $found['xbm1'] ) ) {
- if ( !strncasecmp( $remainder, $xbmMagic2, strlen( $xbmMagic2 ) ) ) {
- $found['xbm2'] = true;
- }
- }
- }
-
- // BinHex
- if ( !strncmp( $remainder, $binhexMagic, strlen( $binhexMagic ) ) ) {
- $found['binhex'] = true;
- }
- }
- return array( 'found' => $found, 'counters' => $counters );
- }
-
- /**
- * @param $version
- * @param $type
- * @return int|string
- */
- protected function getDataFormat( $version, $type ) {
- $types = $this->typeTable[$version];
- if ( $type == '(null)' || strval( $type ) === '' ) {
- return 'ambiguous';
- }
- foreach ( $types as $format => $list ) {
- if ( in_array( $type, $list ) ) {
- return $format;
- }
- }
- return 'unknown';
- }
-}
diff --git a/includes/libs/IEUrlExtension.php b/includes/libs/IEUrlExtension.php
deleted file mode 100644
index 49d05d4b..00000000
--- a/includes/libs/IEUrlExtension.php
+++ /dev/null
@@ -1,271 +0,0 @@
-<?php
-/**
- * Checks for validity of requested URL's extension.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * Internet Explorer derives a cache filename from a URL, and then in certain
- * circumstances, uses the extension of the resulting file to determine the
- * content type of the data, ignoring the Content-Type header.
- *
- * This can be a problem, especially when non-HTML content is sent by MediaWiki,
- * and Internet Explorer interprets it as HTML, exposing an XSS vulnerability.
- *
- * Usually the script filename (e.g. api.php) is present in the URL, and this
- * makes Internet Explorer think the extension is a harmless script extension.
- * But Internet Explorer 6 and earlier allows the script extension to be
- * obscured by encoding the dot as "%2E".
- *
- * This class contains functions which help in detecting and dealing with this
- * situation.
- *
- * Checking the URL for a bad extension is somewhat complicated due to the fact
- * that CGI doesn't provide a standard method to determine the URL. Instead it
- * is necessary to pass a subset of $_SERVER variables, which we then attempt
- * to use to guess parts of the URL.
- */
-class IEUrlExtension {
- /**
- * Check a subset of $_SERVER (or the whole of $_SERVER if you like)
- * to see if it indicates that the request was sent with a bad file
- * extension. Returns true if the request should be denied or modified,
- * false otherwise. The relevant $_SERVER elements are:
- *
- * - SERVER_SOFTWARE
- * - REQUEST_URI
- * - QUERY_STRING
- * - PATH_INFO
- *
- * If the a variable is unset in $_SERVER, it should be unset in $vars.
- *
- * @param array $vars A subset of $_SERVER.
- * @param array $extWhitelist Extensions which are allowed, assumed harmless.
- * @return bool
- */
- public static function areServerVarsBad( $vars, $extWhitelist = array() ) {
- // Check QUERY_STRING or REQUEST_URI
- if ( isset( $vars['SERVER_SOFTWARE'] )
- && isset( $vars['REQUEST_URI'] )
- && self::haveUndecodedRequestUri( $vars['SERVER_SOFTWARE'] ) )
- {
- $urlPart = $vars['REQUEST_URI'];
- } elseif ( isset( $vars['QUERY_STRING'] ) ) {
- $urlPart = $vars['QUERY_STRING'];
- } else {
- $urlPart = '';
- }
-
- if ( self::isUrlExtensionBad( $urlPart, $extWhitelist ) ) {
- return true;
- }
-
- // Some servers have PATH_INFO but not REQUEST_URI, so we check both
- // to be on the safe side.
- if ( isset( $vars['PATH_INFO'] )
- && self::isUrlExtensionBad( $vars['PATH_INFO'], $extWhitelist ) )
- {
- return true;
- }
-
- // All checks passed
- return false;
- }
-
- /**
- * Given a right-hand portion of a URL, determine whether IE would detect
- * a potentially harmful file extension.
- *
- * @param string $urlPart The right-hand portion of a URL
- * @param array $extWhitelist An array of file extensions which may occur in this
- * URL, and which should be allowed.
- * @return bool
- */
- public static function isUrlExtensionBad( $urlPart, $extWhitelist = array() ) {
- if ( strval( $urlPart ) === '' ) {
- return false;
- }
-
- $extension = self::findIE6Extension( $urlPart );
- if ( strval( $extension ) === '' ) {
- // No extension or empty extension
- return false;
- }
-
- if ( in_array( $extension, array( 'php', 'php5' ) ) ) {
- // Script extension, OK
- return false;
- }
- if ( in_array( $extension, $extWhitelist ) ) {
- // Whitelisted extension
- return false;
- }
-
- if ( !preg_match( '/^[a-zA-Z0-9_-]+$/', $extension ) ) {
- // Non-alphanumeric extension, unlikely to be registered.
- //
- // The regex above is known to match all registered file extensions
- // in a default Windows XP installation. It's important to allow
- // extensions with ampersands and percent signs, since that reduces
- // the number of false positives substantially.
- return false;
- }
-
- // Possibly bad extension
- return true;
- }
-
- /**
- * Returns a variant of $url which will pass isUrlExtensionBad() but has the
- * same GET parameters, or false if it can't figure one out.
- * @param $url
- * @param $extWhitelist array
- * @return bool|string
- */
- public static function fixUrlForIE6( $url, $extWhitelist = array() ) {
- $questionPos = strpos( $url, '?' );
- if ( $questionPos === false ) {
- $beforeQuery = $url . '?';
- $query = '';
- } elseif ( $questionPos === strlen( $url ) - 1 ) {
- $beforeQuery = $url;
- $query = '';
- } else {
- $beforeQuery = substr( $url, 0, $questionPos + 1 );
- $query = substr( $url, $questionPos + 1 );
- }
-
- // Multiple question marks cause problems. Encode the second and
- // subsequent question mark.
- $query = str_replace( '?', '%3E', $query );
- // Append an invalid path character so that IE6 won't see the end of the
- // query string as an extension
- $query .= '&*';
- // Put the URL back together
- $url = $beforeQuery . $query;
- if ( self::isUrlExtensionBad( $url, $extWhitelist ) ) {
- // Avoid a redirect loop
- return false;
- }
- return $url;
- }
-
- /**
- * Determine what extension IE6 will infer from a certain query string.
- * If the URL has an extension before the question mark, IE6 will use
- * that and ignore the query string, but per the comment at
- * isPathInfoBad() we don't have a reliable way to determine the URL,
- * so isPathInfoBad() just passes in the query string for $url.
- * All entry points have safe extensions (php, php5) anyway, so
- * checking the query string is possibly overly paranoid but never
- * insecure.
- *
- * The criteria for finding an extension are as follows:
- * - a possible extension is a dot followed by one or more characters not
- * in <>\"/:|?.#
- * - if we find a possible extension followed by the end of the string or
- * a #, that's our extension
- * - if we find a possible extension followed by a ?, that's our extension
- * - UNLESS it's exe, dll or cgi, in which case we ignore it and continue
- * searching for another possible extension
- * - if we find a possible extension followed by a dot or another illegal
- * character, we ignore it and continue searching
- *
- * @param string $url URL
- * @return mixed Detected extension (string), or false if none found
- */
- public static function findIE6Extension( $url ) {
- $pos = 0;
- $hashPos = strpos( $url, '#' );
- if ( $hashPos !== false ) {
- $urlLength = $hashPos;
- } else {
- $urlLength = strlen( $url );
- }
- $remainingLength = $urlLength;
- while ( $remainingLength > 0 ) {
- // Skip ahead to the next dot
- $pos += strcspn( $url, '.', $pos, $remainingLength );
- if ( $pos >= $urlLength ) {
- // End of string, we're done
- return false;
- }
-
- // We found a dot. Skip past it
- $pos++;
- $remainingLength = $urlLength - $pos;
-
- // Check for illegal characters in our prospective extension,
- // or for another dot
- $nextPos = $pos + strcspn( $url, "<>\\\"/:|?*.", $pos, $remainingLength );
- if ( $nextPos >= $urlLength ) {
- // No illegal character or next dot
- // We have our extension
- return substr( $url, $pos, $urlLength - $pos );
- }
- if ( $url[$nextPos] === '?' ) {
- // We've found a legal extension followed by a question mark
- // If the extension is NOT exe, dll or cgi, return it
- $extension = substr( $url, $pos, $nextPos - $pos );
- if ( strcasecmp( $extension, 'exe' ) && strcasecmp( $extension, 'dll' ) &&
- strcasecmp( $extension, 'cgi' ) )
- {
- return $extension;
- }
- // Else continue looking
- }
- // We found an illegal character or another dot
- // Skip to that character and continue the loop
- $pos = $nextPos;
- $remainingLength = $urlLength - $pos;
- }
- return false;
- }
-
- /**
- * When passed the value of $_SERVER['SERVER_SOFTWARE'], this function
- * returns true if that server is known to have a REQUEST_URI variable
- * with %2E not decoded to ".". On such a server, it is possible to detect
- * whether the script filename has been obscured.
- *
- * The function returns false if the server is not known to have this
- * behavior. Microsoft IIS in particular is known to decode escaped script
- * filenames.
- *
- * SERVER_SOFTWARE typically contains either a plain string such as "Zeus",
- * or a specification in the style of a User-Agent header, such as
- * "Apache/1.3.34 (Unix) mod_ssl/2.8.25 OpenSSL/0.9.8a PHP/4.4.2"
- *
- * @param $serverSoftware
- * @return bool
- *
- */
- public static function haveUndecodedRequestUri( $serverSoftware ) {
- static $whitelist = array(
- 'Apache',
- 'Zeus',
- 'LiteSpeed' );
- if ( preg_match( '/^(.*?)($|\/| )/', $serverSoftware, $m ) ) {
- return in_array( $m[1], $whitelist );
- } else {
- return false;
- }
- }
-
-}
diff --git a/includes/search/SearchMssql.php b/includes/search/SearchMssql.php
deleted file mode 100644
index 0d7970de..00000000
--- a/includes/search/SearchMssql.php
+++ /dev/null
@@ -1,210 +0,0 @@
-<?php
-/**
- * Mssql search engine
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Search
- */
-
-/**
- * Search engine hook base class for Mssql (ConText).
- * @ingroup Search
- */
-class SearchMssql extends SearchDatabase {
- /**
- * Perform a full text search query and return a result set.
- *
- * @param string $term Raw search term
- * @return SqlSearchResultSet
- * @access public
- */
- function searchText( $term ) {
- $resultSet = $this->db->query( $this->getQuery( $this->filter( $term ), true ) );
- return new SqlSearchResultSet( $resultSet, $this->searchTerms );
- }
-
- /**
- * Perform a title-only search query and return a result set.
- *
- * @param string $term Raw search term
- * @return SqlSearchResultSet
- * @access public
- */
- function searchTitle( $term ) {
- $resultSet = $this->db->query( $this->getQuery( $this->filter( $term ), false ) );
- return new SqlSearchResultSet( $resultSet, $this->searchTerms );
- }
-
- /**
- * Return a partial WHERE clause to limit the search to the given namespaces
- *
- * @return string
- * @private
- */
- function queryNamespaces() {
- $namespaces = implode( ',', $this->namespaces );
- if ( $namespaces == '' ) {
- $namespaces = '0';
- }
- return 'AND page_namespace IN (' . $namespaces . ')';
- }
-
- /**
- * Return a LIMIT clause to limit results on the query.
- *
- * @param string $sql
- *
- * @return string
- */
- function queryLimit( $sql ) {
- return $this->db->limitResult( $sql, $this->limit, $this->offset );
- }
-
- /**
- * Does not do anything for generic search engine
- * subclasses may define this though
- *
- * @param string $filteredTerm
- * @param bool $fulltext
- * @return string
- */
- function queryRanking( $filteredTerm, $fulltext ) {
- return ' ORDER BY ftindex.[RANK] DESC'; // return ' ORDER BY score(1)';
- }
-
- /**
- * Construct the full SQL query to do the search.
- * The guts shoulds be constructed in queryMain()
- *
- * @param string $filteredTerm
- * @param bool $fulltext
- * @return string
- */
- function getQuery( $filteredTerm, $fulltext ) {
- return $this->queryLimit( $this->queryMain( $filteredTerm, $fulltext ) . ' ' .
- $this->queryNamespaces() . ' ' .
- $this->queryRanking( $filteredTerm, $fulltext ) . ' ' );
- }
-
- /**
- * Picks which field to index on, depending on what type of query.
- *
- * @param bool $fulltext
- * @return string
- */
- function getIndexField( $fulltext ) {
- return $fulltext ? 'si_text' : 'si_title';
- }
-
- /**
- * Get the base part of the search query.
- *
- * @param string $filteredTerm
- * @param bool $fulltext
- * @return string
- * @private
- */
- function queryMain( $filteredTerm, $fulltext ) {
- $match = $this->parseQuery( $filteredTerm, $fulltext );
- $page = $this->db->tableName( 'page' );
- $searchindex = $this->db->tableName( 'searchindex' );
-
- return 'SELECT page_id, page_namespace, page_title, ftindex.[RANK]' .
- "FROM $page,FREETEXTTABLE($searchindex , $match, LANGUAGE 'English') as ftindex " .
- 'WHERE page_id=ftindex.[KEY] ';
- }
-
- /** @todo document
- * @param string $filteredText
- * @param bool $fulltext
- * @return string
- */
- function parseQuery( $filteredText, $fulltext ) {
- global $wgContLang;
- $lc = $this->legalSearchChars();
- $this->searchTerms = array();
-
- # @todo FIXME: This doesn't handle parenthetical expressions.
- $m = array();
- $q = array();
-
- if ( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
- $filteredText, $m, PREG_SET_ORDER ) ) {
- foreach ( $m as $terms ) {
- $q[] = $terms[1] . $wgContLang->normalizeForSearch( $terms[2] );
-
- if ( !empty( $terms[3] ) ) {
- $regexp = preg_quote( $terms[3], '/' );
- if ( $terms[4] ) {
- $regexp .= "[0-9A-Za-z_]+";
- }
- } else {
- $regexp = preg_quote( str_replace( '"', '', $terms[2] ), '/' );
- }
- $this->searchTerms[] = $regexp;
- }
- }
-
- $searchon = $this->db->addQuotes( join( ',', $q ) );
- $field = $this->getIndexField( $fulltext );
- return "$field, $searchon";
- }
-
- /**
- * Create or update the search index record for the given page.
- * Title and text should be pre-processed.
- *
- * @param int $id
- * @param string $title
- * @param string $text
- * @return bool|ResultWrapper
- */
- function update( $id, $title, $text ) {
- // We store the column data as UTF-8 byte order marked binary stream
- // because we are invoking the plain text IFilter on it so that, and we want it
- // to properly decode the stream as UTF-8. SQL doesn't support UTF8 as a data type
- // but the indexer will correctly handle it by this method. Since all we are doing
- // is passing this data to the indexer and never retrieving it via PHP, this will save space
- $table = $this->db->tableName( 'searchindex' );
- $utf8bom = '0xEFBBBF';
- $si_title = $utf8bom . bin2hex( $title );
- $si_text = $utf8bom . bin2hex( $text );
- $sql = "DELETE FROM $table WHERE si_page = $id;";
- $sql .= "INSERT INTO $table (si_page, si_title, si_text) VALUES ($id, $si_title, $si_text)";
- return $this->db->query( $sql, 'SearchMssql::update' );
- }
-
- /**
- * Update a search index record's title only.
- * Title should be pre-processed.
- *
- * @param int $id
- * @param string $title
- * @return bool|ResultWrapper
- */
- function updateTitle( $id, $title ) {
- $table = $this->db->tableName( 'searchindex' );
-
- // see update for why we are using the utf8bom
- $utf8bom = '0xEFBBBF';
- $si_title = $utf8bom . bin2hex( $title );
- $sql = "DELETE FROM $table WHERE si_page = $id;";
- $sql .= "INSERT INTO $table (si_page, si_title, si_text) VALUES ($id, $si_title, 0x00)";
- return $this->db->query( $sql, 'SearchMssql::updateTitle' );
- }
-}
diff --git a/includes/search/SearchOracle.php b/includes/search/SearchOracle.php
deleted file mode 100644
index 58211484..00000000
--- a/includes/search/SearchOracle.php
+++ /dev/null
@@ -1,273 +0,0 @@
-<?php
-/**
- * Oracle search engine
- *
- * Copyright © 2004 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Search
- */
-
-/**
- * Search engine hook base class for Oracle (ConText).
- * @ingroup Search
- */
-class SearchOracle extends SearchDatabase {
- private $reservedWords = array(
- 'ABOUT' => 1,
- 'ACCUM' => 1,
- 'AND' => 1,
- 'BT' => 1,
- 'BTG' => 1,
- 'BTI' => 1,
- 'BTP' => 1,
- 'FUZZY' => 1,
- 'HASPATH' => 1,
- 'INPATH' => 1,
- 'MINUS' => 1,
- 'NEAR' => 1,
- 'NOT' => 1,
- 'NT' => 1,
- 'NTG' => 1,
- 'NTI' => 1,
- 'NTP' => 1,
- 'OR' => 1,
- 'PT' => 1,
- 'RT' => 1,
- 'SQE' => 1,
- 'SYN' => 1,
- 'TR' => 1,
- 'TRSYN' => 1,
- 'TT' => 1,
- 'WITHIN' => 1,
- );
-
- /**
- * Perform a full text search query and return a result set.
- *
- * @param string $term Raw search term
- * @return SqlSearchResultSet
- */
- function searchText( $term ) {
- if ( $term == '' ) {
- return new SqlSearchResultSet( false, '' );
- }
-
- $resultSet = $this->db->query( $this->getQuery( $this->filter( $term ), true ) );
- return new SqlSearchResultSet( $resultSet, $this->searchTerms );
- }
-
- /**
- * Perform a title-only search query and return a result set.
- *
- * @param string $term Raw search term
- * @return SqlSearchResultSet
- */
- function searchTitle( $term ) {
- if ( $term == '' ) {
- return new SqlSearchResultSet( false, '' );
- }
-
- $resultSet = $this->db->query( $this->getQuery( $this->filter( $term ), false ) );
- return new SqlSearchResultSet( $resultSet, $this->searchTerms );
- }
-
- /**
- * Return a partial WHERE clause to limit the search to the given namespaces
- * @return string
- */
- function queryNamespaces() {
- if ( is_null( $this->namespaces ) ) {
- return '';
- }
- if ( !count( $this->namespaces ) ) {
- $namespaces = '0';
- } else {
- $namespaces = $this->db->makeList( $this->namespaces );
- }
- return 'AND page_namespace IN (' . $namespaces . ')';
- }
-
- /**
- * Return a LIMIT clause to limit results on the query.
- *
- * @param string $sql
- *
- * @return string
- */
- function queryLimit( $sql ) {
- return $this->db->limitResult( $sql, $this->limit, $this->offset );
- }
-
- /**
- * Does not do anything for generic search engine
- * subclasses may define this though
- *
- * @param string $filteredTerm
- * @param bool $fulltext
- * @return string
- */
- function queryRanking( $filteredTerm, $fulltext ) {
- return ' ORDER BY score(1)';
- }
-
- /**
- * Construct the full SQL query to do the search.
- * The guts shoulds be constructed in queryMain()
- * @param string $filteredTerm
- * @param bool $fulltext
- * @return string
- */
- function getQuery( $filteredTerm, $fulltext ) {
- return $this->queryLimit( $this->queryMain( $filteredTerm, $fulltext ) . ' ' .
- $this->queryNamespaces() . ' ' .
- $this->queryRanking( $filteredTerm, $fulltext ) . ' ' );
- }
-
- /**
- * Picks which field to index on, depending on what type of query.
- * @param bool $fulltext
- * @return string
- */
- function getIndexField( $fulltext ) {
- return $fulltext ? 'si_text' : 'si_title';
- }
-
- /**
- * Get the base part of the search query.
- *
- * @param string $filteredTerm
- * @param bool $fulltext
- * @return string
- */
- function queryMain( $filteredTerm, $fulltext ) {
- $match = $this->parseQuery( $filteredTerm, $fulltext );
- $page = $this->db->tableName( 'page' );
- $searchindex = $this->db->tableName( 'searchindex' );
- return 'SELECT page_id, page_namespace, page_title ' .
- "FROM $page,$searchindex " .
- 'WHERE page_id=si_page AND ' . $match;
- }
-
- /**
- * Parse a user input search string, and return an SQL fragment to be used
- * as part of a WHERE clause
- * @param string $filteredText
- * @param bool $fulltext
- * @return string
- */
- function parseQuery( $filteredText, $fulltext ) {
- global $wgContLang;
- $lc = $this->legalSearchChars();
- $this->searchTerms = array();
-
- # @todo FIXME: This doesn't handle parenthetical expressions.
- $m = array();
- $searchon = '';
- if ( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
- $filteredText, $m, PREG_SET_ORDER ) ) {
- foreach ( $m as $terms ) {
- // Search terms in all variant forms, only
- // apply on wiki with LanguageConverter
- $temp_terms = $wgContLang->autoConvertToAllVariants( $terms[2] );
- if ( is_array( $temp_terms ) ) {
- $temp_terms = array_unique( array_values( $temp_terms ) );
- foreach ( $temp_terms as $t ) {
- $searchon .= ( $terms[1] == '-' ? ' ~' : ' & ' ) . $this->escapeTerm( $t );
- }
- }
- else {
- $searchon .= ( $terms[1] == '-' ? ' ~' : ' & ' ) . $this->escapeTerm( $terms[2] );
- }
- if ( !empty( $terms[3] ) ) {
- $regexp = preg_quote( $terms[3], '/' );
- if ( $terms[4] ) {
- $regexp .= "[0-9A-Za-z_]+";
- }
- } else {
- $regexp = preg_quote( str_replace( '"', '', $terms[2] ), '/' );
- }
- $this->searchTerms[] = $regexp;
- }
- }
-
- $searchon = $this->db->addQuotes( ltrim( $searchon, ' &' ) );
- $field = $this->getIndexField( $fulltext );
- return " CONTAINS($field, $searchon, 1) > 0 ";
- }
-
- private function escapeTerm( $t ) {
- global $wgContLang;
- $t = $wgContLang->normalizeForSearch( $t );
- $t = isset( $this->reservedWords[strtoupper( $t )] ) ? '{' . $t . '}' : $t;
- $t = preg_replace( '/^"(.*)"$/', '($1)', $t );
- $t = preg_replace( '/([-&|])/', '\\\\$1', $t );
- return $t;
- }
-
- /**
- * Create or update the search index record for the given page.
- * Title and text should be pre-processed.
- *
- * @param int $id
- * @param string $title
- * @param string $text
- */
- function update( $id, $title, $text ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->replace( 'searchindex',
- array( 'si_page' ),
- array(
- 'si_page' => $id,
- 'si_title' => $title,
- 'si_text' => $text
- ), 'SearchOracle::update' );
-
- // Sync the index
- // We need to specify the DB name (i.e. user/schema) here so that
- // it can work from the installer, where
- // ALTER SESSION SET CURRENT_SCHEMA = ...
- // was used.
- $dbw->query( "CALL ctx_ddl.sync_index(" .
- $dbw->addQuotes( $dbw->getDBname() . '.' . $dbw->tableName( 'si_text_idx', 'raw' ) ) . ")" );
- $dbw->query( "CALL ctx_ddl.sync_index(" .
- $dbw->addQuotes( $dbw->getDBname() . '.' . $dbw->tableName( 'si_title_idx', 'raw' ) ) . ")" );
- }
-
- /**
- * Update a search index record's title only.
- * Title should be pre-processed.
- *
- * @param int $id
- * @param string $title
- */
- function updateTitle( $id, $title ) {
- $dbw = wfGetDB( DB_MASTER );
-
- $dbw->update( 'searchindex',
- array( 'si_title' => $title ),
- array( 'si_page' => $id ),
- 'SearchOracle::updateTitle',
- array() );
- }
-
- public static function legalSearchChars() {
- return "\"" . parent::legalSearchChars();
- }
-}