diff --git a/.phpcs.xml b/.phpcs.xml index 62a2ec8..aee7870 100644 --- a/.phpcs.xml +++ b/.phpcs.xml @@ -10,6 +10,8 @@ --> + + diff --git a/composer.json b/composer.json index 05bf531..f199cfa 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,8 @@ "symfony/css-selector": "~5.1" }, "require-dev": { - "mediawiki/mediawiki-codesniffer": "37.0.0", + "roave/security-advisories": "dev-latest", + "mediawiki/mediawiki-codesniffer": "36.0.0", "mediawiki/minus-x": "1.1.1", "php-parallel-lint/php-console-highlighter": "0.5.0", "php-parallel-lint/php-parallel-lint": "1.3.0" diff --git a/extension.json b/extension.json index ad02577..317909b 100644 --- a/extension.json +++ b/extension.json @@ -46,12 +46,16 @@ "EDConnectorFile": "includes/connectors/EDConnectorFile.php", "EDConnectorDirectory": "includes/connectors/EDConnectorDirectory.php", "EDConnectorDb": "includes/connectors/EDConnectorDb.php", + "EDConnectorComposed": "includes/connectors/EDConnectorComposed.php", "EDConnectorRelational": "includes/connectors/EDConnectorRelational.php", + "EDConnectorRdbms": "includes/connectors/EDConnectorRdbms.php", "EDConnectorSql": "includes/connectors/EDConnectorSql.php", "EDConnectorSqlite": "includes/connectors/EDConnectorSqlite.php", "EDConnectorMongodb": "includes/connectors/EDConnectorMongodb.php", "EDConnectorMongodb5": "includes/connectors/EDConnectorMongodb5.php", "EDConnectorMongodb7": "includes/connectors/EDConnectorMongodb7.php", + "EDConnectorPrepared": "includes/connectors/EDConnectorPrepared.php", + "EDConnectorPreparedMysql": "includes/connectors/EDConnectorPreparedMysql.php", "EDParserBase": "includes/parsers/EDParserBase.php", "EDParserText": "includes/parsers/EDParserText.php", "EDParserRegex": "includes/parsers/EDParserRegex.php", @@ -71,34 +75,20 @@ }, "config": { "_prefix": "edg", - "Values": [], + "ExternalValueVerbose": true, "StringReplacements": [], - "CacheTable": null, - "AlwaysAllowStaleCache": true, + "CacheTable": null, "AlwaysAllowStaleCache": true, "CacheExpireTime": 604800, "AllowSSL": true, - "ExternalValueVerbose": true, - "CacheExpireTime": 604800, - "TryEncodings": [ "ASCII", "UTF-8", "Windows-1251", "Windows-1252", "Windows-1254", "KOI8-R", "ISO-8859-1" ], - "DBServer": [], - "DBServerType ": [], - "DBName": [], - "DBUser": [], - "DBPass": [], - "DBDirectory": [], - "DBFlags": [], - "DBTablePrefix": [], - "DirectoryPath": [], - "FilePath": [], - "LDAPServer": [], - "LDAPUser": [], - "LDAPPass": [], - "LDAPBaseDN": [], "HTTPOptions": { "timeout": "default" }, + "TryEncodings": [ "ASCII", "UTF-8", "Windows-1251", "Windows-1252", "Windows-1254", "KOI8-R", "ISO-8859-1" ], + "DBServer": [], "DBServerType ": [], "DBName": [], "DBUser": [], "DBPass": [], "DBDirectory": [], "DBFlags": [], "DBTablePrefix": [], "DBPrepared": [], "DBTypes": [], + "DirectoryPath": [], "FilePath": [], + "LDAPServer": [], "LDAPUser": [], "LDAPPass": [], "LDAPBaseDN": [], "Secrets": { - "server": [ "DBServer", "DBServerType", "DBName", "DBUser", "DBPass", "DBDirectory", "DBFlags", "DBTablePrefix" ], - "db": [ "DBServer", "DBServerType", "DBName", "DBUser", "DBPass", "DBDirectory", "DBFlags", "DBTablePrefix" ], + "server": [ "DBServer", "DBServerType", "DBName", "DBUser", "DBPass", "DBDirectory", "DBFlags", "DBTablePrefix", "DBPrepared", "DBTypes" ], + "db": [ "DBServer", "DBServerType", "DBName", "DBUser", "DBPass", "DBDirectory", "DBFlags", "DBTablePrefix", "DBPrepared", "DBTypes" ], "directory": [ "DirectoryPath" ], "file": [ "FilePath" ], "domain": [ "LDAPServer", "LDAPUser", "LDAPPass", "LDAPBaseDN" ] @@ -110,6 +100,7 @@ [ { "__pf": "get_file_data" }, "EDConnectorFile" ], [ { "__pf": "get_soap_data" }, "EDConnectorSoap" ], [ { "__pf": "get_ldap_data" }, "EDConnectorLdap" ], + [ { "__pf": "get_db_data", "DBServerType": "mysql", "DBPrepared": true }, "EDConnectorPreparedMysql" ], [ { "__pf": "get_db_data", "DBServerType": "sqlite" }, "EDConnectorSqlite" ], [ { "__pf": "get_db_data", "DBServerType": "mongodb", "__mongo": "MongoDB\\Client" }, "EDConnectorMongodb7" ], [ { "__pf": "get_db_data", "DBServerType": "mongodb", "__mongo": "MongoClient" }, "EDConnectorMongodb5" ], diff --git a/i18n/en.json b/i18n/en.json index 50f7e93..5314e30 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -23,12 +23,16 @@ "externaldata-db-incomplete-information": "Error: Incomplete information for this database ID $1 ($2).", "externaldata-db-could-not-get-url": "Could not get URL $1 after $2 {{PLURAL:$2|try|tries}}.", "externaldata-db-unknown-type": "Error: Unknown database type $1.", - "externaldata-db-could-not-connect": "Error: Could not connect to database.", + "externaldata-db-could-not-connect": "Error: Could not connect to database ($1).", "externaldata-db-unknown-database": "Error: Could not select the database specified with ID $1.", "externaldata-db-no-return-values": "Error: No return values specified.", - "externaldata-db-invalid-query": "The query $1 is invalid.", + "externaldata-db-invalid-query": "The query $1 is invalid ($2).", "externaldata-db-too-many-joins": "The number of JOIN conditions (now $1) must not exceed the number of tables ($2)", "externaldata-db-invalid-join": "Invalid JOIN condition $1: every part must contain an \"=\" sign.", + "externaldata-db-empty-rowset": "The query $1 returned an empty rowset.", + "externaldata-db-prepared-not-specified": "Please specify (from=), which prepared statement to use for the database ID $1.", + "externaldata-db-no-such-prepared": "The prepared statement $2 is not set up for the database ID $1.", + "externaldata-odbc-illegal": "The parameter $2 contains an illegal sequence $1. Please stop.", "externaldata-mongodb-unknown-collection": "Error: Unknown MongoDB collection $1.", "externaldata-mongodb-aggregation-failed": "MongoDB aggregation failed with error $1", "externaldata-format-unavailable": "The library $1, required to use the '$2' format without the '$3' option, is missing.", diff --git a/i18n/qqq.json b/i18n/qqq.json index 7ef0fc7..4a1137a 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -29,12 +29,16 @@ "externaldata-db-incomplete-information": "Used as error message if not all necessary database connection settings are provided in wiki settings.\n\nDatabase server, database type, database directory, database username and/or database password are not specified.\nParameters:\n* $1 is the database ID,\n* $2 is the name of the missing parameter.", "externaldata-db-could-not-get-url": "Parameters:\n* $1 - the URL,\n* $2 - number of HTTP tries", "externaldata-db-unknown-type": "Used as error message when creating a database object with using the specified information. $1 is the database type.", - "externaldata-db-could-not-connect": "Used as error message when connecting to the database system.", + "externaldata-db-could-not-connect": "Used as error message when connecting to the database system. $1 is the error message.", "externaldata-db-unknown-database": "An error message whe the database set in $edgDBName cannot be selected. $1 is the database id (db parameter).", "externaldata-db-no-return-values": "Used as error message, if the number of the specified columns is zero.\n\nIf successful, the system returns values in the specified columns.", - "externaldata-db-invalid-query": "Used as error message, if the query has failed. $1 is the query text.", + "externaldata-db-invalid-query": "Used as error message, if the query has failed. $1 is the query text, $2 is the error message from the server.", "externaldata-db-too-many-joins": "An error message when the number of JOIN conditions exceeds the number of tables. Parameters:\n* $1 is the number of JOIN conditions,\n* $2 is the number of tables.", "externaldata-db-invalid-join": "An error message for a wrong JOIN condition, any comma-separated part of which should contain a \"=\" sign. $1 is a wrong part.", + "externaldata-db-empty-rowset": "An error message used when an SQL query returns an empty rowset. $1 is an SQL query.", + "externaldata-db-prepared-not-specified": "The error message shown if several prepared statements are set up for the database, but no statement key is provided in the from parameter. $1 is the database ID.", + "externaldata-db-no-such-prepared": "This error message is shown when prepared statements array for the database does not include the provided key. Parameters:\n\n $1 is the database ID,\n\n$2 is the prepared statement key.", + "externaldata-odbc-illegal": "This error message shown if an attempted SQL injection is suspected. $1 is the offending substring, and $2 is the parameter in whach it was found.", "externaldata-mongodb-unknown-collection": "The error message if #get_db_data can't find the specified \"collection\" when connecting to MongoDB. $1 is the collection name", "externaldata-mongodb-aggregation-failed": "An error message produced when an aggregation query against a MongoDB collection fails. $1 is the error message.", "externaldata-format-unavailable": "An error message for a missing PHP library.\n\nParameters:\n* $1 - the name of the missing PHP library\n* $2 - the format that requires it\n* $3 - a parameter for a MediaWiki parser function", diff --git a/i18n/ru.json b/i18n/ru.json index 1e8bb45..f2da0da 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -28,12 +28,16 @@ "externaldata-db-incomplete-information": "В настройках вики неполная информация для БД ID $1. Отсутствует $2.", "externaldata-db-could-not-get-url": "Не удалось получить URL $1 после $2 {{PLURAL:$2|попытки|попыток}}.", "externaldata-db-unknown-type": "Ошибка: Неизвестный тип базы данных $1.", - "externaldata-db-could-not-connect": "ОШИБКА. Не удаётся подключиться к базе данных.", + "externaldata-db-could-not-connect": "ОШИБКА. Не удаётся подключиться к базе данных ($1).", "externaldata-db-unknown-database": "Ошибка: невозможно выбрать базу данных, указанную в настройках вики для $1.", "externaldata-db-no-return-values": "ОШИБКА. Не указаны возвращаемые значение.", - "externaldata-db-invalid-query": "Ошибочный запрос $1.", + "externaldata-db-invalid-query": "Ошибочный запрос $1 ($2).", "externaldata-db-too-many-joins": "Число условий соединения таблиц (сейчас $1) не должно превышать числа таблиц ($2)", "externaldata-db-invalid-join": "Неверное условие JOIN $1: каждая часть должна содержать знак «=».", + "externaldata-db-empty-rowset": "Запрос $1 не возвратил ни одной строки.", + "externaldata-db-prepared-not-specified": "Укажите (from=), который из заданных длф БД ID $1 подготавливаемых запросов использовать.", + "externaldata-db-no-such-prepared": "Подготавливаемый запрос $2 не задан для БД ID $1.", + "externaldata-odbc-illegal": "В параметре $2 обнаружена недопустимая последовательность $1. Перестаньте.", "externaldata-mongodb-unknown-collection": "Ошибка: Неизвестная коллекция MongoDB $1.", "externaldata-mongodb-aggregation-failed": "Ошибка при агрегации MongoDB: $1", "externaldata-format-unavailable": "Отсутствует библиотека $1, необходимая для использования формата $2 без опции '$3'.", diff --git a/includes/EDParserFunctions.php b/includes/EDParserFunctions.php index c0652fc..1dc1dda 100644 --- a/includes/EDParserFunctions.php +++ b/includes/EDParserFunctions.php @@ -1,4 +1,7 @@ getTitle()->getText() ); @@ -92,60 +95,60 @@ private static function fetch( Parser &$parser, $name, array $args ) { /** * Implementation of the {{#get_web_data:}} parser function. * - * @param Parser &$parser Parser object. + * @param Parser $parser Parser object. * @param string $params,... Parameters to parser function. * * @return string|null An error message or null on success. */ - public static function getWebData( Parser &$parser, ...$params ) { + public static function getWebData( Parser $parser, ...$params ) { return self::fetch( $parser, 'get_web_data', $params ); } /** * Implementation of the {{#get_file_data:}} parser function. * - * @param Parser &$parser Parser object. + * @param Parser $parser Parser object. * @param string $params,... Parameters to parser function. * * @return string|null An error message or null on success. */ - public static function getFileData( Parser &$parser, ...$params ) { + public static function getFileData( Parser $parser, ...$params ) { return self::fetch( $parser, 'get_file_data', $params ); } /** * Implementation of the {{#get_soap_data:}} parser function. * - * @param Parser &$parser Parser object. + * @param Parser $parser Parser object. * @param string $params,... Parameters to parser function. * * @return string|null An error message or null on success. */ - public static function getSoapData( Parser &$parser, ...$params ) { + public static function getSoapData( Parser $parser, ...$params ) { return self::fetch( $parser, 'get_soap_data', $params ); } /** * Implementation of the {{#get_ldap_data:}} parser function. * - * @param Parser &$parser Parser object. + * @param Parser $parser Parser object. * @param string $params,... Parameters to parser function. * * @return string|null An error message or null on success. */ - public static function getLdapData( Parser &$parser, ...$params ) { + public static function getLdapData( Parser $parser, ...$params ) { return self::fetch( $parser, 'get_ldap_data', $params ); } /** * Implementation of the {{#get_db_data:}} parser function. * - * @param Parser &$parser Parser object. + * @param Parser $parser Parser object. * @param string $params,... Parameters to parser function. * * @return string|null An error message or null on success. */ - public static function getDBData( Parser &$parser, ...$params ) { + public static function getDBData( Parser $parser, ...$params ) { return self::fetch( $parser, 'get_db_data', $params ); } @@ -166,11 +169,11 @@ private static function getIndexedValue( $var, $i ) { /** * Render the #external_value parser function. - * @param Parser &$parser + * @param Parser $parser * @param string $local_var * @return string|null */ - public static function doExternalValue( Parser &$parser, $local_var = '' ) { + public static function doExternalValue( Parser $parser, $local_var = '' ) { global $edgExternalValueVerbose; if ( !array_key_exists( $local_var, self::$values ) ) { return $edgExternalValueVerbose @@ -187,11 +190,11 @@ public static function doExternalValue( Parser &$parser, $local_var = '' ) { /** * Render the #for_external_table parser function. - * @param Parser &$parser + * @param Parser $parser * @param string $expression * @return string */ - public static function doForExternalTable( Parser &$parser, $expression = '' ) { + public static function doForExternalTable( Parser $parser, $expression = '' ) { // Get the variables used in this expression, get the number // of values for each, and loop through. $matches = []; @@ -201,7 +204,7 @@ public static function doForExternalTable( Parser &$parser, $expression = '' ) { $commands = [ 'urlencode', 'htmlencode' ]; // Used for a regexp check. - $commandsStr = implode( '|', $commands ); + $commands_str = implode( '|', $commands ); foreach ( $variables as $variable ) { // If it ends with one of the pre-defined "commands", @@ -222,10 +225,10 @@ public static function doForExternalTable( Parser &$parser, $expression = '' ) { // If it ends with one of the pre-defined "commands", // ignore the command to get the actual variable name. $matches = []; - preg_match( "/([^.]*)\.?($commandsStr)?$/", $variable, $matches ); + preg_match( "/([^.]*)\.?($commands_str)?$/", $variable, $matches ); $real_var = $matches[1]; - if ( count( $matches ) == 3 ) { + if ( count( $matches ) === 3 ) { $command = $matches[2]; } else { $command = null; @@ -258,10 +261,10 @@ public static function doForExternalTable( Parser &$parser, $expression = '' ) { * Render the #display_external_table parser function. * * @author Dan Bolser - * @param Parser &$parser + * @param Parser $parser * @return array|string */ - public static function doDisplayExternalTable( Parser &$parser ) { + public static function doDisplayExternalTable( Parser $parser ) { $params = func_get_args(); array_shift( $params ); // we already know the $parser ... $args = self::parseParams( $params ); // parse params into name-value pairs @@ -292,7 +295,7 @@ public static function doDisplayExternalTable( Parser &$parser ) { } $num_loops = 0; // May differ when multiple '#get_'s are used in one page - foreach ( $mappings as $template_param => $local_variable ) { + foreach ( $mappings as $local_variable ) { if ( !array_key_exists( $local_variable, self::$values ) ) { // Don't throw an error message - the source may just // not publish this variable. @@ -334,42 +337,42 @@ public static function doDisplayExternalTable( Parser &$parser ) { */ public static function callSubobject( Parser $parser, array $params ) { // This is a hack, since SMW's SMWSubobject::render() call is - // not meant to be called outside of SMW. However, this seemed + // not meant to be called outside SMW. However, this seemed // like the better solution than copying over all of that // method's code. Ideally, a true public function can be // added to SMW, that handles a subobject creation, that this // code can then call. - $subobjectArgs = [ &$parser ]; + $subobject_args = [ $parser ]; // Blank first argument, so that subobject ID will be // an automatically-generated random number. - $subobjectArgs[1] = ''; + $subobject_args[1] = ''; // "main" property, pointing back to the page. - $mainPageName = $parser->getTitle()->getText(); - $mainPageNamespace = $parser->getTitle()->getNsText(); - if ( $mainPageNamespace != '' ) { - $mainPageName = $mainPageNamespace . ':' . $mainPageName; + $main_page_name = $parser->getTitle()->getText(); + $main_page_namespace = $parser->getTitle()->getNsText(); + if ( $main_page_namespace !== '' ) { + $main_page_name = $main_page_namespace . ':' . $main_page_name; } - $subobjectArgs[2] = $params[0] . '=' . $mainPageName; + $subobject_args[2] = $params[0] . '=' . $main_page_name; foreach ( $params as $i => $value ) { if ( $i === 0 ) { continue; } - $subobjectArgs[] = $value; + $subobject_args[] = $value; } // SMW 1.9+ - $instance = \SMW\ParserFunctionFactory::newFromParser( $parser )->getSubobjectParser(); - return $instance->parse( new SMW\ParserParameterFormatter( $subobjectArgs ) ); + $instance = ParserFunctionFactory::newFromParser( $parser )->newSubobjectParserFunction( $parser ); + return $instance->parse( new SMW\ParserParameterFormatter( $subobject_args ) ); } /** * Render the #store_external_table parser function. - * @param Parser &$parser + * @param Parser $parser * @return string|null */ - public static function doStoreExternalTable( Parser &$parser ) { + public static function doStoreExternalTable( Parser $parser ) { // Quick exit if Semantic MediaWiki is not installed. if ( !class_exists( '\SMW\ParserFunctionFactory' ) ) { return self::formatErrorMessages( wfMessage( 'externaldata-smw-needed' )->inContentLanguage()->text() ); @@ -420,9 +423,9 @@ public static function doStoreExternalTable( Parser &$parser ) { /** * Render the #clear_external_data parser function. * - * @param Parser &$parser + * @param Parser $parser */ - public static function doClearExternalData( Parser &$parser ) { + public static function doClearExternalData( Parser $parser ) { self::$values = []; } diff --git a/includes/EDParsesParams.php b/includes/EDParsesParams.php index 3fb648e..48debb0 100644 --- a/includes/EDParsesParams.php +++ b/includes/EDParsesParams.php @@ -7,9 +7,14 @@ * @author Alexander Mashin * */ + +use Wikimedia\AtEase\AtEase; +use function MediaWiki\restoreWarnings; +use function MediaWiki\suppressWarnings; + trait EDParsesParams { - /** @var bool $preserve_external_variables_case Whether external variables' names are case-sensitive for this format. */ - protected static $preserve_external_variables_case = false; + /** @var bool $keepExternalVarsCase Whether external variables' names are case-sensitive for this format. */ + protected static $keepExternalVarsCase = false; /** * This method adds secret parameters to user-supplied ones, extracting them from @@ -44,14 +49,12 @@ protected static function supplementParams( array $params ) { * @param array $patterns An array of patterns that $params has to match. * * @return string|null ID of matched pattern. - * - * @throws EDParserException */ protected static function getMatch( array $args, array $patterns ) { // Bring keys to lowercase: $args = self::paramToArray( $args, true, false ); $supplemented_params = self::supplementParams( $args ); - foreach ( $patterns as list( $pattern, $match ) ) { + foreach ( $patterns as [ $pattern, $match ] ) { if ( self::paramsFit( $supplemented_params, $pattern ) ) { return $match; } @@ -87,10 +90,11 @@ private static function paramsFit( array $params, array $pattern ) { * @param string|array $arg Values to parse. * @param bool $lowercaseKeys bring keys to lower case. * @param bool $lowercaseValues bring values to lower case. + * @param bool $numeric Set anonymous parameter's name to a number rather than to itself. * * @return array Parsed parameter. */ - protected static function paramToArray( $arg, $lowercaseKeys = false, $lowercaseValues = false ) { + protected static function paramToArray( $arg, $lowercaseKeys = false, $lowercaseValues = false, $numeric = false ) { if ( !is_array( $arg ) ) { // Not an array. Splitting needed. $arg = preg_replace( "/\s\s+/", ' ', $arg ); // whitespace @@ -110,14 +114,17 @@ protected static function paramToArray( $arg, $lowercaseKeys = false, $lowercase // " - fix for color highlighting in vi :) $keyValuePairs = preg_split( $pattern, $arg ); $splitArray = []; + $counter = 0; foreach ( $keyValuePairs as $keyValuePair ) { - if ( $keyValuePair == '' ) { + if ( $keyValuePair === '' ) { // Ignore. } elseif ( strpos( $keyValuePair, '=' ) !== false ) { - list( $key, $value ) = explode( '=', $keyValuePair, 2 ); + [ $key, $value ] = explode( '=', $keyValuePair, 2 ); $splitArray[trim( $key )] = trim( $value ); + } elseif ( $numeric ) { + $splitArray[$counter++] = trim( $keyValuePair ); } else { - $splitArray[trim( $keyValuePair )] = null; + $splitArray[trim( $keyValuePair )] = trim( $keyValuePair ); } } } else { @@ -153,7 +160,7 @@ protected static function parseParams( $params ) { if ( count( $param_parts ) < 2 ) { $args[$param_parts[0]] = null; } else { - list( $name, $value ) = $param_parts; + [ $name, $value ] = $param_parts; $args[$name] = $value; } } @@ -166,6 +173,47 @@ protected static function parseParams( $params ) { * @return bool False, is external variables' names are brought to lowercase, true otherwise. */ public static function preservesCase() { - return static::$preserve_external_variables_case; // late binding. + return static::$keepExternalVarsCase; // late binding. + } + + /** + * Suppress warnings absolutely. + */ + protected static function suppressWarnings() { + if ( method_exists( AtEase::class, 'suppressWarnings' ) ) { + // MW >= 1.33 + AtEase::suppressWarnings(); + } else { + suppressWarnings(); + } + } + + /** + * Restore warnings. + */ + protected static function restoreWarnings() { + if ( method_exists( AtEase::class, 'restoreWarnings' ) ) { + // MW >= 1.33 + AtEase::restoreWarnings(); + } else { + restoreWarnings(); + } + } + + /** + * Instead of producing a warning, throw an exception. + * @throws Exception + */ + protected static function throwWarnings() { + set_error_handler( static function ( $errno, $errstr ) { + throw new Exception( $errstr ); + } ); + } + + /** + * Resume warnings. + */ + protected static function stopThrowingWarnings() { + restore_error_handler(); } } diff --git a/includes/connectors/EDConnectorBase.php b/includes/connectors/EDConnectorBase.php index 500b48a..6117d1e 100644 --- a/includes/connectors/EDConnectorBase.php +++ b/includes/connectors/EDConnectorBase.php @@ -9,15 +9,18 @@ abstract class EDConnectorBase { use EDParsesParams; // Needs paramToArray(). /** @var array|null An array of errors. */ - private $errors = null; + private $errors; /** @var bool Whether error messages are to be suppressed in wikitext. */ - private $suppress_error = false; + private $suppressError = false; /** @var bool True, if the connector needs one of EDParser* objects. */ - protected static $needs_parser = false; + protected static $needsParser = false; /** @var EDParserBase A Parser. */ - private $parser = null; + private $parser; + + /** @var string $encoding */ + protected $encoding; /** @var array An associative array mapping internal variables to external. */ protected $mappings = []; @@ -38,7 +41,7 @@ protected function __construct( array &$args ) { $args = self::supplementParams( $args ); // Text parser, if needed. - if ( static::$needs_parser ) { // late binding. + if ( static::$needsParser ) { // late binding. // Encoding override supplied by wiki user may also be needed. $this->encoding = isset( $args['encoding'] ) && $args['encoding'] ? $args['encoding'] : null; try { @@ -51,7 +54,7 @@ protected function __construct( array &$args ) { // Data mappings. May be handled by the parser or by self. if ( array_key_exists( 'data', $args ) ) { // Whether to bring the external variables to lower case. It depends on the parser, if any. - $lower = !( $this->parser ? $this->parser : $this )->preservesCase(); // late binding in both. + $lower = !( $this->parser ?: $this )->preservesCase(); // late binding in both. $this->mappings = self::paramToArray( $args['data'], false, $lower ); } else { $this->error( 'externaldata-no-param-specified', 'data' ); @@ -64,7 +67,7 @@ protected function __construct( array &$args ) { // Whether to suppress error messages. if ( array_key_exists( 'suppress error', $args ) ) { - $this->suppress_error = true; + $this->suppressError = true; } } @@ -94,7 +97,7 @@ public function result() { * * @return EDConnectorBase An EDConnector* object. */ - public static function getConnector( $name, array $args ) { + public static function getConnector( $name, array $args ): EDConnectorBase { $args['__pf'] = $name; $args['__mongo'] = class_exists( 'MongoDB\Client' ) ? 'MongoDB\Client' : ( class_exists( 'MongoClient' ) ? 'MongoClient' : null ); @@ -143,10 +146,8 @@ private function filteredAndMappedValues() { if ( is_array( $external_values[$filter_var] ) ) { $column_values = $external_values[$filter_var]; foreach ( $column_values as $i => $single_value ) { - // if a value doesn't match - // the filter value, remove - // the value from this row for - // all columns + // if a value doesn't match the filter value, remove + // the value from this row for all columns if ( trim( $single_value ) !== trim( $filter_value ) ) { foreach ( $external_values as $external_var => $external_value ) { unset( $external_values[$external_var][$i] ); @@ -154,10 +155,8 @@ private function filteredAndMappedValues() { } } } else { - // if we have only one row of values, - // and the filter doesn't match, just - // keep the results array blank and - // return + // if we have only one row of values, and the filter doesn't match, + // just keep the results array blank and return if ( $external_values[$filter_var] != $filter_value ) { return []; } @@ -215,6 +214,6 @@ public function errors() { * @return bool The message. */ public function suppressError() { - return $this->suppress_error; + return $this->suppressError; } } diff --git a/includes/connectors/EDConnectorComposed.php b/includes/connectors/EDConnectorComposed.php new file mode 100644 index 0000000..927f154 --- /dev/null +++ b/includes/connectors/EDConnectorComposed.php @@ -0,0 +1,48 @@ +errors. + * + * @param array &$args An array of arguments for parser/Lua function. + */ + protected function __construct( array &$args ) { + parent::__construct( $args ); + // Query parts. + if ( isset( $args['from'] ) ) { + $this->from = $args['from']; + } else { + $this->error( 'externaldata-no-param-specified', 'from' ); + } + // @todo Allow Lua tables rather than comma-separated strings for the below parametres. + $this->conditions = array_key_exists( 'where', $args ) ? $args['where'] : null; + $this->sqlOptions = [ + 'ORDER BY' => array_key_exists( 'order by', $args ) ? $args['order by'] : null, + 'GROUP BY' => array_key_exists( 'group by', $args ) ? $args['group by'] : null, + 'HAVING' => array_key_exists( 'having', $args ) ? $args['having'] : null + ]; + if ( isset( $args['limit'] ) ) { + if ( is_numeric( $args['limit'] ) ) { + $this->sqlOptions['LIMIT'] = (int)$args['limit']; + } else { + $this->error( 'externaldata-param-type-error', 'limit', 'integer' ); + } + } + } +} diff --git a/includes/connectors/EDConnectorDb.php b/includes/connectors/EDConnectorDb.php index 105420b..4b971ff 100644 --- a/includes/connectors/EDConnectorDb.php +++ b/includes/connectors/EDConnectorDb.php @@ -8,22 +8,16 @@ */ abstract class EDConnectorDb extends EDConnectorBase { /** @var string Database ID. */ - protected $db_id; // Database ID. + protected $dbId; // Database ID. /** @var string Database type. */ protected $type; /** @var array Connection settings. */ - protected $connection = []; + protected $credentials = []; // SQL query components. - /** @var string FROM clause as a string. */ - protected $from; /** @var array Columns to query. */ protected $columns; - /** @var string Select conditions. */ - protected $conditions; - /** @var array LIMIT, ORDER BY and GROUP BY clauses. */ - protected $sql_options; /** * Constructor. Analyse parameters and wiki settings; set $this->errors. @@ -35,57 +29,121 @@ protected function __construct( array &$args ) { // Specific parameters. if ( isset( $args['db'] ) ) { - $this->db_id = $args['db']; + $this->dbId = $args['db']; } elseif ( isset( $args['server'] ) ) { // For backwards-compatibility - 'db' parameter was // added in External Data version 1.3. - $this->db_id = $args['server']; + $this->dbId = $args['server']; } - if ( !$this->db_id ) { + if ( !$this->dbId ) { $this->error( 'externaldata-no-param-specified', 'db' ); } if ( isset( $args['DBServerType'] ) ) { $this->type = $args['DBServerType']; } else { - $this->error( 'externaldata-db-incomplete-information', $this->db_id, 'edgDBServerType' ); + $this->error( 'externaldata-db-incomplete-information', $this->dbId, 'edgDBServerType' ); } // Database credentials. - $this->setConnection( $args ); // late binding. + $this->setCredentials( $args ); // late binding. // Query parts. - if ( isset( $args['from'] ) ) { - $this->from = $args['from']; - } else { - $this->error( 'externaldata-no-param-specified', 'from' ); - } $this->columns = array_values( $this->mappings ); - $this->conditions = ( array_key_exists( 'where', $args ) ) ? $args['where'] : null; - $this->sql_options = [ - 'ORDER BY' => ( array_key_exists( 'order by', $args ) ) ? $args['order by'] : null, - 'GROUP BY' => ( array_key_exists( 'group by', $args ) ) ? $args['group by'] : null, - 'HAVING' => ( array_key_exists( 'having', $args ) ) ? $args['having'] : null - ]; - if ( isset( $args['limit'] ) ) { - if ( is_numeric( $args['limit'] ) ) { - $this->sql_options['LIMIT'] = intval( $args['limit'] ); - } else { - $this->error( 'externaldata-param-type-error', 'limit', 'integer' ); - } - } } /** - * Set connection settings for database from $this->db_id. - * Should be overloaded, with parent::setConnection(). + * Set credentials settings for database from $this->dbId. + * Should be overloaded, with a call to parent::setCredentials(). * * @param array $params Supplemented parameters. */ - protected function setConnection( array $params ) { - $this->connection['user'] = isset( $params[ 'DBUser' ] ) ? $params[ 'DBUser' ] : null; - $this->connection['password'] = isset( $params[ 'DBPass' ] ) ? $params[ 'DBPass' ] : null; + protected function setCredentials( array $params ) { + $this->credentials['user'] = isset( $params['DBUser' ] ) ? $params['DBUser' ] : null; + $this->credentials['password'] = isset( $params['DBPass' ] ) ? $params['DBPass' ] : null; if ( isset( $params[ 'DBName' ] ) ) { - $this->connection['dbname'] = $params['DBName']; + $this->credentials['dbname'] = $params['DBName']; } else { - $this->error( 'externaldata-db-incomplete-information', $this->db_id, 'edgDBName' ); + $this->error( 'externaldata-db-incomplete-information', $this->dbId, 'edgDBName' ); + } + } + + /** + * Actually connect to the external data source. + * It is presumed that there are no errors in parameters and wiki settings. + * Set $this->values and $this->errors. + * + * @return bool True on success, false if error were encountered. + */ + public function run() { + if ( !$this->connect() /* late binding. */ ) { + return false; + } + $rows = $this->fetch(); // late binding. + if ( !$rows ) { + return false; } + $this->values = $this->processRows( $rows ); // late binding. + $this->disconnect(); // late binding. + return true; } + + /** + * Establish connection the database server. + */ + abstract protected function connect(); + + /** + * Get query text. + * @return string + */ + abstract protected function getQuery(); + + /** + * Get query result as a two-dimensional array. + * @return mixed + */ + abstract protected function fetch(); + + /** + * Postprocess query result. + * @param mixed $rows A two-dimensional array or result wrapper containing query results. + * @param array $aliases An optional associative array of column aliases. + * @return array A two-dimensional array containing post-processed query results + */ + protected function processRows( $rows, array $aliases = [] ): array { + $result = []; + foreach ( $rows as $row ) { + foreach ( $this->columns as $column ) { + $alias = isset( $aliases[$column] ) ? $aliases[$column] : $column; + if ( !isset( $result[$column] ) ) { + $result[$column] = []; + } + // Can be both array and object. + $result[$column][] = self::processField( is_array( $row ) ? $row[$alias] : $row->$alias ); + } + } + return $result; + } + + /** + * Process field value. + * + * @param string|DateTime $value + * @return string + */ + protected static function processField( $value ) { + // This can happen with MSSQL. + if ( $value instanceof DateTime ) { + $value = $value->format( 'Y-m-d H:i:s' ); + } + // Convert the encoding to UTF-8 + // if necessary - based on code at + // http://www.php.net/manual/en/function.mb-detect-encoding.php#102510 + return mb_detect_encoding( $value, 'UTF-8', true ) === 'UTF-8' + ? $value + : utf8_encode( $value ); + } + + /** + * Disconnect from DB server. + */ + abstract protected function disconnect(); } diff --git a/includes/connectors/EDConnectorDirectory.php b/includes/connectors/EDConnectorDirectory.php index 5b62ace..064c3a9 100644 --- a/includes/connectors/EDConnectorDirectory.php +++ b/includes/connectors/EDConnectorDirectory.php @@ -53,8 +53,8 @@ public function __construct( array $args ) { * @return bool True on success, false if error were encountered. */ public function run() { - $this->real_path = realpath( $this->real_directory . $this->file_name ); - if ( $this->real_path === false || strpos( $this->real_path, $this->real_directory ) !== 0 ) { + $this->realPath = realpath( $this->real_directory . $this->file_name ); + if ( $this->realPath === false || strpos( $this->realPath, $this->real_directory ) !== 0 ) { // No file found in directory. $this->error( 'externaldata-no-file-in-directory', $this->directory, $this->file_name ); return false; diff --git a/includes/connectors/EDConnectorFile.php b/includes/connectors/EDConnectorFile.php index 7c4767c..007f684 100644 --- a/includes/connectors/EDConnectorFile.php +++ b/includes/connectors/EDConnectorFile.php @@ -23,7 +23,7 @@ public function __construct( array $args ) { if ( isset( $args['file'] ) ) { $this->file = $args['file']; if ( isset( $args['FilePath'] ) ) { - $this->real_path = $args['FilePath']; + $this->realPath = $args['FilePath']; } else { // File not defined. $this->error( 'externaldata-undefined-file', $this->file ); diff --git a/includes/connectors/EDConnectorGet.php b/includes/connectors/EDConnectorGet.php index bd4ff0d..2d8637d 100644 --- a/includes/connectors/EDConnectorGet.php +++ b/includes/connectors/EDConnectorGet.php @@ -12,20 +12,20 @@ abstract class EDConnectorGet extends EDConnectorHttp { private static $tries = 3; // Cache variables. - /** @var bool $cache_set_up Is the cache set up? */ - private static $cache_set_up; - /** @var string|null $cache_table Cache table name. */ - private static $cache_table; + /** @var bool $cacheIsUp Is the cache set up? */ + private static $cacheIsUp; + /** @var string|null $cacheTable Cache table name. */ + private static $cacheTable; /** @var int Number of seconds before cache expires. */ - private $cache_expires; + private $cacheExpires; /** @var bool Whether the data can be fetched from stale cache. */ - private $allow_stale_cache; + private $allowStaleCache; /** @var int When the cache was cached. */ - private $cached_time; + private $cachedTime; /** @var bool Is the cache fresh. */ - private $cache_fresh; + private $cacheFresh; /** @var int Timestamp of when the result was fetched. */ private $time; @@ -39,18 +39,18 @@ protected function __construct( array $args ) { // Cache. global $edgCacheTable; - self::$cache_set_up = (bool)$edgCacheTable; - self::$cache_table = $edgCacheTable; + self::$cacheIsUp = (bool)$edgCacheTable; + self::$cacheTable = $edgCacheTable; // Cache expiration. global $edgCacheExpireTime; - $this->cache_expires = array_key_exists( 'cache seconds', $args ) + $this->cacheExpires = array_key_exists( 'cache seconds', $args ) ? max( $args['cache seconds'], $edgCacheExpireTime ) : $edgCacheExpireTime; // Allow to use stale cache. global $edgAlwaysAllowStaleCache; - $this->allow_stale_cache = array_key_exists( 'use stale cache', $args ) || $edgAlwaysAllowStaleCache; + $this->allowStaleCache = array_key_exists( 'use stale cache', $args ) || $edgAlwaysAllowStaleCache; } /** @@ -62,11 +62,11 @@ protected function __construct( array $args ) { */ public function run() { $this->cache_time = null; - $this->cache_fresh = false; + $this->cacheFresh = false; $cached = false; // Is the cache set up, present and fresh? - if ( self::$cache_set_up && ( $this->cache_expires !== 0 || $this->allow_stale_cache ) ) { + if ( self::$cacheIsUp && ( $this->cacheExpires !== 0 || $this->allowStaleCache ) ) { // Cache set up and can be used. $cached = $this->cached(); } @@ -74,16 +74,16 @@ public function run() { // If there is no fresh cache, try to get from the web. $cache_present = (bool)$cached; $tries = 0; - if ( !self::$cache_set_up || !$cache_present || !$this->cache_fresh || $this->cache_expires === 0 ) { + if ( !self::$cacheIsUp || !$cache_present || !$this->cacheFresh || $this->cacheExpires === 0 ) { // Allow extensions or LocalSettings.php to alter HTTP options. - Hooks::run( 'ExternalDataBeforeWebCall', [ 'get', $this->real_url, $this->options ] ); + Hooks::run( 'ExternalDataBeforeWebCall', [ 'get', $this->realUrl, $this->options ] ); do { // Actually send a request. $contents = $this->fetcher(); // Late binding; fetcher() is pure virtual. Also sets $this->headers. } while ( !$contents && ++$tries <= self::$tries ); if ( $contents ) { // Fetched successfully. - $this->cache_fresh = true; + $this->cacheFresh = true; // Encoding needs to be detected from HTTP headers this early and not later, // during text parsing, so that the converted text may be cached. // Try HTTP headers. @@ -93,31 +93,31 @@ public function run() { $contents = EDEncodingConverter::toUTF8( $contents, $this->encoding ); $this->time = time(); // Update cache, if possible and required. - if ( self::$cache_set_up && $this->cache_expires !== 0 ) { + if ( self::$cacheIsUp && $this->cacheExpires !== 0 ) { $this->cache( $contents, $cache_present ); } } else { // Not fetched. - if ( $cache_present && $this->allow_stale_cache ) { + if ( $cache_present && $this->allowStaleCache ) { // But can serve stale cache, if any and allowed. $contents = $cached; - $this->cache_fresh = false; - $this->time = $this->cached_time; + $this->cacheFresh = false; + $this->time = $this->cachedTime; } else { // Nothing to serve. - $this->error( 'externaldata-db-could-not-get-url', $this->original_url, self::$tries ); + $this->error( 'externaldata-db-could-not-get-url', $this->originalUrl, self::$tries ); return false; } } } else { // We have a fresh cache; so serve it. $contents = $cached; - $this->time = $this->cached_time; + $this->time = $this->cachedTime; } $this->values = $this->parse( $contents, [ '__time' => [ $this->time ], - '__stale' => [ !$this->cache_fresh ], + '__stale' => [ !$this->cacheFresh ], '__tries' => [ $tries ] ] ); return !$this->errors(); @@ -133,17 +133,17 @@ abstract protected function fetcher(); /** * Get cached value, if any. It is assumed that the cache is set up. - * Sets $this->cached_time and $this->cache_fresh. + * Sets $this->cachedTime and $this->cacheFresh. * * @return string|null The cached value; null if none. */ private function cached() { // Check the cache (only the first 254 chars of the url). $dbr = wfGetDB( DB_REPLICA ); - $row = $dbr->selectRow( self::$cache_table, '*', [ 'url' => substr( $this->real_url, 0, 254 ) ], __METHOD__ ); + $row = $dbr->selectRow( self::$cacheTable, '*', [ 'url' => substr( $this->realUrl, 0, 254 ) ], __METHOD__ ); if ( $row ) { - $this->cached_time = $row->req_time; - $this->cache_fresh = $this->cache_expires !== 0 && time() - $this->cached_time <= $this->cache_expires; + $this->cachedTime = $row->req_time; + $this->cacheFresh = $this->cacheExpires !== 0 && time() - $this->cachedTime <= $this->cacheExpires; return $row->result; } else { return null; @@ -160,12 +160,12 @@ private function cache( $contents, $old_cache ) { $dbw = wfGetDB( DB_MASTER ); // Delete the old entry, if one exists. if ( $old_cache ) { - $dbw->delete( self::$cache_table, [ 'url' => substr( $this->real_url, 0, 254 ) ] ); + $dbw->delete( self::$cacheTable, [ 'url' => substr( $this->realUrl, 0, 254 ) ] ); } // Insert contents into the cache table. $dbw->insert( - self::$cache_table, - [ 'url' => substr( $this->real_url, 0, 254 ), 'result' => $contents, 'req_time' => time() ] + self::$cacheTable, + [ 'url' => substr( $this->realUrl, 0, 254 ), 'result' => $contents, 'req_time' => time() ] ); } } diff --git a/includes/connectors/EDConnectorHttp.php b/includes/connectors/EDConnectorHttp.php index 53ee0b9..38edc2a 100644 --- a/includes/connectors/EDConnectorHttp.php +++ b/includes/connectors/EDConnectorHttp.php @@ -7,18 +7,16 @@ * */ abstract class EDConnectorHttp extends EDConnectorBase { - /** @var bool $needs_parser True, if the connector needs one of EDParser* objects. */ - protected static $needs_parser = true; + /** @var bool $needsParser True, if the connector needs one of EDParser* objects. */ + protected static $needsParser = true; /** @var string URL to fetch data from as provided by user. */ - protected $original_url; + protected $originalUrl; /** @var string URL to fetch data from after substitutions. */ - protected $real_url; + protected $realUrl; /** @var array HTTP options. */ protected $options; /** @var array HTTP headers. */ protected $headers; - /** @var int When the result was actually retched. */ - private $time; /** * Constructor. Analyse parameters and wiki settings; set $this->errors. @@ -35,7 +33,7 @@ protected function __construct( array $args ) { if ( isset( $args['url'] ) ) { $url = $args['url']; $url = str_replace( ' ', '%20', $url ); // -- do some minor URL-encoding. - $this->original_url = $url; + $this->originalUrl = $url; // If the URL isn't allowed (based on a whitelist), exit. if ( self::isURLAllowed( $url ) ) { // Do any special variable replacements in the URLs, for secret API keys and the like. @@ -43,7 +41,7 @@ protected function __construct( array $args ) { foreach ( $edgStringReplacements as $key => $value ) { $url = str_replace( $key, $value, $url ); } - $this->real_url = $url; + $this->realUrl = $url; } else { // URL not allowed. $this->error( 'externaldata-url-not-allowed', $url ); @@ -81,10 +79,10 @@ private static function isURLAllowed( $url ) { // this code is based on Parser::maybeMakeExternalImage(). global $edgAllowExternalDataFrom; $data_from = $edgAllowExternalDataFrom; - $text = false; if ( empty( $data_from ) ) { return true; - } elseif ( is_array( $data_from ) ) { + } + if ( is_array( $data_from ) ) { foreach ( $data_from as $match ) { if ( strpos( $url, $match ) === 0 ) { return true; @@ -92,11 +90,7 @@ private static function isURLAllowed( $url ) { } return false; } else { - if ( strpos( $url, $data_from ) === 0 ) { - return true; - } else { - return false; - } + return strpos( $url, $data_from ) === 0; } } } diff --git a/includes/connectors/EDConnectorLdap.php b/includes/connectors/EDConnectorLdap.php index 4367c41..c34be48 100644 --- a/includes/connectors/EDConnectorLdap.php +++ b/includes/connectors/EDConnectorLdap.php @@ -14,7 +14,7 @@ class EDConnectorLdap extends EDConnectorBase { /** @var string LDAP domain (key to $edgLDAPServer, etc.). */ private $domain; /** @var string Base DN for the directory. */ - private $base_dn; + private $baseDn; /** @var string Real LDAP server. */ private $server; /** @var string Real LDAP user. */ @@ -60,7 +60,7 @@ public function __construct( array $args ) { $this->user = isset( $args['LDAPUser'] ) ? $args['LDAPUser'] : null; $this->password = isset( $args['LDAPPass'] ) ? $args['LDAPPass'] : null; if ( isset( $args['LDAPBaseDN'] ) ) { - $this->base_dn = $args['LDAPBaseDN']; + $this->baseDn = $args['LDAPBaseDN']; } else { $this->error( 'externaldata-ldap-domain-not-defined', $this->domain ); } @@ -110,7 +110,7 @@ public function run() { /** * Connect to LDAP server using server, username and password set by the constructor. - * Set $this->connection. + * Set $this->credentials. */ private function connectLDAP() { $this->connection = ldap_connect( $this->server ); @@ -120,10 +120,8 @@ private function connectLDAP() { ldap_set_option( $this->connection, LDAP_OPT_REFERRALS, 0 ); $bound = false; $exception = false; - // Suppress warnings. - set_error_handler( static function () { - throw new Exception(); - } ); + // Throw exceptions instead of warnings. + self::throwWarnings(); try { $bound = $this->user ? ldap_bind( $this->connection, $this->user, $this->password ) @@ -134,7 +132,7 @@ private function connectLDAP() { $exception = true; } // Restore warnings. - restore_error_handler(); + self::stopThrowingWarnings(); if ( !$bound && !$exception /* Do not repeat twice. */ ) { $this->error( 'externaldata-ldap-unable-to-bind', $this->domain ); // not $this->server! $this->connection = null; @@ -150,7 +148,7 @@ private function connectLDAP() { * @return array Search results. */ private function searchLDAP() { - $sr = ldap_search( $this->connection, $this->base_dn, $this->filter, array_values( $this->mappings ) ); + $sr = ldap_search( $this->connection, $this->baseDn, $this->filter, array_values( $this->mappings ) ); $results = ldap_get_entries( $this->connection, $sr ); return $results; } diff --git a/includes/connectors/EDConnectorMongodb.php b/includes/connectors/EDConnectorMongodb.php index ec98df0..e724ae1 100644 --- a/includes/connectors/EDConnectorMongodb.php +++ b/includes/connectors/EDConnectorMongodb.php @@ -6,20 +6,20 @@ * @author Yaron Koren * @author Alexander Mashin */ -abstract class EDConnectorMongodb extends EDConnectorDb { - /** @var bool $preserve_external_variables_case Whether external variables' names are case-sensitive for this format. */ - protected static $preserve_external_variables_case = true; +abstract class EDConnectorMongodb extends EDConnectorComposed { + /** @var bool $keepExternalVarsCase Whether external variables' names are case-sensitive for this format. */ + protected static $keepExternalVarsCase = true; /** @var string MondoDB connection string. */ - protected $connect_string; + protected $connectString; /** @var array MongoDB aggregate. */ - private $aggregate = []; + protected $aggregate = []; /** @var array MongoDB find query. */ private $find = []; /** @var array MongoDB sort. */ private $sort = []; /** @var string|null A key for Memcached/APC. */ - private $cache_key = null; + private $cacheKey = null; /** * Constructor. @@ -60,24 +60,24 @@ public function __construct( array $args ) { foreach ( $whereElements as $key => $whereElement ) { $whereElement = str_ireplace( ' like ', ' LIKE ', $whereElement ); if ( strpos( $whereElement, '>=' ) ) { - list( $fieldName, $value ) = explode( '>=', $whereElement ); + [ $fieldName, $value ] = explode( '>=', $whereElement ); $this->find[trim( $fieldName )] = [ '$gte' => trim( $value ) ]; } elseif ( strpos( $whereElement, '>' ) ) { - list( $fieldName, $value ) = explode( '>', $whereElement ); + [ $fieldName, $value ] = explode( '>', $whereElement ); $this->find[trim( $fieldName )] = [ '$gt' => trim( $value ) ]; } elseif ( strpos( $whereElement, '<=' ) ) { - list( $fieldName, $value ) = explode( '<=', $whereElement ); + [ $fieldName, $value ] = explode( '<=', $whereElement ); $this->find[trim( $fieldName )] = [ '$lte' => trim( $value ) ]; } elseif ( strpos( $whereElement, '<' ) ) { - list( $fieldName, $value ) = explode( '<', $whereElement ); + [ $fieldName, $value ] = explode( '<', $whereElement ); $this->find[trim( $fieldName )] = [ '$lt' => trim( $value ) ]; } elseif ( strpos( $whereElement, ' LIKE ' ) ) { - list( $fieldName, $value ) = explode( ' LIKE ', $whereElement ); + [ $fieldName, $value ] = explode( ' LIKE ', $whereElement ); $value = trim( $value ); - $regex_class = static::$regex_class; // late binding. + $regex_class = static::$regexClass; // late binding. $this->find[trim( $fieldName )] = new $regex_class( "/$value/i" ); } elseif ( strpos( $whereElement, '=' ) ) { - list( $fieldName, $value ) = explode( '=', $whereElement ); + [ $fieldName, $value ] = explode( '=', $whereElement ); $this->find[trim( $fieldName )] = trim( $value ); } elseif ( is_string( $key ) ) { $this->find[$key] = $whereElement; @@ -86,11 +86,11 @@ public function __construct( array $args ) { } // Do the same for the "order=" parameter as the "where=" parameter - if ( $this->sql_options['ORDER BY'] ) { - $sortElements = explode( ',', $this->sql_options['ORDER BY'] ); + if ( $this->sqlOptions['ORDER BY'] ) { + $sortElements = explode( ',', $this->sqlOptions['ORDER BY'] ); foreach ( $sortElements as $sortElement ) { if ( strpos( $sortElement, ' ' ) !== false ) { - list( $fieldName, $order ) = explode( ' ', $sortElement, 2 ); + [ $fieldName, $order ] = explode( ' ', $sortElement, 2 ); $orderingNum = 1; if ( $order && strtolower( trim( $order ) ) === 'desc' ) { $orderingNum = -1; @@ -103,11 +103,11 @@ public function __construct( array $args ) { } if ( $this->aggregate && count( $this->aggregate ) > 0 ) { - if ( isset( $this->sql_options['ORDER BY'] ) ) { + if ( isset( $this->sqlOptions['ORDER BY'] ) ) { $this->aggregate[] = [ '$sort' => $this->sort ]; } - if ( isset( $this->sql_options['LIMIT'] ) ) { - $this->aggregate[] = [ '$limit' => intval( $this->sql_options['LIMIT'] ) ]; + if ( isset( $this->sqlOptions['LIMIT'] ) ) { + $this->aggregate[] = [ '$limit' => intval( $this->sqlOptions['LIMIT'] ) ]; } } @@ -116,34 +116,40 @@ public function __construct( array $args ) { if ( ( $wgMainCacheType === CACHE_MEMCACHED || $wgMainCacheType === CACHE_ACCEL ) && $edgMemCachedMongoDBSeconds > 0 ) { - $this->cache_key = ObjectCache::getLocalClusterInstance()->makeKey( 'mongodb', $this->from, md5( - json_encode( $this->aggregate ) . - json_encode( $this->find ) . - json_encode( $this->sort ) . - json_encode( $this->columns ) . - $this->conditions . - json_encode( $this->sql_options ) . - $this->connection['db_name'] . - $this->connection['host'] + $this->cacheKey = ObjectCache::getLocalClusterInstance()->makeKey( 'mongodb', $this->from, md5( + $this->getQuery() . + $this->credentials['db_name'] . + $this->credentials['host'] ) ); } } /** - * Create a MongoDB connection. + * Form credentials for database from $this->dbId. * - * @return MongoClient|MongoDB\Client|null + * @param array $params Supplemented parameters. */ - abstract protected function connect(); + protected function setCredentials( array $params ) { + parent::setCredentials( $params ); + $this->credentials['host'] = isset( $params['DBServer'] ) ? $params['DBServer'] : 'localhost:27017'; + + // MongoDB login is done using a single string. + // When specifying extra connect string options (e.g. replicasets,timeout, etc.), + // use $edgDBServer[$this->dbId] to pass these values + // see http://docs.mongodb.org/manual/reference/connection-string + $this->connectString = "mongodb://"; + if ( $this->credentials['user'] ) { + $this->connectString .= $this->credentials['user'] . ':' . $this->credentials['password'] . '@'; + } + $this->connectString .= $this->credentials['host']; + } /** * Get the MongoDB collection $name provided the connection is established. * - * @param string $collection The collection name. - * * @return MongoCollection|MongoDB\Collection|null MongoDB collection. */ - abstract protected function getCollection( $collection ); + abstract protected function fetch(); /** * Run a query against MongoDB $collection. @@ -183,7 +189,7 @@ public function run() { return true; } - $collection = $this->getCollection( $this->from ); // late binding. + $collection = $this->fetch(); // late binding. if ( !$collection ) { return false; } @@ -197,7 +203,7 @@ public function run() { $this->find, $this->columns, $this->sort, - $this->sql_options['LIMIT'] + $this->sqlOptions['LIMIT'] ); // late binding. } @@ -207,7 +213,7 @@ public function run() { } // Arrange values returned by MongoDB in a column-based array. - $this->values = $this->arrange( $results ); + $this->values = $this->processRows( $results ); // Cache, if so configured. if ( count( $this->values ) > 0 ) { @@ -220,13 +226,14 @@ public function run() { /** * Arrange values returned by MongoDB in a column-based array. * - * @param array $results Results from MongoDB. + * @param array $rows Results from MongoDB. + * @param array $aliases Stub. * * @return array $values Column-based array of values. */ - private function arrange( array $results ) { + protected function processRows( $rows, array $aliases = [] ): array { $values = []; - foreach ( $results as $doc ) { + foreach ( $rows as $doc ) { foreach ( $this->columns as $column ) { if ( strstr( $column, "." ) ) { // If the exact path of the value was @@ -289,23 +296,17 @@ private static function getValueFromJSONArray( array $origArray, $path, $default } /** - * Form connection settings for database from $this->db_id. + * Pseudo-query in text form for diagnostics. Also used to from cache key. * - * @param array $params Supplemented parameters. + * @return string */ - protected function setConnection( array $params ) { - parent::setConnection( $params ); - $this->connection['host'] = isset( $params['DBServer'] ) ? $params['DBServer'] : 'localhost:27017'; - - // MongoDB login is done using a single string. - // When specifying extra connect string options (e.g. replicasets,timeout, etc.), - // use $edgDBServer[$this->db_id] to pass these values - // see http://docs.mongodb.org/manual/reference/connection-string - $this->connect_string = "mongodb://"; - if ( $this->connection['user'] ) { - $this->connect_string .= $this->connection['user'] . ':' . $this->connection['password'] . '@'; - } - $this->connect_string .= $this->connection['host']; + protected function getQuery() { + return json_encode( $this->aggregate ) . + json_encode( $this->find ) . + json_encode( $this->sort ) . + json_encode( $this->columns ) . + $this->conditions . + json_encode( $this->sqlOptions ); } /** @@ -314,9 +315,9 @@ protected function setConnection( array $params ) { * @return array|null Stored values or null, if no storage is configured. */ private function cached() { - if ( $this->cache_key ) { + if ( $this->cacheKey ) { // Check if cache entry exists. - return ObjectCache::getLocalClusterInstance()->get( $this->cache_key ); + return ObjectCache::getLocalClusterInstance()->get( $this->cacheKey ); } else { return null; } @@ -328,9 +329,9 @@ private function cached() { * @param array $values Values to store. */ private function cache( array $values ) { - if ( $this->cache_key ) { + if ( $this->cacheKey ) { global $edgMemCachedMongoDBSeconds; - ObjectCache::getLocalClusterInstance()->set( $this->cache_key, $values, $edgMemCachedMongoDBSeconds ); + ObjectCache::getLocalClusterInstance()->set( $this->cacheKey, $values, $edgMemCachedMongoDBSeconds ); } } } diff --git a/includes/connectors/EDConnectorMongodb5.php b/includes/connectors/EDConnectorMongodb5.php index 6c54925..13bf032 100644 --- a/includes/connectors/EDConnectorMongodb5.php +++ b/includes/connectors/EDConnectorMongodb5.php @@ -7,8 +7,10 @@ * @author Alexander Mashin */ class EDConnectorMongodb5 extends EDConnectorMongodb { - /** @var string $regex_class Class that stores MongoDB regular expressions. */ - protected static $regex_class = 'MongoRegex'; + /** @var ?MongoClient $mongoClient MongoDB client. */ + private $mongoClient; + /** @var string $regexClass Class that stores MongoDB regular expressions. */ + protected static $regexClass = 'MongoRegex'; /** * Create a MongoDB connection. @@ -20,9 +22,9 @@ protected function connect() { // the MongoDB connect string, which may have sensitive // information. try { - return new MongoClient( $this->connect_string ); + return new MongoClient( $this->connectString ); } catch ( Exception $e ) { - $this->error( 'externaldata-db-could-not-connect' ); + $this->error( 'externaldata-db-could-not-connect', $e->getMessage() ); return null; } } @@ -30,28 +32,32 @@ protected function connect() { /** * Get the MongoDB collection $name provided the connection is established. * - * @param string $collection The collection name. - * * @return MongoCollection|null MongoDB collection. */ - protected function getCollection( $collection ) { - $connection = $this->connect(); + protected function fetch() { + $this->mongoClient = $this->connect(); - if ( !$connection ) { + if ( !$this->mongoClient ) { return null; } - $db = $connection->SelectDb( $this->connection['dbname'] ); + $db = $this->mongoClient->SelectDb( $this->credentials['dbname'] ); if ( !$db ) { - $this->error( 'externaldata-db-unknown-database', $this->db_id ); + $this->error( 'externaldata-db-unknown-database', $this->dbId ); return null; } // Check if collection exists. if ( !in_array( $this->from, $db->getCollectionNames(), true ) ) { - // Not $this->connection['dbname']! - $this->error( 'externaldata-mongodb-unknown-collection', $this->db_id . ':' . $this->from ); + // Not $this->credentials['dbname']! + $this->error( 'externaldata-mongodb-unknown-collection', $this->dbId . ':' . $this->from ); return null; } - return new MongoCollection( $db, $collection ); + try { + $collection = new MongoCollection( $db, $this->from ); + } catch ( Exception $e ) { + $this->error( 'externaldata-mongodb-unknown-collection', $this->dbId . ':' . $this->from ); + $collection = false; + } + return $collection; } /** @@ -66,7 +72,7 @@ protected function getCollection( $collection ) { * @return array MongoCursor */ protected function find( $collection, array $filter, array $columns, array $sort, $limit ) { - return $collection->find( $filter, $columns )->sort( $sort )->limit( $limit )->toArray(); + return iterator_to_array( $collection->find( $filter, $columns )->sort( $sort )->limit( $limit ) ); } /** @@ -86,4 +92,11 @@ protected function aggregate( $collection, array $aggregate ) { return null; } } + + /** + * Disconnect from MongoDB. + */ + protected function disconnect() { + $this->mongoClient->close(); + } } diff --git a/includes/connectors/EDConnectorMongodb7.php b/includes/connectors/EDConnectorMongodb7.php index e27625d..e67ea41 100644 --- a/includes/connectors/EDConnectorMongodb7.php +++ b/includes/connectors/EDConnectorMongodb7.php @@ -7,8 +7,10 @@ * @author Alexander Mashin */ class EDConnectorMongodb7 extends EDConnectorMongodb { - /** @var string $regex_class Class that stores MongoDB regular expressions. */ - protected static $regex_class = 'MongoDB\BSON\Regex'; + /** @var ?MongoDB\Client $mongoClient MongoDB client. */ + private $mongoClient; + /** @var string $regexClass Class that stores MongoDB regular expressions. */ + protected static $regexClass = 'MongoDB\BSON\Regex'; /** * Create a MongoDB connection. @@ -20,7 +22,7 @@ protected function connect() { // the MongoDB connect string, which may have sensitive // information. try { - return new MongoDB\Client( $this->connect_string ); + return new MongoDB\Client( $this->connectString ); } catch ( Exception $e ) { return null; } @@ -29,17 +31,15 @@ protected function connect() { /** * Get the MongoDB collection $name provided the connection is established. * - * @param string $collection The collection name. - * * @return MongoDB\Collection|null MongoDB collection. */ - protected function getCollection( $collection ) { - $connection = $this->connect(); - if ( !$connection ) { + protected function fetch() { + $this->mongoClient = $this->connect(); + if ( !$this->mongoClient ) { $this->error( 'externaldata-db-could-not-connect' ); return null; } - return $connection->selectCollection( $this->connection['dbname'], $collection ); + return $this->mongoClient->selectCollection( $this->credentials['dbname'], $this->from ); } /** @@ -57,7 +57,7 @@ protected function find( $collection, array $filter, array $columns, array $sort try { $found = $collection->find( $filter, [ 'sort' => $sort, 'limit' => $limit ] )->toArray(); } catch ( Exception $e ) { - $this->error( 'externaldata-db-could-not-connect' ); + $this->error( 'externaldata-db-could-not-connect', $e->getMessage() ); return null; } return $found; @@ -79,4 +79,11 @@ protected function aggregate( $collection, array $aggregate ) { return null; } } + + /** + * Disconnect from MongoDB. + */ + protected function disconnect() { + $this->mongoClient->close(); + } } diff --git a/includes/connectors/EDConnectorPath.php b/includes/connectors/EDConnectorPath.php index 79b54f6..e38f5c0 100644 --- a/includes/connectors/EDConnectorPath.php +++ b/includes/connectors/EDConnectorPath.php @@ -7,11 +7,11 @@ * */ abstract class EDConnectorPath extends EDConnectorBase { - /** @var bool $needs_parser Needs a EDParser* object. */ - protected static $needs_parser = true; + /** @var bool $needsParser Needs a EDParser* object. */ + protected static $needsParser = true; /** @var string Real filepath. */ - protected $real_path; + protected $realPath; /** * Get data from absolute filepath. Set $this->values. @@ -22,11 +22,11 @@ abstract class EDConnectorPath extends EDConnectorBase { * */ protected function getDataFromPath( $alias ) { - if ( !file_exists( $this->real_path ) ) { + if ( !file_exists( $this->realPath ) ) { $this->error( 'externaldata-missing-file', $alias ); return false; } - $file_contents = file_get_contents( $this->real_path ); + $file_contents = file_get_contents( $this->realPath ); if ( empty( $file_contents ) ) { // Show an error message if there's nothing there. $this->error( 'externaldata-empty-file', $alias ); diff --git a/includes/connectors/EDConnectorPost.php b/includes/connectors/EDConnectorPost.php index 893c551..2b596b8 100644 --- a/includes/connectors/EDConnectorPost.php +++ b/includes/connectors/EDConnectorPost.php @@ -30,13 +30,13 @@ public function __construct( array $args ) { */ public function run() { // Allow extensions or LocalSettings.php to alter HTTP options. - Hooks::run( 'ExternalDataBeforeWebCall', [ 'post', $this->real_url, $this->options ] ); - list( $contents, $this->headers, $errors ) = EDHttpWithHeaders::post( $this->real_url, $this->options ); + Hooks::run( 'ExternalDataBeforeWebCall', [ 'post', $this->realUrl, $this->options ] ); + [ $contents, $this->headers, $errors ] = EDHttpWithHeaders::post( $this->realUrl, $this->options ); if ( !$contents ) { if ( is_array( $errors ) ) { $errors = implode( ',', $errors ); } - $this->error( 'externaldata-post-failed', $this->original_url, $errors ); + $this->error( 'externaldata-post-failed', $this->originalUrl, $errors ); return false; } diff --git a/includes/connectors/EDConnectorPrepared.php b/includes/connectors/EDConnectorPrepared.php new file mode 100644 index 0000000..6feaf8f --- /dev/null +++ b/includes/connectors/EDConnectorPrepared.php @@ -0,0 +1,72 @@ +errors. + * + * @param array &$args An array of arguments for parser/Lua function. + */ + public function __construct( array &$args ) { + parent::__construct( $args ); + + // Specific parameters. + // SQL statement to prepare. + if ( is_array( $args['DBPrepared'] ) ) { + // Several statements for this database connection. + if ( isset( $args['query'] ) && is_string( $args['query'] ) ) { + if ( isset( $args['DBPrepared'][$args['query']] ) ) { + $this->query = $args['DBPrepared'][$args['query']]; + } else { + $this->error( 'externaldata-db-no-such-prepared', $this->dbId, $args['query'] ); + } + } else { + $this->error( 'externaldata-db-prepared-not-specified', $this->dbId ); + } + } else { + // Only one statement for this database connection. + $this->query = $args['DBPrepared']; + } + if ( isset( $args['parameters'] ) ) { + $this->parameters = self::paramToArray( $args['parameters'], false, false, true ); + } + $this->types = isset( $args['DBTypes'] ) ? $args['DBTypes'] : str_repeat( 's', count( $this->parameters ) ); + } + + /** + * Set credentials settings for database from $this->dbId. + * Called by the constructor. + * + * @param array $params Supplemented parameters. + */ + protected function setCredentials( array $params ) { + parent::setCredentials( $params ); + + // Database credentials. + if ( isset( $params['DBServer'] ) ) { + $this->credentials['host'] = $params['DBServer']; + } else { + $this->error( 'externaldata-db-incomplete-information', $this->dbId, 'edgDBServer' ); + } + } + + /** + * Get query text. + * @return string + */ + protected function getQuery() { + return $this->query; + } +} diff --git a/includes/connectors/EDConnectorPreparedMysql.php b/includes/connectors/EDConnectorPreparedMysql.php new file mode 100644 index 0000000..627f79b --- /dev/null +++ b/includes/connectors/EDConnectorPreparedMysql.php @@ -0,0 +1,76 @@ +mysqli = new mysqli( + $this->credentials['host'], + $this->credentials['user'], + $this->credentials['password'], + $this->credentials['dbname'] + ); + } catch ( Exception $e ) { + $this->error( 'externaldata-db-could-not-connect', $e->getMessage() ); + return false; + } + self::stopThrowingWarnings(); + if ( $this->mysqli->connect_error ) { + // Could not create Database object. + $this->error( 'externaldata-db-could-not-connect', $this->mysqli->connect_error ); + return false; + } + return true; + } + + /** + * Get query result as a two-dimensional array. + * @return string[][]|void + */ + protected function fetch() { + // Prepared statement. + $this->prepared = $this->mysqli->prepare( $this->query ); + if ( !$this->prepared ) { + $this->error( 'externaldata-db-invalid-query', $this->query ); + } + + // Bind parameters. + $this->prepared->bind_param( $this->types, ...$this->parameters ); + + // Execute query. + $this->prepared->execute(); + + // Get values. + $result = $this->prepared->get_result(); + if ( $result !== false ) { + $rows = $result->fetch_all( MYSQLI_ASSOC ); + $this->prepared->close(); // late binding. + return $rows; + } else { + $this->error( 'externaldata-db-no-return-values' ); + } + } + + /** + * Disconnect from DB server. + */ + protected function disconnect() { + $this->mysqli->close(); + } +} diff --git a/includes/connectors/EDConnectorRdbms.php b/includes/connectors/EDConnectorRdbms.php new file mode 100644 index 0000000..3519405 --- /dev/null +++ b/includes/connectors/EDConnectorRdbms.php @@ -0,0 +1,97 @@ +database = Database::factory( $this->type, $this->credentials ); + } catch ( Exception $e ) { + $this->error( 'externaldata-db-could-not-connect', $e->getMessage() ); + return false; + } + if ( !$this->database ) { + // Could not create Database object. + $this->error( 'externaldata-db-unknown-type', $this->type ); + return false; + } + if ( !$this->database->isOpen() ) { + $this->error( 'externaldata-db-could-not-connect' ); + return false; + } + return true; + } + + /** + * Get query text. + * @return string + */ + protected function getQuery() { + return $this->database->selectSQLText( + $this->tables, + $this->columns, + $this->conditions, + __METHOD__, + $this->sqlOptions, + $this->joins + ); + } + + /** + * Get query result as a two-dimensional array. + * @return ?array + */ + protected function fetch() { + try { + $rows = $this->database->select( + $this->tables, + $this->columns, + $this->conditions, + __METHOD__, + $this->sqlOptions, + $this->joins + ); + } catch ( Exception $e ) { + // No result. + $this->error( + 'externaldata-db-invalid-query', + $this->getQuery(), + $e->getMessage() + ); + return false; + } + if ( $rows ) { + return $rows; + } else { + // No result. + $this->error( + 'externaldata-db-invalid-query', + $this->getQuery(), + $this->database->lastError() + ); + return false; + } + } + + /** + * Disconnect from DB server. + */ + protected function disconnect() { + $this->database->close(); + } +} diff --git a/includes/connectors/EDConnectorRelational.php b/includes/connectors/EDConnectorRelational.php index d864670..e0bb107 100644 --- a/includes/connectors/EDConnectorRelational.php +++ b/includes/connectors/EDConnectorRelational.php @@ -7,13 +7,13 @@ * @author Yaron Koren * */ -abstract class EDConnectorRelational extends EDConnectorDb { - /** @var Database The database object. */ - private $db; +abstract class EDConnectorRelational extends EDConnectorComposed { /** @var array Tables to query. */ - private $tables = []; + protected $tables = []; /** @var array JOIN conditions. */ - private $joins = []; + protected $joins = []; + /** @var array $aliases Column aliases. */ + private $aliases = []; /** * Constructor. Analyse parameters and wiki settings; set $this->errors. @@ -26,39 +26,34 @@ public function __construct( array &$args ) { // Specific parameters. // The format of $from can be just "TableName", or the more // complex "Table1=Alias1,Table2=Alias2,...". - // TODO: check if self::paramToArray will work here. - foreach ( explode( ',', $this->from ) as $table_string ) { - if ( strpos( $table_string, '=' ) !== false ) { - list( $name, $alias ) = explode( '=', $table_string, 2 ); - } else { - $name = $alias = $table_string; - } - $this->tables[trim( $alias )] = trim( $name ); - } + $this->tables = array_flip( self::paramToArray( $this->from ) ); + $this->joins = isset( $args['join on'] ) ? self::paramToArray( $args['join on'] ) : null; - // Join conditions. - $joins = ( array_key_exists( 'join on', $args ) ) ? $args['join on'] : ''; - $join_strings = explode( ',', $joins ); - if ( count( $join_strings ) > count( $this->tables ) ) { - $this->error( - 'externaldata-db-too-many-joins', - (string)count( $join_strings ), - (string)count( $this->tables ) - ); - } - foreach ( $join_strings as $i => $join_string ) { - if ( $join_string === '' ) { - continue; - } - if ( strpos( $join_string, '=' ) === false ) { - $this->error( 'externaldata-db-invalid-join', $join_string ); + // Column aliases: the correspondence $external_variable => $column_name_in_query_result. + foreach ( $this->columns as $column ) { + // Deal with AS in external names. + $chunks = preg_split( '/\bas\s+/i', $column, 2 ); + $alias = isset( $chunks[1] ) ? trim( $chunks[1] ) : $column; + // Deal with table prefixes in column names (internal_var=tbl1.col1). + if ( preg_match( '/[^.]+$/', $alias, $matches ) ) { + $alias = $matches[0]; } - $aliases = array_keys( $this->tables ); - $alias = $aliases[$i + 1]; - $this->joins[$alias] = [ 'JOIN', $join_string ]; + $this->aliases[$column] = $alias; } } + /** + * Set credentials settings for database from $this->dbId. + * Called by the constructor. + * + * @param array $params Supplemented parameters. + */ + protected function setCredentials( array $params ) { + parent::setCredentials( $params ); + $this->credentials['flags'] = isset( $params['DBFlags'] ) ? $params['DBFlags'] : DBO_DEFAULT; + $this->credentials['tablePrefix'] = isset( $params['DBTablePrefix'] ) ? $params['DBTablePrefix'] : ''; + } + /** * Actually connect to the external data source. * It is presumed that there are no errors in parameters and wiki settings. @@ -67,98 +62,16 @@ public function __construct( array &$args ) { * @return bool True on success, false if error were encountered. */ public function run() { - $this->db = Database::factory( $this->type, $this->connection ); - if ( !$this->db ) { - // Could not create Database object. - $this->error( 'externaldata-db-unknown-type', $this->type ); + if ( !$this->connect() /* late binding. */ ) { return false; } - if ( !$this->db->isOpen() ) { - $this->error( 'externaldata-db-could-not-connect' ); + $rows = $this->fetch(); // late binding. + if ( !$rows ) { return false; } - $this->values = $this->searchDB(); - $this->db->close(); + $this->values = $this->processRows( $rows, $this->aliases ); // late binding. + $this->disconnect(); // late binding. return true; } - /** - * Set connection settings for database from $this->db_id. - * Called by the constructor. - * - * @param array $params Supplemented parameters. - */ - protected function setConnection( array $params ) { - parent::setConnection( $params ); - $this->connection['flags'] = isset( $params['DBFlags'] ) ? $params['DBFlags'] : DBO_DEFAULT; - $this->connection['tablePrefix'] = isset( $params['DBTablePrefix'] ) ? $params['DBTablePrefix'] : ''; - } - - /** - * Extract column name from db.tbl.col identifier. - * @param string $field Full column identifier. - * @return string Extracted column name. - */ - private static function getColumn( $field ) { - if ( preg_match( '/[^.]+$/', $field, $matches ) ) { - return $matches[0]; - } - } - - /** - * Run a query in an open database. - * @return string[][]|void - */ - private function searchDB() { - $rows = $this->db->select( - $this->tables, - $this->columns, - $this->conditions, - __METHOD__, - $this->sql_options, - $this->joins - ); - if ( $rows ) { - $result = []; - foreach ( $rows as $row ) { - // Create a new row object that uses the passed-in - // column names as keys, so that there's always an - // exact match between what's in the query and what's - // in the return value (so that "a.b", for instance, - // doesn't get chopped off to just "b"). - foreach ( $this->columns as $column ) { - $stripped = self::getColumn( $column ); - $field = $row->$stripped; - // This can happen with MSSQL. - if ( $field instanceof DateTime ) { - $field = $field->format( 'Y-m-d H:i:s' ); - } - // Convert the encoding to UTF-8 - // if necessary - based on code at - // http://www.php.net/manual/en/function.mb-detect-encoding.php#102510 - $field = mb_detect_encoding( $field, 'UTF-8', true ) === 'UTF-8' - ? $field - : utf8_encode( $field ); - if ( !isset( $result[$column] ) ) { - $result[$column] = []; - } - $result[$column][] = $field; - } - } - return $result; - } else { - // No result. - $this->error( - 'externaldata-db-invalid-query', - $this->db->selectSQLText( - $this->tables, - $this->columns, - $this->conditions, - __METHOD__, - $this->options, - $this->joins - ) - ); - } - } } diff --git a/includes/connectors/EDConnectorSoap.php b/includes/connectors/EDConnectorSoap.php index c106cb3..0bffdb1 100644 --- a/includes/connectors/EDConnectorSoap.php +++ b/includes/connectors/EDConnectorSoap.php @@ -1,4 +1,5 @@ errors. @@ -36,15 +37,15 @@ public function __construct( array $args ) { ); } if ( array_key_exists( 'request', $args ) ) { - $this->request_name = $args['request']; + $this->requestName = $args['request']; } else { $this->error( 'externaldata-no-param-specified', 'request' ); } - $this->request_data = array_key_exists( 'requestData', $args ) + $this->requestData = array_key_exists( 'requestData', $args ) ? self::paramToArray( $args['requestData'] ) : []; if ( array_key_exists( 'response', $args ) ) { - $this->response_name = $args['response']; + $this->responseName = $args['response']; } else { $this->error( 'externaldata-no-param-specified', 'response' ); } @@ -60,14 +61,9 @@ protected function fetcher() { static $log_errors_client = true; static $log_errors_request = true; // Suppress warnings. - if ( method_exists( \Wikimedia\AtEase\AtEase::class, 'suppressWarnings' ) ) { - // MW >= 1.33 - \Wikimedia\AtEase\AtEase::suppressWarnings(); - } else { - \MediaWiki\suppressWarnings(); - } + self::suppressWarnings(); try { - $client = new SoapClient( $this->real_url, [ 'trace' => true ] ); + $client = new SoapClient( $this->realUrl, [ 'trace' => true ] ); } catch ( Exception $e ) { if ( $log_errors_client ) { $this->error( 'externaldata-caught-exception-soap', $e->getMessage() ); @@ -76,15 +72,10 @@ protected function fetcher() { return null; } // Restore warnings. - if ( method_exists( \Wikimedia\AtEase\AtEase::class, 'restoreWarnings' ) ) { - // MW >= 1.33 - \Wikimedia\AtEase\AtEase::restoreWarnings(); - } else { - \MediaWiki\restoreWarnings(); - } - $request = $this->request_name; + self::restoreWarnings(); + $request = $this->requestName; try { - $result = $client->$request( $this->request_data ); + $result = $client->$request( $this->requestData ); } catch ( Exception $e ) { if ( $log_errors_request ) { $this->error( 'externaldata-caught-exception-soap', $e->getMessage() ); @@ -94,7 +85,7 @@ protected function fetcher() { } if ( $result ) { $this->headers = self::headers( $client->__getLastResponseHeaders() ); - $response = $this->response_name; + $response = $this->responseName; return $result->$response; } } diff --git a/includes/connectors/EDConnectorSql.php b/includes/connectors/EDConnectorSql.php index cdec5e3..e86f84f 100644 --- a/includes/connectors/EDConnectorSql.php +++ b/includes/connectors/EDConnectorSql.php @@ -1,38 +1,29 @@ errors. - * - * @param array &$args An array of arguments for parser/Lua function. - */ - public function __construct( array &$args ) { - parent::__construct( $args ); - } - - /** - * Set connection settings for database from $this->db_id. + * Set credentials settings for database from $this->dbId. * Called by the constructor. * * @param array $params Supplemented parameters. */ - protected function setConnection( array $params ) { - parent::setConnection( $params ); + protected function setCredentials( array $params ) { + parent::setCredentials( $params ); // Database credentials. if ( isset( $params['DBServer'] ) ) { - $this->connection['host'] = $params['DBServer']; + $this->credentials['host'] = $params['DBServer']; } else { - $this->error( 'externaldata-db-incomplete-information', $this->db_id, 'edgDBServer' ); + $this->error( 'externaldata-db-incomplete-information', $this->dbId, 'edgDBServer' ); } } } diff --git a/includes/connectors/EDConnectorSqlite.php b/includes/connectors/EDConnectorSqlite.php index e15ae35..ddfb604 100644 --- a/includes/connectors/EDConnectorSqlite.php +++ b/includes/connectors/EDConnectorSqlite.php @@ -7,32 +7,20 @@ * @author Alexander Mashin * */ -class EDConnectorSqlite extends EDConnectorRelational { - /** @var bool $preserve_external_variables_case External variables' case ought to be preserved. */ - protected static $preserve_external_variables_case = true; - - /** @var string Database directory. */ - private $directory; - - /** - * Constructor. Analyse parameters and wiki settings; set $this->errors. - * - * @param array $args An array of arguments for parser/Lua function. - */ - protected function __construct( array $args ) { - parent::__construct( $args ); - } +class EDConnectorSqlite extends EDConnectorRdbms { + /** @var bool $keepExternalVarsCase External variables' case ought to be preserved. */ + protected static $keepExternalVarsCase = true; /** - * Form connection settings for database from $this->db_id. + * Form credentials settings for database from $this->dbId. * * @param array $params Supplemented parameters. */ - protected function setConnection( array $params ) { - parent::setConnection( $params ); + protected function setCredentials( array $params ) { + parent::setCredentials( $params ); if ( isset( $params['DBDirectory'] ) ) { - $this->connection['dbDirectory'] = $params['DBDirectory']; + $this->credentials['dbDirectory'] = $params['DBDirectory']; } else { $this->error( 'externaldata-db-incomplete-information', 'sqlite directory' ); } diff --git a/includes/connectors/EDConnectorWeb.php b/includes/connectors/EDConnectorWeb.php index 92bca60..ac4ebec 100644 --- a/includes/connectors/EDConnectorWeb.php +++ b/includes/connectors/EDConnectorWeb.php @@ -7,15 +7,6 @@ * */ class EDConnectorWeb extends EDConnectorGet { - /** - * Constructor. Analyse parameters and wiki settings; set $this->errors. - * - * @param array $args An array of arguments for parser/Lua function. - */ - public function __construct( array $args ) { - parent::__construct( $args ); - } - /** * Fetch the web data. Sets HTTP headers. * @@ -24,9 +15,9 @@ public function __construct( array $args ) { protected function fetcher() { // We do not want to repeat error messages self::$tries times. static $log_errors = true; - [ $result, $this->headers, $errors ] = EDHttpWithHeaders::get( $this->real_url, $this->options, __METHOD__ ); + [ $result, $this->headers, $errors ] = EDHttpWithHeaders::get( $this->realUrl, $this->options, __METHOD__ ); if ( $errors && $log_errors ) { - $this->error( 'externaldata-url-not-fetched', $this->original_url ); + $this->error( 'externaldata-url-not-fetched', $this->originalUrl ); foreach ( $errors as $error ) { if ( is_array( $error ) && isset( $error['message'] ) && isset( $error['params'] ) ) { $this->error( $error['message'], $error['params'] ); // -- MW message. diff --git a/includes/parsers/EDParserBase.php b/includes/parsers/EDParserBase.php index 263ec08..cdd2216 100644 --- a/includes/parsers/EDParserBase.php +++ b/includes/parsers/EDParserBase.php @@ -28,7 +28,7 @@ protected function __construct( array $params, array $headers = [] ) { // We need only external variables. // For some parsers, they may be brought lo lower case. $this->external = array_values( - self::paramToArray( $params['data'], false, !static::$preserve_external_variables_case ) + self::paramToArray( $params['data'], false, !static::$keepExternalVarsCase ) ); } else { throw new EDParserException( 'externaldata-no-param-specified', 'data' ); diff --git a/includes/parsers/EDParserCSV.php b/includes/parsers/EDParserCSV.php index feb32a6..fae0b2a 100644 --- a/includes/parsers/EDParserCSV.php +++ b/includes/parsers/EDParserCSV.php @@ -8,7 +8,7 @@ class EDParserCSV extends EDParserBase { /** @var bool The processed text contains a header line. */ - private $header = false; + private $header; /** @var string Column delimiter. */ private $delimiter = ','; @@ -33,7 +33,7 @@ public function __construct( array $params ) { * Parse the text. Called as $parser( $text ) as syntactic sugar. * * @param string $text The text to be parsed. - * @param ?array $defaults The intial values. + * @param ?array $defaults The initial values. * * @return array A two-dimensional column-based array of the parsed values. * @@ -51,12 +51,12 @@ public function __invoke( $text, $defaults = [] ) { //} else { $fiveMBs = 5 * 1024 * 1024; $fp = fopen( "php://temp/maxmemory:$fiveMBs", 'r+' ); - fputs( $fp, $text ); + fwrite( $fp, $text ); rewind( $fp ); $table = []; // phpcs:ignore MediaWiki.ControlStructures.AssignmentInControlStructures.AssignmentInControlStructures while ( $line = fgetcsv( $fp, 0, $this->delimiter ) ) { - array_push( $table, $line ); + $table[] = $line; } fclose( $fp ); // } @@ -107,7 +107,7 @@ public function __invoke( $text, $defaults = [] ) { $header_vals = array_shift( $table ); // On the off chance that there are one or more blank // lines at the beginning, cycle through. - while ( count( $header_vals ) == 0 ) { + while ( count( $header_vals ) === 0 ) { $header_vals = array_shift( $table ); } } diff --git a/includes/parsers/EDParserException.php b/includes/parsers/EDParserException.php index 8738785..6e92d4b 100644 --- a/includes/parsers/EDParserException.php +++ b/includes/parsers/EDParserException.php @@ -8,7 +8,7 @@ class EDParserException extends Exception { /** @var string MW message code. */ - private $msg_code; + private $msgCode; /** @var array Parameters to that message. */ private $params; @@ -20,7 +20,7 @@ class EDParserException extends Exception { */ public function __construct( $code, ...$params ) { parent::__construct( wfMessage( $code, $params )->inContentLanguage()->text() ); - $this->msg_code = $code; + $this->msgCode = $code; $this->params = $params; } @@ -30,7 +30,7 @@ public function __construct( $code, ...$params ) { * @return string Message code. */ public function code() { - return $this->msg_code; + return $this->msgCode; } /** diff --git a/includes/parsers/EDParserGFF.php b/includes/parsers/EDParserGFF.php index 8fdc2ae..b5ad4ec 100644 --- a/includes/parsers/EDParserGFF.php +++ b/includes/parsers/EDParserGFF.php @@ -24,7 +24,7 @@ public function __construct( array $params ) { * Parse the text. Called as $parser( $text ) as syntactic sugar. * * @param string $text The text to be parsed. - * @param ?array $defaults The intial values. + * @param ?array $defaults The initial values. * * @return array A two-dimensional column-based array of the parsed values. * @@ -35,7 +35,7 @@ public function __invoke( $text, $defaults = [] ) { // case a tab) $fiveMBs = 5 * 1024 * 1024; $fp = fopen( "php://temp/maxmemory:$fiveMBs", 'r+' ); - fputs( $fp, $text ); + fwrite( $fp, $text ); rewind( $fp ); $table = []; // phpcs:ignore MediaWiki.ControlStructures.AssignmentInControlStructures.AssignmentInControlStructures @@ -65,7 +65,7 @@ public function __invoke( $text, $defaults = [] ) { // each of the columns in GFF have a // pre-defined name - even the last column // has its own name, "attributes". - $column = is_numeric( $i ) && isset( self::$columns[intval( $i )] ) ? self::$columns[intval( $i )] : $i; + $column = is_numeric( $i ) && isset( self::$columns[(int)$i] ) ? self::$columns[(int)$i] : $i; if ( !array_key_exists( $column, $values ) ) { $values[$column] = []; } diff --git a/includes/parsers/EDParserHTMLwithCSS.php b/includes/parsers/EDParserHTMLwithCSS.php index 522483e..d95957c 100644 --- a/includes/parsers/EDParserHTMLwithCSS.php +++ b/includes/parsers/EDParserHTMLwithCSS.php @@ -8,7 +8,7 @@ class EDParserHTMLwithCSS extends EDParserHTMLwithXPath { /** @var array Mappings of CSS selectors to XPaths. */ - private $css_to_xpath = []; + private $cssToXpath = []; /** * Constructor. @@ -53,7 +53,7 @@ public function __construct( array $params ) { ] ); // CSS selector syntax extension: .attr(href). $xpath .= isset( $matches['attr'] ) ? '/@' . $matches['attr'] : ''; - $this->css_to_xpath[$selector] = $xpath; + $this->cssToXpath[$selector] = $xpath; $selector = $xpath; } } @@ -72,7 +72,7 @@ public function __construct( array $params ) { public function __invoke( $text, $defaults = [] ) { $xpath_values = parent::__invoke( $text, $defaults ); $css_values = []; - foreach ( $this->css_to_xpath as $css => $xpath ) { + foreach ( $this->cssToXpath as $css => $xpath ) { $css_values[$css] = $xpath_values[$xpath]; } return $css_values; diff --git a/includes/parsers/EDParserHTMLwithXPath.php b/includes/parsers/EDParserHTMLwithXPath.php index cb88c28..5e391e2 100644 --- a/includes/parsers/EDParserHTMLwithXPath.php +++ b/includes/parsers/EDParserHTMLwithXPath.php @@ -6,23 +6,11 @@ */ class EDParserHTMLwithXPath extends EDParserXMLwithXPath { - /** - * Constructor. - * - * @param array $params A named array of parameters passed from parser or Lua function. - * - * @throws MWException. - * - */ - public function __construct( array $params ) { - parent::__construct( $params ); - } - /** * Parse the text as HTML. Called as $parser( $text ) as syntactic sugar. * * @param string $text The text to be parsed. - * @param ?array $defaults The intial values. + * @param ?array $defaults The initial values. * * @return array A two-dimensional column-based array of the parsed values. * @@ -68,7 +56,7 @@ public function __invoke( $text, $defaults = [] ) { } catch ( Exception $e ) { throw new EDParserException( 'externaldata-xpath-invalid', $xpath, $e->getMessage() ); } - if ( is_a( $entries, 'DOMNodeList' ) ) { + if ( $entries instanceof DOMNodeList ) { // It's a list of DOM nodes. foreach ( $entries as $entry ) { $nodesArray[] = self::filterEmptyNodes( $entry->textContent ); diff --git a/includes/parsers/EDParserJSON.php b/includes/parsers/EDParserJSON.php index 3e752bc..0ae8a24 100644 --- a/includes/parsers/EDParserJSON.php +++ b/includes/parsers/EDParserJSON.php @@ -8,7 +8,7 @@ class EDParserJSON extends EDParserBase { /** @var int Optional length of the ignored prefix. */ - private $prefix_length = 0; + private $prefixLength; /** * Constructor. @@ -18,7 +18,7 @@ class EDParserJSON extends EDParserBase { */ public function __construct( array $params ) { parent::__construct( $params ); - $this->prefix_length = isset( $params['json offset'] ) ? intval( $params['json offset'] ) : 0; + $this->prefixLength = isset( $params['json offset'] ) ? (int)$params['json offset'] : 0; } /** @@ -32,7 +32,7 @@ public function __construct( array $params ) { * @throws EDParserException */ public function __invoke( $text, $defaults = [] ) { - $json = substr( $text, $this->prefix_length ); + $json = substr( $text, $this->prefixLength ); // FormatJson class is provided by MediaWiki. $json_tree = FormatJson::decode( $json, true ); if ( $json_tree === null ) { @@ -66,13 +66,13 @@ private static function parseTree( array $tree, array &$retrieved_values ) { self::parseTree( $val, $retrieved_values ); } elseif ( is_array( $val ) && count( $val ) > 1 ) { self::parseTree( $val, $retrieved_values ); - } elseif ( is_array( $val ) && count( $val ) == 1 && is_array( current( $val ) ) ) { + } elseif ( is_array( $val ) && count( $val ) === 1 && is_array( current( $val ) ) ) { self::parseTree( current( $val ), $retrieved_values ); } else { // If it's an array with just one element, // treat it like a regular value. // (Why is the null check necessary?) - if ( $val != null && is_array( $val ) ) { + if ( $val !== null && is_array( $val ) ) { $val = current( $val ); } $key = strtolower( $key ); diff --git a/includes/parsers/EDParserJSONwithJSONPath.php b/includes/parsers/EDParserJSONwithJSONPath.php index d46ff08..a0f792b 100644 --- a/includes/parsers/EDParserJSONwithJSONPath.php +++ b/includes/parsers/EDParserJSONwithJSONPath.php @@ -7,8 +7,10 @@ */ class EDParserJSONwithJSONPath extends EDParserBase { - /** @var bool $preserve_external_variables_case Whether external variables' names are case-sensitive for this format. */ - protected static $preserve_external_variables_case = true; + /** @var bool $keepExternalVarsCase Whether external variables' names are case-sensitive for this format. */ + protected static $keepExternalVarsCase = true; + /** @var int Optional length of the ignored prefix. */ + private $prefixLength; /** * Constructor. @@ -19,20 +21,20 @@ class EDParserJSONwithJSONPath extends EDParserBase { public function __construct( array $params ) { parent::__construct( $params ); - $this->prefix_length = isset( $params['json offset'] ) ? intval( $params['json offset'] ) : 0; + $this->prefixLength = isset( $params['json offset'] ) ? (int)$params['json offset'] : 0; } /** * Parse the text. Called as $parser( $text ) as syntactic sugar. * * @param string $text The text to be parsed. - * @param ?array $defaults The intial values. + * @param ?array $defaults The initial values. * * @return array A two-dimensional column-based array of the parsed values. * */ public function __invoke( $text, $defaults = [] ) { - $json = new EDJsonObject( $text ); + $json = new EDJsonObject( substr( $text, $this->prefixLength ) ); $values = parent::__invoke( $text, $defaults ); foreach ( $this->external as $jsonpath ) { try { diff --git a/includes/parsers/EDParserRegex.php b/includes/parsers/EDParserRegex.php index 3e2407f..f470f80 100644 --- a/includes/parsers/EDParserRegex.php +++ b/includes/parsers/EDParserRegex.php @@ -1,4 +1,5 @@ = 1.33 - \Wikimedia\AtEase\AtEase::suppressWarnings(); - } else { - \MediaWiki\suppressWarnings(); - } + self::suppressWarnings(); // Run regular expression against null and compare results with false. // @see https://stackoverflow.com/a/12941133. if ( preg_match( $regex, null ) !== false ) { @@ -40,12 +36,7 @@ public function __construct( array $params ) { throw new EDParserException( 'externaldata-invalid-regex', $regex ); } // Restore warnings. - if ( method_exists( \Wikimedia\AtEase\AtEase::class, 'restoreWarnings' ) ) { - // MW >= 1.33 - \Wikimedia\AtEase\AtEase::restoreWarnings(); - } else { - \MediaWiki\restoreWarnings(); - } + self::restoreWarnings(); } /** diff --git a/includes/parsers/EDParserText.php b/includes/parsers/EDParserText.php index 9b6d4ee..8425b3c 100644 --- a/includes/parsers/EDParserText.php +++ b/includes/parsers/EDParserText.php @@ -6,24 +6,14 @@ */ class EDParserText extends EDParserBase { - /** @var bool $preserve_external_variables_case Whether external variables' names are case-sensitive for this format. */ - protected static $preserve_external_variables_case = true; - - /** - * Constructor. - * - * @param array $params A named array of parameters passed from parser or Lua function. - * - */ - public function __construct( array $params ) { - parent::__construct( $params ); - } + /** @var bool $keepExternalVarsCase Whether external variables' names are case-sensitive for this format. */ + protected static $keepExternalVarsCase = true; /** * Parse the text. Called as $parser( $text ) as syntactic sugar. * * @param string $text The text to be parsed. - * @param ?array $defaults The intial values. + * @param ?array $defaults The initial values. * * @return array A two-dimensional column-based array of the parsed values. * diff --git a/includes/parsers/EDParserXML.php b/includes/parsers/EDParserXML.php index 7f9990e..81f7a11 100644 --- a/includes/parsers/EDParserXML.php +++ b/includes/parsers/EDParserXML.php @@ -7,8 +7,8 @@ */ class EDParserXML extends EDParserBase { - /** @var array $XMLValues Stores XML values between calls. */ - private static $XMLValues = []; + /** @var array $xmlValues Stores XML values between calls. */ + private static $xmlValues = []; /** @var string $currentXMLTag Stores current XML tag between calls. */ private static $currentXMLTag = null; /** @var string $currentValue Stores value of the current XML tag between calls. */ @@ -16,23 +16,11 @@ class EDParserXML extends EDParserBase { /** @var string $ampersandReplacement A temporary replacement for ampersands, not likely to be met in a real XML. */ private static $ampersandReplacement = 'THIS IS A LONG STRING USED AS A REPLACEMENT FOR AMPERSANDS 55555555'; - /** - * Constructor. - * - * @param array $params A named array of parameters passed from parser or Lua function. - * - * @throws EDParserException. - * - */ - public function __construct( array $params ) { - parent::__construct( $params ); - } - /** * Parse the text as XML. Called as $parser( $text ) as syntactic sugar. * * @param string $text The text to be parsed. - * @param ?array $defaults The intial values. + * @param ?array $defaults The initial values. * * @return array A two-dimensional column-based array of the parsed values. * @@ -40,7 +28,7 @@ public function __construct( array $params ) { * */ public function __invoke( $text, $defaults = [] ) { - self::$XMLValues = parent::__invoke( $text, $defaults ); + self::$xmlValues = parent::__invoke( $text, $defaults ); // Remove comments from XML - for some reason, xml_parse() // can't handle them. @@ -60,7 +48,7 @@ public function __invoke( $text, $defaults = [] ) { ); } xml_parser_free( $xml_parser ); - return self::$XMLValues; + return self::$xmlValues; } /** @@ -79,10 +67,10 @@ private static function startElement( $parser, $name, array $attrs ) { foreach ( $attrs as $attr => $value ) { $attr = strtolower( $attr ); $value = str_replace( self::$ampersandReplacement, '&', $value ); - if ( array_key_exists( $attr, self::$XMLValues ) ) { - self::$XMLValues[$attr][] = $value; + if ( array_key_exists( $attr, self::$xmlValues ) ) { + self::$xmlValues[$attr][] = $value; } else { - self::$XMLValues[$attr] = [ $value ]; + self::$xmlValues[$attr] = [ $value ]; } } } @@ -94,10 +82,10 @@ private static function startElement( $parser, $name, array $attrs ) { * */ private static function endElement( $parser, $name ) { - if ( !array_key_exists( self::$currentXMLTag, self::$XMLValues ) ) { - self::$XMLValues[self::$currentXMLTag] = []; + if ( !array_key_exists( self::$currentXMLTag, self::$xmlValues ) ) { + self::$xmlValues[self::$currentXMLTag] = []; } - self::$XMLValues[self::$currentXMLTag][] = self::$currentValue; + self::$xmlValues[self::$currentXMLTag][] = self::$currentValue; // Clear the value both here and in startElement(), in case this // is an embedded tag. self::$currentValue = ''; @@ -110,7 +98,7 @@ private static function endElement( $parser, $name ) { * attribute self::$currentValue with the current value and append to it. * * @param resource $parser XML parser created by xml_parser_create(); - * @param string $content A chunk of XML tag unner content. + * @param string $content A chunk of XML tag inner content. */ private static function getContent( $parser, $content ) { // Replace ampersands, to avoid the XML getting split up diff --git a/includes/parsers/EDParserXMLwithXPath.php b/includes/parsers/EDParserXMLwithXPath.php index 414a07d..f5acbcd 100644 --- a/includes/parsers/EDParserXMLwithXPath.php +++ b/includes/parsers/EDParserXMLwithXPath.php @@ -7,11 +7,11 @@ */ class EDParserXMLwithXPath extends EDParserBase { - /** @var bool $preserve_external_variables_case Whether external variables' names are case-sensitive for this format. */ - protected static $preserve_external_variables_case = true; + /** @var bool $keepExternalVarsCase Whether external variables' names are case-sensitive for this format. */ + protected static $keepExternalVarsCase = true; - /** @var string $default_xmlns_prefix Default prefix for xmlns. */ - private $default_xmlns_prefix; + /** @var string $defaultXmlnsPrefix Default prefix for xmlns. */ + private $defaultXmlnsPrefix; /** * Constructor. @@ -24,7 +24,7 @@ class EDParserXMLwithXPath extends EDParserBase { public function __construct( array $params ) { parent::__construct( $params ); if ( array_key_exists( 'default xmlns prefix', $params ) ) { - $this->default_xmlns_prefix = $params['default xmlns prefix']; + $this->defaultXmlnsPrefix = $params['default xmlns prefix']; } } @@ -51,9 +51,9 @@ public function __invoke( $text, $defaults = [] ) { // Set default prefix for unprefixed xmlns's. $namespaces = $xml->getDocNamespaces( true ); foreach ( $namespaces as $prefix => $namespace ) { - if ( !$prefix && $this->default_xmlns_prefix ) { - $namespaces[$this->default_xmlns_prefix] = $namespace; - $xml->registerXPathNamespace( $this->default_xmlns_prefix, $namespace ); + if ( !$prefix && $this->defaultXmlnsPrefix ) { + $namespaces[$this->defaultXmlnsPrefix] = $namespace; + $xml->registerXPathNamespace( $this->defaultXmlnsPrefix, $namespace ); } }