diff --git a/admin/NoidLib/custom/MysqlArkDB.php b/admin/NoidLib/custom/MysqlArkDB.php
index 916113f..fdcbd54 100644
--- a/admin/NoidLib/custom/MysqlArkDB.php
+++ b/admin/NoidLib/custom/MysqlArkDB.php
@@ -149,6 +149,23 @@ class MysqlArkDB implements DatabaseInterface
return FALSE;
}
+ /**
+ * Query
+ * @param $query
+ * @return false|string
+ */
+ public function query($query) {
+ if (!($this->handle instanceof mysqli)) {
+ return FALSE;
+ }
+ $query = str_replace('', $this->db_name, $query);
+
+ if ($res = $this->handle->query($query)) {
+ return $res->fetch_all(MYSQLI_ASSOC);
+ }
+ return FALSE;
+ }
+
/**
* @param string $key
* @param string $value
diff --git a/admin/admin.php b/admin/admin.php
index 714240e..a682e37 100755
--- a/admin/admin.php
+++ b/admin/admin.php
@@ -132,6 +132,124 @@ $subheader .= "
";
+
+ //
+ // Pipelining function for DataTables. To be used to the `ajax` option of DataTables
+ //
+ $.fn.dataTable.pipeline = function ( opts ) {
+ // Configuration options
+ var conf = $.extend( {
+ pages: 5, // number of pages to cache
+ url: '', // script url
+ data: null, // function or object with parameters to send to the server
+ // matching how `ajax.data` works in DataTables
+ method: 'GET' // Ajax HTTP method
+ }, opts );
+
+ // Private variables for storing the cache
+ var cacheLower = -1;
+ var cacheUpper = null;
+ var cacheLastRequest = null;
+ var cacheLastJson = null;
+
+ return function ( request, drawCallback, settings ) {
+ var ajax = false;
+ var requestStart = request.start;
+ var drawStart = request.start;
+ var requestLength = request.length;
+ var requestEnd = requestStart + requestLength;
+
+ if ( settings.clearCache ) {
+ // API requested that the cache be cleared
+ ajax = true;
+ settings.clearCache = false;
+ }
+ else if ( cacheLower < 0 || requestStart < cacheLower || requestEnd > cacheUpper ) {
+ // outside cached data - need to make a request
+ ajax = true;
+ }
+ else if ( JSON.stringify( request.order ) !== JSON.stringify( cacheLastRequest.order ) ||
+ JSON.stringify( request.columns ) !== JSON.stringify( cacheLastRequest.columns ) ||
+ JSON.stringify( request.search ) !== JSON.stringify( cacheLastRequest.search )
+ ) {
+ // properties changed (ordering, columns, searching)
+ ajax = true;
+ }
+
+ // Store the request for checking next time around
+ cacheLastRequest = $.extend( true, {}, request );
+
+ if ( ajax ) {
+ // Need data from the server
+ if ( requestStart < cacheLower ) {
+ requestStart = requestStart - (requestLength*(conf.pages-1));
+
+ if ( requestStart < 0 ) {
+ requestStart = 0;
+ }
+ }
+
+ cacheLower = requestStart;
+ cacheUpper = requestStart + (requestLength * conf.pages);
+
+ request.start = requestStart;
+ request.length = requestLength*conf.pages;
+
+ // Provide the same `data` options as DataTables.
+ if ( typeof conf.data === 'function' ) {
+ // As a function it is executed with the data object as an arg
+ // for manipulation. If an object is returned, it is used as the
+ // data object to submit
+ var d = conf.data( request );
+ if ( d ) {
+ $.extend( request, d );
+ }
+ }
+ else if ( $.isPlainObject( conf.data ) ) {
+ // As an object, the data given extends the default
+ $.extend( request, conf.data );
+ }
+
+ return $.ajax( {
+ "type": conf.method,
+ "url": conf.url,
+ "data": request,
+ "dataType": "json",
+ "cache": false,
+ "success": function ( json ) {
+ cacheLastJson = $.extend(true, {}, json);
+
+ if ( cacheLower != drawStart ) {
+ json.data.splice( 0, drawStart-cacheLower );
+ }
+ if ( requestLength >= -1 ) {
+ json.data.splice( requestLength, json.data.length );
+ }
+
+ drawCallback( json );
+ }
+ } );
+ }
+ else {
+ json = $.extend( true, {}, cacheLastJson );
+ json.draw = request.draw; // Update the echo for each response
+ json.data.splice( 0, requestStart-cacheLower );
+ json.data.splice( requestLength, json.data.length );
+
+ drawCallback(json);
+ }
+ }
+ };
+
+ // Register an API method that will empty the pipelined data, forcing an Ajax
+ // fetch on the next draw (i.e. `table.clearPipeline().draw()`)
+ $.fn.dataTable.Api.register( 'clearPipeline()', function () {
+ return this.iterator( 'table', function ( settings ) {
+ settings.clearCache = true;
+ } );
+ } );
+
+
$(document).ready(function () {
// To style only selects with the select-ark-id class
@@ -174,10 +292,12 @@ $subheader .= "";
let mintedTable = jQuery('#minted_table').DataTable({
dom: 'lBfrtip',
- "ajax": {
+ "ajax": $.fn.dataTable.pipeline( {
"url": "rest.php?db=",
- "dataSrc": ""
- },
+ "pages": 5 // number of pages to cache
+ }),
+ processing: true,
+ serverSide: true,
columns: [
{data: 'select'},
{data: '_key'},
@@ -264,10 +384,12 @@ $subheader .= "";
// Make a Ajax call to Rest api and render data to table
let boundTable = jQuery('#bound_table').DataTable({
dom: 'lBfrtip',
- "ajax": {
+ "ajax": $.fn.dataTable.pipeline( {
"url": "rest.php?db=",
- "dataSrc": ""
- },
+ "pages": 5 // number of pages to cache
+ }),
+ processing: true,
+ serverSide: true,
"initComplete": function (settings, json) {
$(".collapse").collapse({
toggle: false
@@ -329,7 +451,7 @@ $subheader .= "";
{
"targets": 5,
- "data": "metadata",
+ "data": "metadata",
"render": function (data, type, row) {
if (data !== undefined && data.indexOf("|") != -1) {
var now = row.id;
diff --git a/admin/rest.php b/admin/rest.php
index 24b03d8..de31081 100644
--- a/admin/rest.php
+++ b/admin/rest.php
@@ -278,44 +278,131 @@ function selectBound()
if (!Database::exist($_GET['db'])) {
die(json_encode('Database not found'));
}
- $columns = json_decode(select());
- //array_push($columns, (object)[]);
+
+ $noid = Database::dbopen($_GET["db"], getcwd() . "/db/", DatabaseInterface::DB_WRITE);
+ $firstpart = Database::$engine->get(Globals::_RR . "/firstpart");
+
+ $columnIdx = $_GET['order'][0]['column'];
+ $sortCol = $_GET['columns'][$columnIdx];
+ $sortDir = $_GET['order'][0]['dir'] === 'asc' ? 'ASC' : 'DESC';
+ $offset = $_GET['start'] ?? 0;
+ $limit = $_GET['length'] ?? 50;
+ $search = $_GET['search']['value'];
+
+ if ($sortCol['data'] === 'redirect') {
+ $sql = "SELECT arks.*
+ FROM ``
+ AS arks
+ JOIN (
+ SELECT bound.id,
+ COALESCE(redirected._value, 0)
+ AS _value
+ FROM (
+ SELECT DISTINCT REGEXP_SUBSTR(_key, '^([^\\\\s]+)') AS id
+ FROM
+ WHERE _key LIKE '$firstpart%' AND _key NOT REGEXP '(\\\\s:\\/c|\\\\sREDIRECT|\\\\sPID|\\\\sLOCAL_ID|\\\\sCOLLECTION)$'
+ ) AS bound
+ LEFT JOIN (
+ SELECT REGEXP_SUBSTR(_key, '^([^\\\\s]+)') AS id, _value
+ FROM
+ WHERE _key LIKE '$firstpart%' AND _key REGEXP '\\\\sREDIRECT$'
+ ) AS redirected ON bound.id = redirected.id
+ ORDER BY _value $sortDir
+ LIMIT $limit
+ OFFSET $offset
+ ) AS subquery
+ ON arks._key LIKE CONCAT(subquery.id, '%')
+ AND arks._key NOT LIKE '%:\\/c'
+ ORDER BY arks._key ASC;
+ ";
+ $sql_count = "SELECT COUNT(*) as num_filtered
+ FROM (
+ SELECT bound.id,
+ COALESCE(redirected._value, 0)
+ AS _value
+ FROM (
+ SELECT DISTINCT REGEXP_SUBSTR(_key, '^([^\\\\s]+)') AS id
+ FROM
+ WHERE _key LIKE '$firstpart%' AND _key NOT REGEXP '(\\\\s:\\/c|\\\\sREDIRECT|\\\\sPID|\\\\sLOCAL_ID|\\\\sCOLLECTION)$'
+ ) AS bound
+ LEFT JOIN (
+ SELECT REGEXP_SUBSTR(_key, '^([^\\\\s]+)') AS id, _value
+ FROM
+ WHERE _key LIKE '$firstpart%' AND _key REGEXP '\\\\sREDIRECT$'
+ ) AS redirected ON bound.id = redirected.id
+ ) AS filtered_ids;
+ ";
+ }
+ else { // Sort on Ark IDs
+ $sql = "SELECT arks.*
+ FROM ``
+ AS arks
+ JOIN (
+ SELECT * FROM (
+ SELECT DISTINCT REGEXP_SUBSTR(_key, '^([^\\\\s]+)') AS id
+ FROM ``
+ WHERE _key LIKE '$firstpart%' AND _key NOT REGEXP '(\\\\s:\\/c|\\\\sREDIRECT|\\\\sPID|\\\\sLOCAL_ID|\\\\sCOLLECTION)$'
+ INTERSECT
+ SELECT DISTINCT REGEXP_SUBSTR(_key, '^([^\\\\s]+)') AS id
+ FROM ``
+ WHERE _key LIKE '$firstpart%' AND _key NOT REGEXP '\\\\s:\\/c' AND (_key LIKE '%$search%' OR _value LIKE '%$search%')
+ ) AS target
+ ORDER BY id $sortDir
+ LIMIT $limit
+ OFFSET $offset
+ ) AS subquery
+ ON arks._key LIKE CONCAT(subquery.id, '%')
+ AND arks._key NOT LIKE '%:\\/c'
+ ORDER BY arks._key $sortDir;
+ ";
+ $sql_count = "SELECT COUNT(*) as num_filtered
+ FROM (
+ SELECT DISTINCT REGEXP_SUBSTR(_key, '^([^\\\\s]+)') AS id
+ FROM ``
+ WHERE _key LIKE '$firstpart%' AND _key NOT REGEXP '(\\\\s:\\/c|\\\\sREDIRECT|\\\\sPID|\\\\sLOCAL_ID|\\\\sCOLLECTION)$'
+ INTERSECT
+ SELECT DISTINCT REGEXP_SUBSTR(_key, '^([^\\\\s]+)') AS id
+ FROM ``
+ WHERE _key LIKE '$firstpart%' AND _key NOT REGEXP '\\\\s:\\/c' AND (_key LIKE '%$search%' OR _value LIKE '%$search%')
+ ) AS filtered_ids;
+ ";
+ }
+
+ $rows = Database::$engine->query($sql);
+ $num_filtered = Database::$engine->query($sql_count)[0]['num_filtered'] ?? 0;
+ Database::dbclose($noid);
+
$currentID = null;
$result = array();
$r = [];
- $countColumns = count($columns);
- $index = 1;
- $qualifiers = [];
- foreach ($columns as $column) {
- $column = (array)$column;
+ foreach ($rows as $row) {
+ $row = (array)$row;
- if (isset($column['_key'])) {
- $key_data = preg_split('/\s+/', $column['_key']);
- //print_r($column);
+ if (isset($row['_key'])) {
+ $key_data = preg_split('/\s+/', $row['_key']);
if (!isset($currentID) || ($currentID !== $key_data[0])) {
$currentID = $key_data[0];
if (is_array($r) && count($r) > 0) {
- if(!array_key_exists('PID', $r)) {
- $r['PID'] = " ";
- }
- if(!array_key_exists('LOCAL_ID', $r)) {
- $r['LOCAL_ID'] = " ";
- }
array_push($result, $r);
}
- $r = [];
+ $r = [
+ 'select' => ' ',
+ 'id' => $currentID,
+ 'PID' => ' ',
+ 'LOCAL_ID' => ' ',
+ 'redirect' => 0,
+ ];
}
- $r['select'] = " ";
- $r['id'] = $currentID;
+
if ($key_data[1] == 'PID')
- $r['PID'] = (!empty($column['_value'])) ? $column['_value'] : ' ';
+ $r['PID'] = (!empty($row['_value'])) ? $row['_value'] : ' ';
if ($key_data[1] == "LOCAL_ID")
- $r['LOCAL_ID'] = (!empty($column['_value'])) ? $column['_value'] : ' ';
+ $r['LOCAL_ID'] = (!empty($row['_value'])) ? $row['_value'] : ' ';
if ($key_data[1] == "REDIRECT")
- $r['redirect'] = (!empty($column['_value'])) ? $column['_value'] : ' ';
- $r['metadata'] = (!empty($r['metadata']) ? $r['metadata'] . "|" : "") . $key_data[1] .':' .$column['_value'];
+ $r['redirect'] = (!empty($row['_value'])) ? $row['_value'] : ' ';
+ $r['metadata'] = (!empty($r['metadata']) ? $r['metadata'] . "|" : "") . $key_data[1] .':' .$row['_value'];
// check if server have https://, if not, go with http://
if (empty($_SERVER['HTTPS'])) {
@@ -334,25 +421,31 @@ function selectBound()
$r['ark_url'] = (array_key_exists("ark_url", $r) && is_array($r['ark_url']) && count($r['ark_url']) > 1) ? $r['ark_url'] : [$ark_url];
// if there is qualifier bound to an Ark ID, establish the link the link
- if ($key_data[1] !== "URL" && filter_var($column['_value'], FILTER_VALIDATE_URL)) {
+ if ($key_data[1] !== "URL" && filter_var($row['_value'], FILTER_VALIDATE_URL)) {
array_push($r['ark_url'], strtolower($ark_url. "/". $key_data[1]));
}
-
}
+ }
+
+ if (!empty($r)) {
+ array_push($result, $r);
+ }
- // if the loop reach the last pair of elements (for incompleted bind)
- if ($index === $countColumns) {
- if(!array_key_exists('PID', $r)) {
- $r['PID'] = " ";
- }
- /*if(!array_key_exists('LOCAL_ID', $r)) {
- $r['LOCAL_ID'] = " ";
- }*/
- array_push($result, $r);
- }
- $index++;
+ if ($sortCol['data'] === 'redirect') {
+ $redirect = array_column($result, "redirect");
+ array_multisort($redirect, $sortDir === 'ASC' ? SORT_ASC : SORT_DESC, $result);
+ }
+ else {
+ $id = array_column($result, "id");
+ array_multisort($id, $sortDir === 'ASC' ? SORT_ASC : SORT_DESC, $result);
}
- return json_encode($result);
+
+ return json_encode(array(
+ "data" => $result,
+ "draw" => isset ( $_GET['draw'] ) ? intval( $_GET['draw'] ) : 0,
+ "recordsTotal" => countBoundedArks(),
+ "recordsFiltered" => $num_filtered,
+ ));
}
/**
@@ -423,27 +516,79 @@ function getURL($arkID) {
*/
function getMinted()
{
- GlobalsArk::$db_type = 'ark_mysql';
- if (!Database::exist($_GET['db'])) {
- die(json_encode('Database not found'));
- }
- $noid = Database::dbopen($_GET["db"], getcwd() . "/db/", DatabaseInterface::DB_WRITE);
- $firstpart = Database::$engine->get(Globals::_RR . "/firstpart");
- $result = Database::$engine->select("_key REGEXP '^$firstpart' and _key REGEXP ':/c$'");
- //return json_encode($result);
- $json = array();
- foreach ($result as $row) {
- $urow = array();
- $urow['select']= ' ';
- $urow['_key'] = trim(str_replace(":/c", "", $row['_key']));
+ GlobalsArk::$db_type = 'ark_mysql';
+ if (!Database::exist($_GET['db'])) {
+ die(json_encode('Database not found'));
+ }
+ $noid = Database::dbopen($_GET["db"], getcwd() . "/db/", DatabaseInterface::DB_WRITE);
+ $firstpart = Database::$engine->get(Globals::_RR . "/firstpart");
- $metadata = explode('|', $row['_value']);
- //$urow['_value'] = date("F j, Y, g:i a", $metadata[2]);
- $urow['_value'] = date("F j, Y", $metadata[2]);
- array_push($json, (object)$urow);
- }
- Database::dbclose($noid);
- return json_encode($json);
+ if (isset($_GET['order'][0]['dir'])) {
+ $sortDir = $_GET['order'][0]['dir'] === 'asc' ? 'ASC' : 'DESC';
+ } else {
+ $sortDir = 'ASC';
+ }
+ $offset = $_GET['start'] ?? 0;
+ $limit = $_GET['length'] ?? 50;
+
+ $sql = "SELECT REGEXP_SUBSTR(_key, '^([^\\\\s]+)') AS id, _value
+ FROM ``
+ WHERE _key LIKE '$firstpart%' AND _key REGEXP '\\\\s:\/c$'
+ ORDER BY _key $sortDir
+ LIMIT $limit
+ OFFSET $offset;
+ ";
+
+ $result = Database::$engine->query($sql);
+ Database::dbclose($noid);
+
+ $json = array();
+ foreach ($result as $row) {
+ $urow = array();
+ $urow['select'] = ' ';
+ $urow['_key'] = $row['id'];
+
+ $metadata = explode('|', $row['_value']);
+ //$urow['_value'] = date("F j, Y, g:i a", $metadata[2]);
+ $urow['_value'] = date("F j, Y", $metadata[2]);
+ array_push($json, (object)$urow);
+ }
+
+ $totalArks = countTotalArks();
+ return json_encode(array(
+ "data" => $json,
+ "draw" => isset ( $_GET['draw'] ) ? intval( $_GET['draw'] ) : 0,
+ "recordsTotal" => $totalArks,
+ "recordsFiltered" => $totalArks,
+ ));
+}
+
+function countTotalArks() {
+ GlobalsArk::$db_type = 'ark_mysql';
+ if (!Database::exist($_GET['db'])) {
+ die(json_encode('Database not found'));
+ }
+ $noid = Database::dbopen($_GET["db"], getcwd() . "/db/", DatabaseInterface::DB_WRITE);
+ $firstpart = Database::$engine->get(Globals::_RR . "/firstpart");
+ $result = Database::$engine->query("SELECT COUNT(DISTINCT REGEXP_SUBSTR(_key, '^([^\\\\s]+)')) AS total FROM `` WHERE _key LIKE '$firstpart%' and _key REGEXP '\\\\s:\\/c$';");
+ Database::dbclose($noid);
+ return $result[0]['total'] ?? 0;
+}
+
+function countBoundedArks() {
+ GlobalsArk::$db_type = 'ark_mysql';
+ if (!Database::exist($_GET['db'])) {
+ die(json_encode('Database not found'));
+ }
+ $noid = Database::dbopen($_GET["db"], getcwd() . "/db/", DatabaseInterface::DB_WRITE);
+ $firstpart = Database::$engine->get(Globals::_RR . "/firstpart");
+ $result = Database::$engine->query("SELECT COUNT(
+ DISTINCT REGEXP_SUBSTR(_key, '^([^\\\\s]+)')) AS total
+ FROM ``
+ WHERE _key LIKE '$firstpart%' AND _key NOT REGEXP '(\\\\s:\\/c|\\\\sREDIRECT|\\\\sPID|\\\\sLOCAL_ID|\\\\sCOLLECTION)$';
+ ");
+ Database::dbclose($noid);
+ return $result[0]['total'] ?? 0;
}
/**