diff options
Diffstat (limited to 'www/wiki/extensions/ExternalData/includes/ED_ParserFunctions.php')
-rw-r--r-- | www/wiki/extensions/ExternalData/includes/ED_ParserFunctions.php | 663 |
1 files changed, 663 insertions, 0 deletions
diff --git a/www/wiki/extensions/ExternalData/includes/ED_ParserFunctions.php b/www/wiki/extensions/ExternalData/includes/ED_ParserFunctions.php new file mode 100644 index 00000000..8edc7e17 --- /dev/null +++ b/www/wiki/extensions/ExternalData/includes/ED_ParserFunctions.php @@ -0,0 +1,663 @@ +<?php +/** + * Class for handling the parser functions for External Data + */ + +class EDParserFunctions { + + /** + * A helper function, called by doGetWebData(). + */ + static public function setGlobalValuesArray( $external_values, $filters, $mappings ) { + global $edgValues; + + foreach ( $filters as $filter_var => $filter_value ) { + // Find the entry of $external_values that matches + // the filter variable; if none exists, just ignore + // the filter. + if ( array_key_exists( $filter_var, $external_values ) ) { + 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 ( trim( $single_value ) != trim( $filter_value ) ) { + foreach ( $external_values as $external_var => $external_value ) { + unset( $external_values[$external_var][$i] ); + } + } + } + } else { + // 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; + } + } + } + } + // for each external variable name specified in the function + // call, get its value or values (if any exist), and attach it + // or them to the local variable name + foreach ( $mappings as $local_var => $external_var ) { + if ( array_key_exists( $external_var, $external_values ) ) { + if ( is_array( $external_values[$external_var] ) ) { + // array_values() restores regular + // 1, 2, 3 indexes to array, after unset() + // in filtering may have removed some + $edgValues[$local_var] = array_values( $external_values[$external_var] ); + } else { + $edgValues[$local_var][] = $external_values[$external_var]; + } + } + } + } + + /** + * Render the #get_web_data parser function. + */ + static function doGetWebData( &$parser ) { + global $edgCurPageName, $edgValues, $edgCacheExpireTime; + + // If we're handling multiple pages, reset $edgValues + // when we move from one page to another. + $cur_page_name = $parser->getTitle()->getText(); + if ( ! isset( $edgCurPageName ) || $edgCurPageName != $cur_page_name ) { + $edgValues = array(); + $edgCurPageName = $cur_page_name; + } + + $params = func_get_args(); + array_shift( $params ); // we already know the $parser ... + $args = EDUtils::parseParams( $params ); // parse params into name-value pairs + if ( array_key_exists( 'url', $args ) ) { + $url = $args['url']; + } else { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'url')->parse() ); + } + $url = str_replace( ' ', '%20', $url ); // do some minor URL-encoding + // if the URL isn't allowed (based on a whitelist), exit + if ( ! EDUtils::isURLAllowed( $url ) ) { + return EDUtils::formatErrorMessage( "URL is not allowed" ); + } + + if ( array_key_exists( 'format', $args ) ) { + $format = strtolower( $args['format'] ); + } else { + $format = ''; + } + if ( $format == 'xml' ) { + if ( array_key_exists( 'use xpath', $args ) ) { + // Somewhat of a hack - store the fact that + // we're using XPath within the format, even + // though the format is still XML. + $format = 'xml with xpath'; + } + } elseif ( $format == 'csv' || $format == 'csv with header' ) { + if ( array_key_exists( 'delimiter', $args ) ) { + $delimiter = $args['delimiter']; + // Hopefully this solution isn't "too clever". + $format = array( $format, $args['delimiter'] ); + } + } + + if ( array_key_exists( 'data', $args ) ) { + // parse the 'data' arg into mappings + if ( $format == 'xml with xpath' ) { + $mappings = EDUtils::paramToArray( $args['data'], false, false ); + } else { + $mappings = EDUtils::paramToArray( $args['data'], false, true ); + } + } else { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'data')->parse() ); + } + + if ( array_key_exists( 'cache seconds', $args) ) { + // set cache expire time + $cacheExpireTime = $args['cache seconds']; + } else { + $cacheExpireTime = $edgCacheExpireTime; + } + + if ( array_key_exists( 'json offset', $args) ) { + $prefixLength = $args['json offset']; + } else { + $prefixLength = 0; + } + + $postData = array_key_exists( 'post data', $args ) ? $args['post data'] : ''; + $external_values = EDUtils::getDataFromURL( $url, $format, $mappings, $postData, $cacheExpireTime, $prefixLength ); + if ( is_string( $external_values ) ) { + // It's an error message - display it on the screen. + return EDUtils::formatErrorMessage( $external_values ); + } + if ( count( $external_values ) == 0 ) { + return; + } + + if ( array_key_exists( 'filters', $args ) ) { + // parse the 'filters' arg + $filters = EDUtils::paramToArray( $args['filters'], true, false ); + } else { + $filters = array(); + } + + self::setGlobalValuesArray( $external_values, $filters, $mappings ); + } + + /** + * Render the #get_file_data parser function. + */ + static function doGetFileData( &$parser ) { + global $edgCurPageName, $edgValues, $edgCacheExpireTime; + + // If we're handling multiple pages, reset $edgValues + // when we move from one page to another. + $cur_page_name = $parser->getTitle()->getText(); + if ( ! isset( $edgCurPageName ) || $edgCurPageName != $cur_page_name ) { + $edgValues = array(); + $edgCurPageName = $cur_page_name; + } + + $params = func_get_args(); + array_shift( $params ); // we already know the $parser ... + $args = EDUtils::parseParams( $params ); // parse params into name-value pairs + if ( array_key_exists( 'file', $args ) ) { + $file = $args['file']; + } elseif ( array_key_exists( 'directory', $args ) ) { + $directory = $args['directory']; + if ( array_key_exists( 'file name', $args ) ) { + $fileName = $args['file name']; + } else { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'file name')->parse() ); + } + } else { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'file|directory')->parse() ); + } + + if ( array_key_exists( 'format', $args ) ) { + $format = strtolower( $args['format'] ); + } else { + $format = ''; + } + if ( $format == 'xml' ) { + if ( array_key_exists( 'use xpath', $args ) ) { + // Somewhat of a hack - store the fact that + // we're using XPath within the format, even + // though the format is still XML. + $format = 'xml with xpath'; + } + } elseif ( $format == 'csv' || $format == 'csv with header' ) { + if ( array_key_exists( 'delimiter', $args ) ) { + $delimiter = $args['delimiter']; + // Hopefully this solution isn't "too clever". + $format = array( $format, $args['delimiter'] ); + } + } + + if ( array_key_exists( 'data', $args ) ) { + // parse the 'data' arg into mappings + if ( $format == 'xml with xpath' ) { + $mappings = EDUtils::paramToArray( $args['data'], false, false ); + } else { + $mappings = EDUtils::paramToArray( $args['data'], false, true ); + } + } else { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'data')->parse() ); + } + + if ( array_key_exists( 'cache seconds', $args) ) { + // set cache expire time + $cacheExpireTime = $args['cache seconds']; + } else { + $cacheExpireTime = $edgCacheExpireTime; + } + + if ( isset( $file ) ) { + $external_values = EDUtils::getDataFromFile( $file, $format, $mappings ); + } else { + $external_values = EDUtils::getDataFromDirectory( $directory, $fileName, $format, $mappings ); + } + + if ( is_string( $external_values ) ) { + // It's an error message - display it on the screen. + return EDUtils::formatErrorMessage( $external_values ); + } + if ( count( $external_values ) == 0 ) { + return; + } + + if ( array_key_exists( 'filters', $args ) ) { + // parse the 'filters' arg + $filters = EDUtils::paramToArray( $args['filters'], true, false ); + } else { + $filters = array(); + } + + self::setGlobalValuesArray( $external_values, $filters, $mappings ); + } + /** + * Render the #get_soap_data parser function. + */ + static function doGetSOAPData( &$parser ) { + global $edgCurPageName, $edgValues, $edgCacheExpireTime; + + // If we're handling multiple pages, reset $edgValues + // when we move from one page to another. + $cur_page_name = $parser->getTitle()->getText(); + if ( ! isset( $edgCurPageName ) || $edgCurPageName != $cur_page_name ) { + $edgValues = array(); + $edgCurPageName = $cur_page_name; + } + + $params = func_get_args(); + array_shift( $params ); // we already know the $parser ... + $args = EDUtils::parseParams( $params ); // parse params into name-value pairs + if ( array_key_exists( 'url', $args ) ) { + $url = $args['url']; + } else { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'url')->parse() ); + } + $url = str_replace( ' ', '%20', $url ); // do some minor URL-encoding + // if the URL isn't allowed (based on a whitelist), exit + if ( ! EDUtils::isURLAllowed( $url ) ) { + return EDUtils::formatErrorMessage( "URL is not allowed" ); + } + + if ( array_key_exists( 'request', $args ) ) { + $requestName = $args['request']; + } else { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'request')->parse() ); + } + + if ( array_key_exists( 'requestData', $args ) ) { + $requestData = EDUtils::paramToArray( $args['requestData'] ); + } else { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'requestData')->parse() ); + } + + if ( array_key_exists( 'response', $args ) ) { + $responseName = $args['response']; + } else { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'response')->parse() ); + } + + if ( array_key_exists( 'data', $args ) ) { + $mappings = EDUtils::paramToArray( $args['data'] ); // parse the data arg into mappings + } else { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'data')->parse() ); + } + + $external_values = EDUtils::getSOAPData( $url, $requestName, $requestData, $responseName, $mappings); + if ( is_string( $external_values ) ) { + // It's an error message - display it on the screen. + return EDUtils::formatErrorMessage( $external_values ); + } + + self::setGlobalValuesArray( $external_values, array(), $mappings ); + } + + /** + * Render the #get_ldap_data parser function + */ + static function doGetLDAPData( &$parser ) { + global $edgCurPageName, $edgValues; + + // if we're handling multiple pages, reset $edgValues + // when we move from one page to another + $cur_page_name = $parser->getTitle()->getText(); + if ( ! isset( $edgCurPageName ) || $edgCurPageName != $cur_page_name ) { + $edgValues = array(); + $edgCurPageName = $cur_page_name; + } + + $params = func_get_args(); + array_shift( $params ); // we already know the $parser ... + $args = EDUtils::parseParams( $params ); // parse params into name-value pairs + if ( array_key_exists( 'data', $args ) ) { + $mappings = EDUtils::paramToArray( $args['data'] ); // parse the data arg into mappings + } else { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'data')->parse() ); + } + + if ( !array_key_exists( 'filter', $args ) ) { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'filter')->parse() ); + } elseif ( !array_key_exists( 'domain', $args ) ) { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'domain')->parse() ); + } else { + $external_values = EDUtils::getLDAPData( $args['filter'], $args['domain'], array_values( $mappings ) ); + } + + // Build $edgValues + foreach ( $external_values as $i => $row ) { + if ( !is_array( $row ) ) { + continue; + } + foreach ( $mappings as $local_var => $external_var ) { + if ( array_key_exists( $external_var, $row ) ) { + $edgValues[$local_var][] = $row[$external_var][0]; + } else { + $edgValues[$local_var][] = ''; + } + } + } + } + + /** + * Render the #get_db_data parser function + */ + static function doGetDBData( &$parser ) { + global $edgCurPageName, $edgValues; + + // if we're handling multiple pages, reset $edgValues + // when we move from one page to another + $cur_page_name = $parser->getTitle()->getText(); + if ( ! isset( $edgCurPageName ) || $edgCurPageName != $cur_page_name ) { + $edgValues = array(); + $edgCurPageName = $cur_page_name; + } + + $params = func_get_args(); + array_shift( $params ); // we already know the $parser ... + $args = EDUtils::parseParams( $params ); // parse params into name-value pairs + $data = ( array_key_exists( 'data', $args ) ) ? $args['data'] : null; + if ( array_key_exists( 'db', $args ) ) { + $dbID = $args['db']; + } elseif ( array_key_exists( 'server', $args ) ) { + // For backwards-compatibility - 'db' parameter was + // added in External Data version 1.3. + $dbID = $args['server']; + } else { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'db' )->parse() ); + } + if ( array_key_exists( 'from', $args ) ) { + $from = $args['from']; + } else { + return EDUtils::formatErrorMessage( wfMessage( 'externaldata-no-param-specified', 'from')->parse() ); + } + $conds = ( array_key_exists( 'where', $args ) ) ? $args['where'] : null; + $limit = ( array_key_exists( 'limit', $args ) ) ? $args['limit'] : null; + $orderBy = ( array_key_exists( 'order by', $args ) ) ? $args['order by'] : null; + $groupBy = ( array_key_exists( 'group by', $args ) ) ? $args['group by'] : null; + $sqlOptions = array( 'LIMIT' => $limit, 'ORDER BY' => $orderBy, 'GROUP BY' => $groupBy ); + $joinOn = ( array_key_exists( 'join on', $args ) ) ? $args['join on'] : null; + $otherParams = array(); + if ( array_key_exists('aggregate', $args ) ) { + $otherParams['aggregate'] = $args['aggregate']; + } elseif ( array_key_exists( 'find query', $args ) ) { + $otherParams['find query'] = $args['find query']; + } + $mappings = EDUtils::paramToArray( $data ); // parse the data arg into mappings + + $external_values = EDUtils::getDBData( $dbID, $from, array_values( $mappings ), $conds, $sqlOptions, $joinOn, $otherParams ); + + // Handle error cases. + if ( !is_array( $external_values ) ) { + return EDUtils::formatErrorMessage( $external_values ); + } + + // Build $edgValues. + foreach ( $mappings as $local_var => $external_var ) { + if ( array_key_exists( $external_var, $external_values ) ) { + foreach ( $external_values[$external_var] as $value ) { + $edgValues[$local_var][] = $value; + } + } + } + } + + /** + * Get the specified index of the array for the specified local + * variable retrieved by one of the #get... parser functions. + */ + static function getIndexedValue( $var, $i ) { + global $edgValues; + if ( array_key_exists( $var, $edgValues ) && array_key_exists( $i, $edgValues[$var] ) ) { + return $edgValues[$var][$i]; + } else { + return ''; + } + } + + /** + * Render the #external_value parser function + */ + static function doExternalValue( &$parser, $local_var = '' ) { + global $edgValues, $edgExternalValueVerbose; + if ( ! array_key_exists( $local_var, $edgValues ) ) { + return $edgExternalValueVerbose ? EDUtils::formatErrorMessage( "Error: no local variable \"$local_var\" was set." ) : ''; + } elseif ( is_array( $edgValues[$local_var] ) ) { + return $edgValues[$local_var][0]; + } else { + return $edgValues[$local_var]; + } + } + + /** + * Render the #for_external_table parser function + */ + static function doForExternalTable( &$parser, $expression = '' ) { + global $edgValues; + + // Get the variables used in this expression, get the number + // of values for each, and loop through. + $matches = array(); + preg_match_all( '/{{{([^}]*)}}}/', $expression, $matches ); + $variables = $matches[1]; + $num_loops = 0; + + $commands = array( "urlencode", "htmlencode" ); + // Used for a regexp check. + $commandsStr = implode( '|', $commands ); + + foreach ( $variables as $variable ) { + // If it ends with one of the pre-defined "commands", + // ignore the command to get the actual variable name. + foreach ( $commands as $command ) { + $variable = str_replace( $command, '', $variable ); + } + $variable = str_replace( '.urlencode', '', $variable ); + if ( array_key_exists( $variable, $edgValues ) ) { + $num_loops = max( $num_loops, count( $edgValues[$variable] ) ); + } + } + + $text = ""; + for ( $i = 0; $i < $num_loops; $i++ ) { + $cur_expression = $expression; + foreach ( $variables as $variable ) { + // If it ends with one of the pre-defined "commands", + // ignore the command to get the actual variable name. + $matches = array(); + preg_match( "/([^.]*)\.?($commandsStr)?$/", $variable, $matches ); + + $real_var = $matches[1]; + if ( count( $matches ) == 3 ) { + $command = $matches[2]; + } else { + $command = null; + } + + switch( $command ) { + case "htmlencode": + $value = htmlentities( self::getIndexedValue( $real_var, $i ), ENT_COMPAT | ENT_HTML401| ENT_SUBSTITUTE, null, false ); + break; + case "urlencode": + $value = urlencode( self::getIndexedValue( $real_var, $i ) ); + break; + default: + $value = self::getIndexedValue( $real_var, $i ); + } + + $cur_expression = str_replace( '{{{' . $variable . '}}}', $value, $cur_expression ); + } + $text .= $cur_expression; + } + return $text; + } + + /** + * Render the #display_external_table parser function + * + * @author Dan Bolser + */ + static function doDisplayExternalTable( &$parser ) { + global $edgValues; + + $params = func_get_args(); + array_shift( $params ); // we already know the $parser ... + $args = EDUtils::parseParams( $params ); // parse params into name-value pairs + + if ( array_key_exists( 'template', $args ) ) { + $template = $args['template']; + } else { + return EDUtils::formatErrorMessage( "No template specified" ); + } + + if ( array_key_exists( 'data', $args ) ) { + // parse the 'data' arg into mappings + $mappings = EDUtils::paramToArray( $args['data'], false, false ); + } else { + // or just use keys from edgValues + foreach ( $edgValues as $local_variable => $values ) { + $mappings[$local_variable] = $local_variable; + } + } + + // The string placed in the wikitext between template calls - + // default is a newline. + if ( array_key_exists( 'delimiter', $args ) ) { + $delimiter = str_replace( '\n', "\n", $args['delimiter'] ); + } else { + $delimiter = "\n"; + } + + $num_loops = 0; // May differ when multiple '#get_'s are used in one page + foreach ( $mappings as $template_param => $local_variable ) { + if ( !array_key_exists( $local_variable, $edgValues ) ) { + // Don't throw an error message - the source may just + // not publish this variable. + continue; + } + $num_loops = max( $num_loops, count( $edgValues[$local_variable] ) ); + } + + if ( array_key_exists( 'intro template', $args ) && $num_loops > 0) { + $text = '{{' . $args['intro template'] . '}}'; + } else { + $text = ""; + } + for ( $i = 0; $i < $num_loops; $i++ ) { + if ( $i > 0 ) { + $text .= $delimiter; + } + $text .= '{{' . $template; + foreach ( $mappings as $template_param => $local_variable ) { + $value = self::getIndexedValue( $local_variable, $i ); + $text .= "|$template_param=$value"; + } + $text .= "}}"; + } + if ( array_key_exists( 'outro template', $args ) && $num_loops > 0 ) { + $text .= '{{' . $args['outro template'] . '}}'; + } + + // This actually 'calls' the template that we built above + return array( $text, 'noparse' => false ); + } + + /** + * Based on Semantic Internal Objects' + * SIOSubobjectHandler::doSetInternal(). + */ + public static function callSubobject( $parser, $params ) { + // This is a hack, since SMW's SMWSubobject::render() call is + // not meant to be called outside of 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 = array( &$parser ); + // Blank first argument, so that subobject ID will be + // an automatically-generated random number. + $subobjectArgs[1] = ''; + // "main" property, pointing back to the page. + $mainPageName = $parser->getTitle()->getText(); + $mainPageNamespace = $parser->getTitle()->getNsText(); + if ( $mainPageNamespace != '' ) { + $mainPageName = $mainPageNamespace . ':' . $mainPageName; + } + $subobjectArgs[2] = $params[0] . '=' . $mainPageName; + + foreach ( $params as $i => $value ) { + if ( $i == 0 ) continue; + $subobjectArgs[] = $value; + } + + // SMW 1.9+ + $instance = \SMW\ParserFunctionFactory::newFromParser( $parser )->getSubobjectParser(); + return $instance->parse( new SMW\ParserParameterFormatter( $subobjectArgs ) ); + } + + /** + * Render the #store_external_table parser function + */ + static function doStoreExternalTable( &$parser ) { + global $edgValues; + + $params = func_get_args(); + array_shift( $params ); // we already know the $parser... + + // Get the variables used in this expression, get the number + // of values for each, and loop through. + $expression = implode( '|', $params ); + $matches = array(); + preg_match_all( '/{{{([^}]*)}}}/', $expression, $matches ); + $variables = $matches[1]; + $num_loops = 0; + foreach ( $variables as $variable ) { + // ignore the presence of '.urlencode' - it's a command, + // not part of the actual variable name + $variable = str_replace( '.urlencode', '', $variable ); + if ( array_key_exists( $variable, $edgValues ) ) { + $num_loops = max( $num_loops, count( $edgValues[$variable] ) ); + } + } + $text = ""; + for ( $i = 0; $i < $num_loops; $i++ ) { + // re-get $params + $params = func_get_args(); + array_shift( $params ); + foreach ( $params as $j => $param ) { + foreach ( $variables as $variable ) { + // If variable name ends with a ".urlencode", + // that's a command - URL-encode the value of + // the actual variable. + if ( strrpos( $variable, '.urlencode' ) === strlen( $variable ) - strlen( '.urlencode' ) ) { + $real_var = str_replace( '.urlencode', '', $variable ); + $value = urlencode( self::getIndexedValue( $real_var , $i ) ); + } else { + $value = self::getIndexedValue( $variable , $i ); + } + $params[$j] = str_replace( '{{{' . $variable . '}}}', $value, $params[$j] ); + } + } + + self::callSubobject( $parser, $params ); + } + return null; + } + + /** + * Render the #clear_external_data parser function + */ + static function doClearExternalData( &$parser ) { + global $edgValues; + $edgValues = array(); + } +} |