diff --git a/CHANGELOG.md b/CHANGELOG.md
index bb5da80db8..15354d1dee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,16 @@
+# Version 8.6.0
+*2022-09-09"
+
+* Support for new output_formatting option in reports for details pages (occurrences, samples,
+ locations) with auto-formatting of hyperlinks for text attribute data.
+* Improvements to the REST API's auto-generated documentation.
+
+# Version 8.5.0
+*2022-09-09*
+
+* Adds a new standard filter parameter for filtering occurrences by sample ID (smp_id).
+* Adds reports required to support a new recording_system_links Drupal module.
+
# Version 8.4.0
*2022-08-10*
diff --git a/application/config/version.php b/application/config/version.php
index a95e5ecb50..1e4be2c762 100644
--- a/application/config/version.php
+++ b/application/config/version.php
@@ -29,7 +29,7 @@
*
* @var string
*/
-$config['version'] = '8.5.0';
+$config['version'] = '8.6.0';
/**
* Version release date.
diff --git a/application/libraries/XMLReportReader.php b/application/libraries/XMLReportReader.php
index 7623a58265..81b89ccfe2 100644
--- a/application/libraries/XMLReportReader.php
+++ b/application/libraries/XMLReportReader.php
@@ -848,7 +848,7 @@ public function describeReport($descLevel) {
/**
*/
public function getAttributeDefns() {
- return $this->attributes;
+ return $this->attributes;
}
public function getVagueDateProcessing() {
@@ -1005,7 +1005,7 @@ private function mergeParam($name, $reader = NULL) {
'population_call',
'linked_to',
'linked_filter_field',
- 'order_by'
+ 'order_by',
]);
if ($this->params[$name]['datatype'] === 'lookup') {
@@ -1030,21 +1030,21 @@ private function mergeParam($name, $reader = NULL) {
if (!isset($this->params[$name]['joins'])) {
$this->params[$name]['joins'] = [];
}
- $this->params[$name]['joins'][] = array(
+ $this->params[$name]['joins'][] = [
'value' => $reader->getAttribute('value'),
'operator' => $reader->getAttribute('operator'),
'sql' => $reader->readString(),
- );
+ ];
}
if ($reader->nodeType == XMLREADER::ELEMENT && $reader->name === 'where') {
if (!isset($this->params[$name]['wheres'])) {
$this->params[$name]['wheres'] = [];
}
- $this->params[$name]['wheres'][] = array(
+ $this->params[$name]['wheres'][] = [
'value' => $reader->getAttribute('value'),
'operator' => $reader->getAttribute('operator'),
'sql' => $reader->readString(),
- );
+ ];
}
}
}
@@ -1238,8 +1238,7 @@ private function mergeXmlColumn($reader) {
private function mergeColumn($name, $display = '', $style = '', $feature_style='', $class='', $visible='', $img='',
$orderby='', $mappable='', $autodef=TRUE) {
- if (array_key_exists($name, $this->columns))
- {
+ if (array_key_exists($name, $this->columns)) {
if ($display != '') $this->columns[$name]['display'] = $display;
if ($style != '') $this->columns[$name]['style'] = $style;
if ($feature_style != '') $this->columns[$name]['feature_style'] = $feature_style;
@@ -1256,17 +1255,17 @@ private function mergeColumn($name, $display = '', $style = '', $feature_style='
}
else
{
- $this->columns[$name] = array(
- 'display' => $display,
- 'style' => $style,
- 'feature_style' => $feature_style,
- 'class' => $class,
- 'visible' => $visible == '' ? 'true' : $visible,
- 'img' => $img == '' ? 'false' : $img,
- 'orderby' => $orderby,
- 'mappable' => empty($mappable) ? 'false' : $mappable,
- 'autodef' => $autodef,
- );
+ $this->columns[$name] = [
+ 'display' => $display,
+ 'style' => $style,
+ 'feature_style' => $feature_style,
+ 'class' => $class,
+ 'visible' => $visible == '' ? 'true' : $visible,
+ 'img' => $img == '' ? 'false' : $img,
+ 'orderby' => $orderby,
+ 'mappable' => empty($mappable) ? 'false' : $mappable,
+ 'autodef' => $autodef,
+ ];
}
}
@@ -1275,51 +1274,55 @@ private function setTable($tablename, $where) {
$this->tableIndex = 0;
$this->nextTableIndex = 1;
$this->tables[$this->tableIndex] = [
- 'tablename' => $tablename,
- 'parent' => -1,
- 'parentKey' => '',
- 'tableKey' => '',
- 'join' => '',
- 'attributes' => '',
- 'where' => $where,
- 'columns' => []
+ 'tablename' => $tablename,
+ 'parent' => -1,
+ 'parentKey' => '',
+ 'tableKey' => '',
+ 'join' => '',
+ 'attributes' => '',
+ 'where' => $where,
+ 'columns' => [],
];
}
private function setSubTable($tablename, $parentKey, $tableKey, $join, $where) {
- if($tableKey == ''){
- if($parentKey == 'id'){
- $tableKey = 'lt'.$this->nextTableIndex.".".(inflector::singular($this->tables[$this->tableIndex]['tablename'])).'_id';
+ if ($tableKey == '') {
+ if ($parentKey == 'id') {
+ $tableKey = 'lt' . $this->nextTableIndex . "." . (inflector::singular($this->tables[$this->tableIndex]['tablename'])) . '_id';
} else {
- $tableKey = 'lt'.$this->nextTableIndex.'.id';
+ $tableKey = 'lt' . $this->nextTableIndex . '.id';
}
} else {
- $tableKey = 'lt'.$this->nextTableIndex.".".$tableKey;
+ $tableKey = 'lt' . $this->nextTableIndex . "." . $tableKey;
+ }
+ if ($parentKey == '') {
+ $parentKey = 'lt' . $this->tableIndex . "." . (inflector::singular($tablename)) . '_id';
}
- if($parentKey == ''){
- $parentKey = 'lt'.$this->tableIndex.".".(inflector::singular($tablename)).'_id';
- } else { // force the link as this table has foreign key to parent table, standard naming convention.
- $parentKey = 'lt'.$this->tableIndex.".".$parentKey;
+ else {
+ // Force the link as this table has foreign key to parent table, standard
+ // naming convention.
+ $parentKey = 'lt' . $this->tableIndex . '.' . $parentKey;
}
- $this->tables[$this->nextTableIndex] = array(
- 'tablename' => $tablename,
- 'parent' => $this->tableIndex,
- 'parentKey' => $parentKey,
- 'tableKey' => $tableKey,
- 'join' => $join,
- 'attributes' => '',
- 'where' => $where,
- 'columns' => []);
- $this->tableIndex=$this->nextTableIndex;
+ $this->tables[$this->nextTableIndex] = [
+ 'tablename' => $tablename,
+ 'parent' => $this->tableIndex,
+ 'parentKey' => $parentKey,
+ 'tableKey' => $tableKey,
+ 'join' => $join,
+ 'attributes' => '',
+ 'where' => $where,
+ 'columns' => [],
+ ];
+ $this->tableIndex = $this->nextTableIndex;
$this->nextTableIndex++;
}
- private function mergeTabColumn($name, $func = '', $display = '', $style = '', $feature_style = '', $class='', $visible='', $autodef=FALSE) {
+ private function mergeTabColumn($name, $func = '', $display = '', $style = '', $feature_style = '', $class = '', $visible = '', $autodef = FALSE) {
$found = FALSE;
- for($r = 0; $r < count($this->tables[$this->tableIndex]['columns']); $r++){
- if($this->tables[$this->tableIndex]['columns'][$r]['name'] == $name) {
+ for ($r = 0; $r < count($this->tables[$this->tableIndex]['columns']); $r++){
+ if ($this->tables[$this->tableIndex]['columns'][$r]['name'] == $name) {
$found = TRUE;
- if($func != '') {
+ if ($func != '') {
$this->tables[$this->tableIndex]['columns'][$r]['func'] = $func;
}
}
@@ -1329,7 +1332,7 @@ private function mergeTabColumn($name, $func = '', $display = '', $style = '', $
'name' => $name,
'func' => $func,
];
- if($display == '') {
+ if ($display == '') {
$display = $this->tables[$this->tableIndex]['tablename']." ".$name;
}
}
@@ -1371,7 +1374,7 @@ private function setAttributes($where, $separator, $hideVagueDateFields, $meanin
$thisDefn->id = 'id'; // id is the name of the column in the subquery holding the attribute id.
$thisDefn->separator = $separator;
$thisDefn->hideVagueDateFields = $hideVagueDateFields;
- $thisDefn->columnPrefix = 'attr_' . $this->tableIndex.'_';
+ $thisDefn->columnPrefix = 'attr_' . $this->tableIndex . '_';
// Folowing is used the query builder only.
$thisDefn->parentTableIndex = $this->tableIndex;
$thisDefn->where = $where;
@@ -1392,14 +1395,13 @@ private function setDownload($mode) {
* matching from. Commas that are part of nested selects or function calls are ignored
* provided they are enclosed in brackets.
*/
- private function inferFromQuery()
- {
- // Find the columns we're searching for - nested between a SELECT and a FROM.
- // To ensure we can detect the words FROM, SELECT and AS, use a regex to wrap
- // spaces around them, then can do a regular string search
- $this->query=preg_replace("/\b(select)\b/i", ' select ', $this->query);
- $this->query=preg_replace("/\b(from)\b/i", ' from ', $this->query);
- $this->query=preg_replace("/\b(as)\b/i", ' as ', $this->query);
+ private function inferFromQuery() {
+ // Find the columns we're searching for - nested between a SELECT and a
+ // FROM. To ensure we can detect the words FROM, SELECT and AS, use a regex
+ // to wrap spaces around them, then can do a regular string search.
+ $this->query = preg_replace("/\b(select)\b/i", ' select ', $this->query);
+ $this->query = preg_replace("/\b(from)\b/i", ' from ', $this->query);
+ $this->query = preg_replace("/\b(as)\b/i", ' as ', $this->query);
$i0 = strpos($this->query, ' select ') + 7;
$nesting = 1;
$offset = $i0;
@@ -1407,13 +1409,13 @@ private function inferFromQuery()
$nextSelect = strpos($this->query, ' select ', $offset);
$nextFrom = strpos($this->query, ' from ', $offset);
if ($nextSelect !== FALSE && $nextSelect < $nextFrom) {
- //found start of sub-query
+ // Found start of sub-query.
$nesting++;
$offset = $nextSelect + 7;
} else {
$nesting--;
if ($nesting != 0) {
- //found end of sub-query
+ // Found end of sub-query.
$offset = $nextFrom + 5;
}
}
@@ -1425,36 +1427,36 @@ private function inferFromQuery()
$colString = str_replace('#fields#', '', substr($this->query, $i0, $i1));
// Now divide up the list of columns, which are comma separated, but ignore
- // commas nested in brackets
+ // commas nested in brackets.
$colStart = 0;
- $nextComma = strpos($colString, ',', $colStart);
- while ($nextComma !== FALSE)
- {//loop through columns
- $nextOpen = strpos($colString, '(', $colStart);
- while ($nextOpen !== FALSE && $nextComma !==FALSE && $nextOpen < $nextComma)
- { //skipping commas in brackets
+ $nextComma = strpos($colString, ',', $colStart);
+ while ($nextComma !== FALSE) {
+ // Loop through columns.
+ $nextOpen = strpos($colString, '(', $colStart);
+ while ($nextOpen !== FALSE && $nextComma !== FALSE && $nextOpen < $nextComma) {
+ // Skipping commas in brackets.
$offset = $this->strposclose($colString, $nextOpen) + 1;
- $nextComma = strpos($colString, ',', $offset);
- $nextOpen = strpos($colString, '(', $offset);
+ $nextComma = strpos($colString, ',', $offset);
+ $nextOpen = strpos($colString, '(', $offset);
}
if ($nextComma !== FALSE) {
- //extract column and move on to next
+ // Extract column and move on to next.
$cols[] = substr($colString, $colStart, ($nextComma - $colStart));
$colStart = $nextComma + 1;
- $nextComma = strpos($colString, ',', $colStart);
- }
+ $nextComma = strpos($colString, ',', $colStart);
+ }
}
- //extract final column
+ // Extract final column.
$cols[] = substr($colString, $colStart);
- // We have cols, which may either be of the form 'x', 'table.x' or 'x as y'. Either way the column name is the part after the last
- // space and full stop.
- foreach ($cols as $col)
- {
- // break down by spaces
- $b = explode(' ' , trim($col));
- // break down the part after the last space, by
- $c = explode('.' , array_pop($b));
+ // We have cols, which may either be of the form 'x', 'table.x' or 'x as
+ // y'. Either way the column name is the part after the last space and full
+ // stop.
+ foreach ($cols as $col) {
+ // Break down by spaces.
+ $b = explode(' ', trim($col));
+ // Break down the part after the last space.
+ $c = explode('.', array_pop($b));
$d = array_pop($c);
$this->mergeColumn(trim($d));
}
@@ -1462,34 +1464,41 @@ private function inferFromQuery()
// Okay, now we need to find parameters, which we do with regex.
preg_match_all('/#([a-z0-9_]+)#%/i', $this->query, $matches);
// Here is why I remember (yet again) why I hate PHP...
- foreach ($matches[1] as $param)
- {
+ foreach ($matches[1] as $param) {
$this->mergeParam($param);
}
}
/**
- * Returns the numeric position of the closing bracket matching the opening bracket
- * @param
'
+ )
+ ELSE
+ value
+ END
+ END;
+END
+$BODY$;
\ No newline at end of file
diff --git a/modules/rest_api/controllers/services/rest.php b/modules/rest_api/controllers/services/rest.php
index f016902e03..2c0bc39b2a 100644
--- a/modules/rest_api/controllers/services/rest.php
+++ b/modules/rest_api/controllers/services/rest.php
@@ -457,12 +457,18 @@ class Rest_Controller extends Controller {
'reports/{path}' => [],
'reports/{path}/{file.xml}' => [
'params' => [
+ 'autofeed' => [
+ 'datatype' => 'boolean',
+ ],
'filter_id' => [
'datatype' => 'integer',
],
'limit' => [
'datatype' => 'integer',
],
+ 'max_time' => [
+ 'datatype' => 'integer',
+ ],
'offset' => [
'datatype' => 'integer',
],
@@ -1776,12 +1782,12 @@ private function checkParamDatatype($paramName, &$value, array $paramDef) {
}
}
elseif ($datatype === 'boolean') {
- if (!preg_match('/^(true|false)$/', $trimmed)) {
+ if (!preg_match('/^(true|false|t|f)$/', $trimmed)) {
RestObjects::$apiResponse->fail('Bad request', 400,
"Invalid boolean for $paramName parameter, value should be true or false");
}
// Set the value to a real bool.
- $value = $trimmed === 'true';
+ $value = $trimmed === 'true' || $trimmed === 't';
}
// If a limited options set available then check the value is in the list.
if (!empty($paramDef['options']) && !in_array($trimmed, $paramDef['options'])) {
diff --git a/modules/rest_api/i18n/en_GB/rest_api.php b/modules/rest_api/i18n/en_GB/rest_api.php
index 356bb7eac3..d107d43bf5 100644
--- a/modules/rest_api/i18n/en_GB/rest_api.php
+++ b/modules/rest_api/i18n/en_GB/rest_api.php
@@ -90,6 +90,11 @@
then saved into the current record as a foreign key.
$lang[resourcesIntro]
HTML; $apiRoot = url::base() . 'index.php/services/rest'; @@ -208,6 +222,9 @@ private function indexHtml($resourceConfig) { } $badge = empty($endpointPathOptions['deprecated']) ? '' : ' deprecated'; $endpointOutput .= "' . kohana::lang('rest_api.deprecatedEndpoint') . '
'; + } $endpointOutput .= "Example URL: $apiRoot/" . str_replace( ['{id}', '{path}', '{file.xml}'], ['123', 'library/occurrences', 'filterable_explore_list.xml'], @@ -400,6 +417,7 @@ public function getUrlWithCurrentParams($url) { /** * Echos a successful response in HTML format. + * * @param array $data * @param array $options */ @@ -413,11 +431,11 @@ private function succeedHtml($data, $options) { $this->outputArrayAsHtml($options['metadata']); } - // output an index table if present for this output + // Output an index table if present for this output. if ($this->wantIndex && isset($data['data'])) { echo $this->getIndexAsHtml($data['data']); } - // output the main response body + // Output the main response body. if (isset($options['metadata']) || !empty($this->responseTitle)) { echo '
$col1 | $col2 | "; echo ''; - foreach ($array as $key=>$value) { - if (empty($value) && !$this->includeEmptyValues) + foreach ($array as $key => $value) { + if (empty($value) && !$this->includeEmptyValues) { continue; - // If in a simple list of data or pg output, start preprocessing rows. Other structural output elements are not - // preprocessed. + } + // If in a simple list of data or pg output, start preprocessing rows. + // Other structural output elements are not preprocessed. $options['preprocess'] = is_int($key) || is_object($value); $class = is_array($value) && !empty($value['type']) ? " class=\"type-$value[type]\"" : ''; echo "
---|---|
$key | ";
$options['tableId'] = $key;
- // Object data here means a pg result object. Or, if it is an non-associative array so a simple list, then
- // output as a table rather than a nested structure.
+ // Object data here means a pg result object. Or, if it is an
+ // non-associative array so a simple list, then output as a table
+ // rather than a nested structure.
if (is_object($value) || (is_array($value) && count($value) > 0 && is_int(array_keys($value)[0]))) {
// recurse into pg result data
$this->outputResultAsHtml($value, $options);
}
elseif (is_array($value)) {
- // recurse into plain array data
+ // Recurse into plain array data.
$this->outputArrayAsHtml($value, $options);
}
else {
- // a simple value to output. If it contains an internal link then process it to hide user/secret data.
+ // A simple value to output. If it contains an internal link then
+ // process it to hide user/secret data.
if (preg_match('/^http(s)?:\/\//', $value)) {
$parts = explode('?', $value);
$displayUrl = $parts[0];
- if (count($parts)>1) {
+ if (count($parts) > 1) {
parse_str($parts[1], $params);
unset($params['user']);
unset($params['user_id']);
@@ -536,21 +564,24 @@ private function outputArrayAsHtml($array, $options = array()) {
/**
* Dumps out an HTML table containing results from a PostgreSQL query.
- * @param array $data PG result data to iterate through.
- * @param array $options Options array. If this has a columns element, it is used to generate a header row and control
- * the output.
+ *
+ * @param array $data
+ * PG result data to iterate through.
+ * @param array $options
+ * Options array. If this has a columns element, it is used to generate a
+ * header row and control the output.
*/
- private function outputResultAsHtml($data, $options) {
+ private function outputResultAsHtml(array $data, array $options) {
echo ' |