diff options
Diffstat (limited to 'includes')
-rw-r--r-- | includes/db/DatabaseMssql.php | 1490 | ||||
-rw-r--r-- | includes/db/DatabaseOracle.php | 1546 | ||||
-rw-r--r-- | includes/installer/MssqlInstaller.php | 736 | ||||
-rw-r--r-- | includes/installer/MssqlUpdater.php | 143 | ||||
-rw-r--r-- | includes/installer/OracleInstaller.php | 344 | ||||
-rw-r--r-- | includes/installer/OracleUpdater.php | 289 | ||||
-rw-r--r-- | includes/libs/IEContentAnalyzer.php | 851 | ||||
-rw-r--r-- | includes/libs/IEUrlExtension.php | 271 | ||||
-rw-r--r-- | includes/search/SearchMssql.php | 210 | ||||
-rw-r--r-- | includes/search/SearchOracle.php | 273 |
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(); - } -} |