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 );
}
}