1413 lines
45 KiB
PHP
1413 lines
45 KiB
PHP
<?php
|
|
/*
|
|
* Copyright (C) 2024 Xibo Signage Ltd
|
|
*
|
|
* Xibo - Digital Signage - https://xibosignage.com
|
|
*
|
|
* This file is part of Xibo.
|
|
*
|
|
* Xibo is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* any later version.
|
|
*
|
|
* Xibo is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
namespace Xibo\Entity;
|
|
|
|
use Carbon\Carbon;
|
|
use Carbon\Factory;
|
|
use Respect\Validation\Validator as v;
|
|
use Stash\Interfaces\PoolInterface;
|
|
use Xibo\Factory\DataSetColumnFactory;
|
|
use Xibo\Factory\DataSetFactory;
|
|
use Xibo\Factory\PermissionFactory;
|
|
use Xibo\Helper\SanitizerService;
|
|
use Xibo\Service\ConfigServiceInterface;
|
|
use Xibo\Service\DisplayNotifyServiceInterface;
|
|
use Xibo\Service\LogServiceInterface;
|
|
use Xibo\Storage\StorageServiceInterface;
|
|
use Xibo\Support\Exception\ConfigurationException;
|
|
use Xibo\Support\Exception\DuplicateEntityException;
|
|
use Xibo\Support\Exception\GeneralException;
|
|
use Xibo\Support\Exception\InvalidArgumentException;
|
|
use Xibo\Support\Exception\NotFoundException;
|
|
use Xibo\Widget\Definition\Sql;
|
|
|
|
/**
|
|
* Class DataSet
|
|
* @package Xibo\Entity
|
|
*
|
|
* @SWG\Definition()
|
|
*/
|
|
class DataSet implements \JsonSerializable
|
|
{
|
|
use EntityTrait;
|
|
|
|
/**
|
|
* @SWG\Property(description="The dataSetId")
|
|
* @var int
|
|
*/
|
|
public $dataSetId;
|
|
|
|
/**
|
|
* @SWG\Property(description="The dataSet Name")
|
|
* @var string
|
|
*/
|
|
public $dataSet;
|
|
|
|
/**
|
|
* @SWG\Property(description="The dataSet description")
|
|
* @var string
|
|
*/
|
|
public $description;
|
|
|
|
/**
|
|
* @SWG\Property(description="The userId of the User that owns this DataSet")
|
|
* @var int
|
|
*/
|
|
public $userId;
|
|
|
|
/**
|
|
* @SWG\Property(description="Timestamp indicating the date/time this DataSet was edited last")
|
|
* @var int
|
|
*/
|
|
public $lastDataEdit;
|
|
|
|
/**
|
|
* @SWG\Property(description="The user name of the User that owns this DataSet")
|
|
* @var string
|
|
*/
|
|
public $owner;
|
|
|
|
/**
|
|
* @SWG\Property(description="A comma separated list of Groups/Users that have permission to this DataSet")
|
|
* @var string
|
|
*/
|
|
public $groupsWithPermissions;
|
|
|
|
/**
|
|
* @SWG\Property(description="A code for this Data Set")
|
|
* @var string
|
|
*/
|
|
public $code;
|
|
|
|
/**
|
|
* @SWG\Property(description="Flag to indicate whether this DataSet is a lookup table")
|
|
* @var int
|
|
*/
|
|
public $isLookup = 0;
|
|
|
|
/**
|
|
* @SWG\Property(description="Flag to indicate whether this DataSet is Remote")
|
|
* @var int
|
|
*/
|
|
public $isRemote = 0;
|
|
|
|
/**
|
|
* @SWG\Property(description="Flag to indicate whether this DataSet is Real time")
|
|
* @var int
|
|
*/
|
|
public $isRealTime = 0;
|
|
|
|
/**
|
|
* @SWG\Property(description="Indicates the source of the data connector. Requires the Real time flag. Can be null,
|
|
* user-defined, or a connector.")
|
|
* @var string
|
|
*/
|
|
public $dataConnectorSource;
|
|
|
|
/**
|
|
* @SWG\Property(description="Method to fetch the Data, can be GET or POST")
|
|
* @var string
|
|
*/
|
|
public $method;
|
|
|
|
/**
|
|
* @SWG\Property(description="URI to call to fetch Data from. Replacements are {{DATE}}, {{TIME}} and, in case this is a sequencial used DataSet, {{COL.NAME}} where NAME is a ColumnName from the underlying DataSet.")
|
|
* @var string
|
|
*/
|
|
public $uri;
|
|
|
|
/**
|
|
* @SWG\Property(description="Data to send as POST-Data to the remote host with the same Replacements as in the URI.")
|
|
* @var string
|
|
*/
|
|
public $postData;
|
|
|
|
/**
|
|
* @SWG\Property(description="Authentication method, can be none, digest, basic")
|
|
* @var string
|
|
*/
|
|
public $authentication;
|
|
|
|
/**
|
|
* @SWG\Property(description="Username to authenticate with")
|
|
* @var string
|
|
*/
|
|
public $username;
|
|
|
|
/**
|
|
* @SWG\Property(description="Corresponding password")
|
|
* @var string
|
|
*/
|
|
public $password;
|
|
|
|
/**
|
|
* @SWG\Property(description="Comma separated string of custom HTTP headers")
|
|
* @var string
|
|
*/
|
|
public $customHeaders;
|
|
|
|
/**
|
|
* @SWG\Property(description="Custom User agent")
|
|
* @var string
|
|
*/
|
|
public $userAgent;
|
|
|
|
/**
|
|
* @SWG\Property(description="Time in seconds this DataSet should fetch new Datas from the remote host")
|
|
* @var int
|
|
*/
|
|
public $refreshRate;
|
|
|
|
/**
|
|
* @SWG\Property(description="Time in seconds when this Dataset should be cleared. If here is a lower value than in RefreshRate it will be cleared when the data is refreshed")
|
|
* @var int
|
|
*/
|
|
public $clearRate;
|
|
|
|
/**
|
|
* @SWG\Property(description="Flag whether to truncate DataSet data if no new data is pulled from remote source")
|
|
* @var int
|
|
*/
|
|
public $truncateOnEmpty;
|
|
|
|
/**
|
|
* @SWG\Property(description="DataSetID of the DataSet which should be fetched and present before the Data from this DataSet are fetched")
|
|
* @var int
|
|
*/
|
|
public $runsAfter;
|
|
|
|
/**
|
|
* @SWG\Property(description="Last Synchronisation Timestamp")
|
|
* @var int
|
|
*/
|
|
public $lastSync = 0;
|
|
|
|
/**
|
|
* @SWG\Property(description="Last Clear Timestamp")
|
|
* @var int
|
|
*/
|
|
public $lastClear = 0;
|
|
|
|
/**
|
|
* @SWG\Property(description="Root-Element form JSON where the data are stored in")
|
|
* @var String
|
|
*/
|
|
public $dataRoot;
|
|
|
|
/**
|
|
* @SWG\Property(description="Optional function to use for summarize or count unique fields in a remote request")
|
|
* @var String
|
|
*/
|
|
public $summarize;
|
|
|
|
/**
|
|
* @SWG\Property(description="JSON-Element below the Root-Element on which the consolidation should be applied on")
|
|
* @var String
|
|
*/
|
|
public $summarizeField;
|
|
|
|
/**
|
|
* @SWG\Property(description="The source id for remote dataSet, 1 - JSON, 2 - CSV")
|
|
* @var integer
|
|
*/
|
|
public $sourceId;
|
|
|
|
/**
|
|
* @SWG\Property(description="A flag whether to ignore the first row, for CSV source remote dataSet")
|
|
* @var integer
|
|
*/
|
|
public $ignoreFirstRow;
|
|
|
|
/**
|
|
* @SWG\Property(description="Soft limit on number of rows per DataSet, if left empty the global DataSet row limit will be used.")
|
|
* @var integer
|
|
*/
|
|
public $rowLimit = null;
|
|
|
|
/**
|
|
* @SWG\Property(description="Type of action that should be taken on next remote DataSet sync - stop, fifo or truncate")
|
|
* @var string
|
|
*/
|
|
public $limitPolicy;
|
|
|
|
/**
|
|
* @SWG\Property(description="Custom separator for CSV source, comma will be used by default")
|
|
* @var string
|
|
*/
|
|
public $csvSeparator;
|
|
|
|
/**
|
|
* @SWG\Property(description="The id of the Folder this DataSet belongs to")
|
|
* @var int
|
|
*/
|
|
public $folderId;
|
|
|
|
/**
|
|
* @SWG\Property(description="The id of the Folder responsible for providing permissions for this DataSet")
|
|
* @var int
|
|
*/
|
|
public $permissionsFolderId;
|
|
|
|
/** @var array Permissions */
|
|
private $permissions = [];
|
|
|
|
/**
|
|
* @var DataSetColumn[]
|
|
*/
|
|
public $columns = [];
|
|
|
|
private $countLast = 0;
|
|
|
|
/** @var \Xibo\Helper\SanitizerService */
|
|
private $sanitizerService;
|
|
|
|
/** @var ConfigServiceInterface */
|
|
private $config;
|
|
|
|
/** @var PoolInterface */
|
|
private $pool;
|
|
|
|
/** @var DataSetFactory */
|
|
private $dataSetFactory;
|
|
|
|
/** @var DataSetColumnFactory */
|
|
private $dataSetColumnFactory;
|
|
|
|
/** @var PermissionFactory */
|
|
private $permissionFactory;
|
|
|
|
/** @var DisplayNotifyServiceInterface */
|
|
private $displayNotifyService;
|
|
|
|
/**
|
|
* Entity constructor.
|
|
* @param StorageServiceInterface $store
|
|
* @param LogServiceInterface $log
|
|
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
|
|
* @param SanitizerService $sanitizerService
|
|
* @param ConfigServiceInterface $config
|
|
* @param PoolInterface $pool
|
|
* @param DataSetFactory $dataSetFactory
|
|
* @param DataSetColumnFactory $dataSetColumnFactory
|
|
* @param PermissionFactory $permissionFactory
|
|
* @param DisplayNotifyServiceInterface $displayNotifyService
|
|
*/
|
|
public function __construct($store, $log, $dispatcher, $sanitizerService, $config, $pool, $dataSetFactory, $dataSetColumnFactory, $permissionFactory, $displayNotifyService)
|
|
{
|
|
$this->setCommonDependencies($store, $log, $dispatcher);
|
|
$this->sanitizerService = $sanitizerService;
|
|
$this->config = $config;
|
|
$this->pool = $pool;
|
|
$this->dataSetFactory = $dataSetFactory;
|
|
$this->dataSetColumnFactory = $dataSetColumnFactory;
|
|
$this->permissionFactory = $permissionFactory;
|
|
$this->displayNotifyService = $displayNotifyService;
|
|
}
|
|
|
|
/**
|
|
* @param $array
|
|
* @return \Xibo\Support\Sanitizer\SanitizerInterface
|
|
*/
|
|
protected function getSanitizer($array)
|
|
{
|
|
return $this->sanitizerService->getSanitizer($array);
|
|
}
|
|
|
|
/**
|
|
* Clone
|
|
*/
|
|
public function __clone()
|
|
{
|
|
$this->dataSetId = null;
|
|
|
|
$this->columns = array_map(function ($object) { return clone $object; }, $this->columns);
|
|
}
|
|
|
|
/**
|
|
* @return int
|
|
*/
|
|
public function getId()
|
|
{
|
|
return $this->dataSetId;
|
|
}
|
|
|
|
public function getPermissionFolderId()
|
|
{
|
|
return $this->permissionsFolderId;
|
|
}
|
|
|
|
/**
|
|
* @return int
|
|
*/
|
|
public function getOwnerId()
|
|
{
|
|
return $this->userId;
|
|
}
|
|
|
|
/**
|
|
* Set the owner of this DataSet
|
|
* @param $userId
|
|
*/
|
|
public function setOwner($userId)
|
|
{
|
|
$this->userId = $userId;
|
|
}
|
|
|
|
/**
|
|
* Get the Count of Records in the last getData()
|
|
* @return int
|
|
*/
|
|
public function countLast()
|
|
{
|
|
return $this->countLast;
|
|
}
|
|
|
|
/**
|
|
* Get the Display Notify Service
|
|
* @return DisplayNotifyServiceInterface
|
|
*/
|
|
public function getDisplayNotifyService(): DisplayNotifyServiceInterface
|
|
{
|
|
return $this->displayNotifyService->init();
|
|
}
|
|
|
|
/**
|
|
* Get Column
|
|
* @param int[Optional] $dataSetColumnId
|
|
* @return DataSetColumn[]|DataSetColumn
|
|
* @throws NotFoundException when the heading is provided and the column cannot be found
|
|
*/
|
|
public function getColumn($dataSetColumnId = 0)
|
|
{
|
|
$this->load();
|
|
|
|
if ($dataSetColumnId != 0) {
|
|
|
|
foreach ($this->columns as $column) {
|
|
/* @var DataSetColumn $column */
|
|
if ($column->dataSetColumnId == $dataSetColumnId)
|
|
return $column;
|
|
}
|
|
|
|
throw new NotFoundException(sprintf(__('Column %s not found'), $dataSetColumnId));
|
|
|
|
} else {
|
|
return $this->columns;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get Column
|
|
* @param string $dataSetColumn
|
|
* @return DataSetColumn[]|DataSetColumn
|
|
* @throws NotFoundException when the heading is provided and the column cannot be found
|
|
*/
|
|
public function getColumnByName($dataSetColumn)
|
|
{
|
|
$this->load();
|
|
|
|
foreach ($this->columns as $column) {
|
|
/* @var DataSetColumn $column */
|
|
if ($column->heading == $dataSetColumn)
|
|
return $column;
|
|
}
|
|
|
|
throw new NotFoundException(sprintf(__('Column %s not found'), $dataSetColumn));
|
|
}
|
|
|
|
/**
|
|
* @param string[] $columns Column Names to select
|
|
* @return array
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
public function getUniqueColumnValues($columns)
|
|
{
|
|
$this->load();
|
|
|
|
$select = '';
|
|
foreach ($columns as $heading) {
|
|
// Check this exists
|
|
$found = false;
|
|
foreach ($this->columns as $column) {
|
|
if ($column->heading == $heading) {
|
|
// Formula column?
|
|
if ($column->dataSetColumnTypeId == 2) {
|
|
$select .= str_replace(
|
|
Sql::DISALLOWED_KEYWORDS,
|
|
'',
|
|
htmlspecialchars_decode($column->formula, ENT_QUOTES)
|
|
) . ' AS `' . $column->heading . '`,';
|
|
} else {
|
|
$select .= '`' . $column->heading . '`,';
|
|
}
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$found) {
|
|
throw new InvalidArgumentException(__('Unknown Column ' . $heading));
|
|
}
|
|
}
|
|
$select = rtrim($select, ',');
|
|
// $select is safe
|
|
|
|
return $this->getStore()->select('SELECT DISTINCT ' . $select . ' FROM `dataset_' . $this->dataSetId . '`', []);
|
|
}
|
|
|
|
/**
|
|
* Get DataSet Data
|
|
* @param array $filterBy
|
|
* @param array $options
|
|
* @param array $extraParams Extra params to apply to the final query
|
|
* @return array
|
|
* @throws NotFoundException
|
|
*/
|
|
public function getData($filterBy = [], $options = [], $extraParams = [])
|
|
{
|
|
$sanitizer = $this->getSanitizer($filterBy);
|
|
|
|
$start = $sanitizer->getInt('start', ['default' => 0]);
|
|
$size = $sanitizer->getInt('size', ['default' => 0]);
|
|
$filter = $filterBy['filter'] ?? '';
|
|
$ordering = $sanitizer->getString('order');
|
|
$displayId = $sanitizer->getInt('displayId', ['default' => 0]);
|
|
|
|
$options = array_merge([
|
|
'includeFormulaColumns' => true,
|
|
'requireTotal' => true,
|
|
'connection' => 'default'
|
|
], $options);
|
|
|
|
// Params (start from extraParams supplied)
|
|
$params = $extraParams;
|
|
|
|
// Fetch display tag value/s
|
|
if ($filter != '' && $displayId != 0) {
|
|
// Define the regular expression to match [Tag:...]
|
|
$pattern = '/\[Tag:[^]]+\]/';
|
|
|
|
// Find all instances of [Tag:...]
|
|
preg_match_all($pattern, $filter, $matches);
|
|
|
|
// Check if matches were found
|
|
if (!empty($matches[0])) {
|
|
$displayTags = [];
|
|
|
|
// Iterate through the matches and process each tag
|
|
foreach ($matches[0] as $tagString) {
|
|
// Remove the enclosing [Tag:] brackets
|
|
$tagContent = substr($tagString, 5, -1);
|
|
|
|
// Explode the tag content by ":" to separate tagName and defaultValue (if present)
|
|
$parts = explode(':', $tagContent);
|
|
$tagName = $parts[0];
|
|
$defaultTagValue = $parts[1] ?? '';
|
|
|
|
$displayTags[] = [
|
|
'tagString' => $tagString,
|
|
'tagName' => $tagName,
|
|
'defaultValue' => $defaultTagValue
|
|
];
|
|
}
|
|
|
|
$tagCount = 1;
|
|
|
|
// Loop through each tag and get the actual tag value from the database
|
|
foreach ($displayTags as $tag) {
|
|
$tagSanitizer = $this->getSanitizer($tag);
|
|
|
|
$tagName = $tagSanitizer->getString('tagName');
|
|
$defaultTagValue = $tagSanitizer->getString('defaultValue');
|
|
$tagString = $tag['tagString'];
|
|
|
|
$query = 'SELECT `lktagdisplaygroup`.`value` AS tagValue
|
|
FROM `lkdisplaydg`
|
|
INNER JOIN `displaygroup`
|
|
ON `displaygroup`.displayGroupId = `lkdisplaydg`.displayGroupId
|
|
AND `displaygroup`.isDisplaySpecific = 1
|
|
INNER JOIN `lktagdisplaygroup`
|
|
ON `lktagdisplaygroup`.displayGroupId = `lkdisplaydg`.displayGroupId
|
|
INNER JOIN `tag` ON `lktagdisplaygroup`.tagId = `tag`.tagId
|
|
WHERE `lkdisplaydg`.displayId = :displayId
|
|
AND `tag`.`tag` = :tagName
|
|
LIMIT 1';
|
|
|
|
$tagParams = [
|
|
'displayId' => $displayId,
|
|
'tagName' => $tagName
|
|
];
|
|
|
|
// Execute the query
|
|
$results = $this->getStore()->select($query, $tagParams);
|
|
|
|
// Determine the tag value
|
|
if (!empty($results)) {
|
|
$tagValue = !empty($results[0]['tagValue']) ? $results[0]['tagValue'] : '';
|
|
} else {
|
|
// Use default tag value if no tag is found
|
|
$tagValue = $defaultTagValue;
|
|
}
|
|
|
|
// Replace the tag string in the filter with the actual tag value or default value
|
|
$filter = str_replace($tagString, ':tagValue_'.$tagCount, $filter);
|
|
$params['tagValue_'.$tagCount] = $tagValue;
|
|
|
|
$tagCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sanitize the filter options provided
|
|
// Get the Latitude and Longitude ( might be used in a formula )
|
|
if ($displayId == 0) {
|
|
$displayGeoLocation =
|
|
"ST_GEOMFROMTEXT('POINT(" . $this->config->getSetting('DEFAULT_LAT') .
|
|
' ' . $this->config->getSetting('DEFAULT_LONG') . ")')";
|
|
} else {
|
|
$displayGeoLocation = '(SELECT GeoLocation FROM `display` WHERE DisplayID =' . $displayId. ')';
|
|
}
|
|
|
|
// Build a SQL statement, based on the columns for this dataset
|
|
$this->load();
|
|
|
|
$select = 'SELECT * FROM ( ';
|
|
$body = 'SELECT id';
|
|
|
|
// Keep track of the columns we are allowed to order by
|
|
$allowedOrderCols = ['id'];
|
|
|
|
// Are there any client side formulas
|
|
$clientSideFormula = [];
|
|
|
|
// Select (columns)
|
|
foreach ($this->getColumn() as $column) {
|
|
/* @var DataSetColumn $column */
|
|
if ($column->dataSetColumnTypeId == 2 && !$options['includeFormulaColumns']) {
|
|
continue;
|
|
}
|
|
|
|
// Formula column?
|
|
if ($column->dataSetColumnTypeId == 2) {
|
|
// Is this a client side column?
|
|
if (str_starts_with($column->formula, '$')) {
|
|
$clientSideFormula[] = $column;
|
|
continue;
|
|
}
|
|
|
|
$count = 0;
|
|
$formula = str_ireplace(
|
|
Sql::DISALLOWED_KEYWORDS,
|
|
'',
|
|
htmlspecialchars_decode($column->formula, ENT_QUOTES),
|
|
$count
|
|
);
|
|
|
|
if ($count > 0) {
|
|
$this->getLog()->error(
|
|
'Formula contains disallowed keywords on DataSet ID ' . $this->dataSetId
|
|
);
|
|
continue;
|
|
}
|
|
|
|
$formula = str_replace('[DisplayId]', $displayId, $formula);
|
|
|
|
$heading = str_replace('[DisplayGeoLocation]', $displayGeoLocation, $formula)
|
|
. ' AS `' . $column->heading . '`';
|
|
} else {
|
|
$heading = '`' . $column->heading . '`';
|
|
}
|
|
|
|
$allowedOrderCols[] = $column->heading;
|
|
|
|
$body .= ', ' . $heading;
|
|
}
|
|
|
|
$body .= ' FROM `dataset_' . $this->dataSetId . '`) dataset WHERE 1 = 1 ';
|
|
|
|
// Filtering
|
|
if ($filter != '') {
|
|
// Support display filtering.
|
|
$filter = str_replace('[DisplayId]', $displayId, $filter);
|
|
$filter = str_ireplace(Sql::DISALLOWED_KEYWORDS, '', $filter);
|
|
|
|
$body .= ' AND ' . $filter;
|
|
}
|
|
|
|
// Filter by ID
|
|
if ($sanitizer->getInt('id') !== null) {
|
|
$body .= ' AND id = :id ';
|
|
$params['id'] = $sanitizer->getInt('id');
|
|
}
|
|
|
|
// Ordering
|
|
$order = '';
|
|
if ($ordering != '') {
|
|
$order = ' ORDER BY ';
|
|
|
|
$ordering = explode(',', $ordering);
|
|
|
|
foreach ($ordering as $orderPair) {
|
|
// Sanitize the clause
|
|
$sanitized = str_replace('`', '', str_replace(' ASC', '', str_replace(' DESC', '', $orderPair)));
|
|
|
|
// Check allowable
|
|
if (!in_array($sanitized, $allowedOrderCols)) {
|
|
$found = false;
|
|
$this->getLog()->info('Potentially disallowed column: ' . $sanitized);
|
|
// the gridRenderSort will strip spaces on column names go through allowed order columns
|
|
// and see if we can find a match by stripping spaces from the heading
|
|
foreach ($allowedOrderCols as $allowedOrderCol) {
|
|
$this->getLog()->info('Checking spaces in original name : ' . $sanitized);
|
|
if (str_replace(' ', '', $allowedOrderCol) === $sanitized) {
|
|
$found = true;
|
|
// put the column heading with the space as sanitized to make sql happy.
|
|
$sanitized = $allowedOrderCol;
|
|
}
|
|
}
|
|
|
|
// we tried, but it was not found, omit this pair
|
|
if (!$found) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Substitute
|
|
if (strripos($orderPair, ' DESC')) {
|
|
$order .= sprintf(' `%s` DESC,', $sanitized);
|
|
} else if (strripos($orderPair, ' ASC')) {
|
|
$order .= sprintf(' `%s` ASC,', $sanitized);
|
|
} else {
|
|
$order .= sprintf(' `%s`,', $sanitized);
|
|
}
|
|
}
|
|
|
|
$order = trim($order, ',');
|
|
|
|
// if after all that we still do not have any column name to order by, default to order by id
|
|
if (trim($order) === 'ORDER BY') {
|
|
$order = ' ORDER BY id ';
|
|
}
|
|
} else {
|
|
$order = ' ORDER BY id ';
|
|
}
|
|
|
|
// Limit
|
|
$limit = '';
|
|
if ($start != 0 || $size != 0) {
|
|
// Substitute in
|
|
|
|
// handle case where lower limit is set to > 0 and upper limit to 0 https://github.com/xibosignage/xibo/issues/2187
|
|
// it is with <= 0 because in some Widgets we calculate the size as upper - lower, https://github.com/xibosignage/xibo/issues/2263.
|
|
if ($start != 0 && $size <= 0) {
|
|
$size = PHP_INT_MAX;
|
|
}
|
|
|
|
$limit = sprintf(' LIMIT %d, %d ', $start, $size);
|
|
}
|
|
|
|
$sql = $select . $body . $order . $limit;
|
|
|
|
$data = $this->getStore()->select($sql, $params, $options['connection']);
|
|
|
|
// If there are limits run some SQL to work out the full payload of rows
|
|
if ($options['requireTotal']) {
|
|
$results = $this->getStore()->select(
|
|
'SELECT COUNT(*) AS total FROM (' . $body,
|
|
$params,
|
|
$options['connection']
|
|
);
|
|
$this->countLast = intval($results[0]['total']);
|
|
}
|
|
|
|
// Are there any client side formulas?
|
|
if (count($clientSideFormula) > 0) {
|
|
$renderedData = [];
|
|
foreach ($data as $item) {
|
|
foreach ($clientSideFormula as $column) {
|
|
// Run the formula and add the resulting value to the list
|
|
$value = null;
|
|
try {
|
|
if (substr($column->formula, 0, strlen('$dateFormat(')) === '$dateFormat(') {
|
|
// Pull out the column name and date format
|
|
$details = explode(',', str_replace(')', '', str_replace('$dateFormat(', '', $column->formula)));
|
|
|
|
if (isset($details[2])) {
|
|
$language = str_replace(' ', '', $details[2]);
|
|
} else {
|
|
$language = $this->config->getSetting('DEFAULT_LANGUAGE', 'en_GB');
|
|
}
|
|
|
|
$carbonFactory = new Factory(['locale' => $language], Carbon::class);
|
|
$value = $carbonFactory->parse($item[$details[0]])->translatedFormat($details[1]);
|
|
}
|
|
} catch (\Exception $e) {
|
|
$this->getLog()->error('DataSet client side formula error in dataSetId ' . $this->dataSetId . ' with column formula ' . $column->formula);
|
|
}
|
|
|
|
$item[$column->heading] = $value;
|
|
}
|
|
|
|
$renderedData[] = $item;
|
|
}
|
|
} else {
|
|
$renderedData = $data;
|
|
}
|
|
|
|
return $renderedData;
|
|
}
|
|
|
|
/**
|
|
* Assign a column
|
|
* @param DataSetColumn $column
|
|
*/
|
|
public function assignColumn($column)
|
|
{
|
|
$this->load();
|
|
|
|
// Set the dataSetId
|
|
$column->dataSetId = $this->dataSetId;
|
|
|
|
// Set the column order if we need to
|
|
if ($column->columnOrder == 0)
|
|
$column->columnOrder = count($this->columns) + 1;
|
|
|
|
$this->columns[] = $column;
|
|
}
|
|
|
|
/**
|
|
* Has Data?
|
|
* @return bool
|
|
*/
|
|
public function hasData()
|
|
{
|
|
return $this->getStore()->exists('SELECT id FROM `dataset_' . $this->dataSetId . '` LIMIT 1', [], 'isolated');
|
|
}
|
|
|
|
/**
|
|
* Returns a Timestamp for the next Synchronisation process.
|
|
* @return int Seconds
|
|
*/
|
|
public function getNextSyncTime()
|
|
{
|
|
return $this->lastSync + $this->refreshRate;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function isTruncateEnabled()
|
|
{
|
|
return $this->clearRate !== 0;
|
|
}
|
|
|
|
/**
|
|
* Returns a Timestamp for the next Clearing process.
|
|
* @return int Seconds
|
|
*/
|
|
public function getNextClearTime()
|
|
{
|
|
return $this->lastClear + $this->clearRate;
|
|
}
|
|
|
|
/**
|
|
* Returns if there is a consolidation field and method present or not.
|
|
* @return boolean
|
|
*/
|
|
public function doConsolidate()
|
|
{
|
|
return ($this->summarizeField != null) && ($this->summarizeField != '')
|
|
&& ($this->summarize != null) && ($this->summarize != '');
|
|
}
|
|
|
|
/**
|
|
* Returns the last Part of the Fieldname on which the consolidation should be applied on
|
|
* @return String
|
|
*/
|
|
public function getConsolidationField()
|
|
{
|
|
$pos = strrpos($this->summarizeField, '.');
|
|
if ($pos !== false) {
|
|
return substr($this->summarizeField, $pos + 1);
|
|
}
|
|
return $this->summarizeField;
|
|
}
|
|
|
|
/**
|
|
* Tests if this DataSet contains parameters for getting values on the dependant DataSet
|
|
* @return boolean
|
|
*/
|
|
public function containsDependantFieldsInRequest()
|
|
{
|
|
return strpos($this->postData, '{{COL.') !== false || strpos($this->uri, '{{COL.') !== false;
|
|
}
|
|
|
|
/**
|
|
* Validate
|
|
* @throws InvalidArgumentException
|
|
* @throws DuplicateEntityException
|
|
*/
|
|
public function validate()
|
|
{
|
|
if (!v::stringType()->notEmpty()->length(null, 50)->validate($this->dataSet)) {
|
|
throw new InvalidArgumentException(__('Name must be between 1 and 50 characters'), 'dataSet');
|
|
}
|
|
|
|
if ($this->description != null && !v::stringType()->length(null, 254)->validate($this->description)) {
|
|
throw new InvalidArgumentException(__('Description can not be longer than 254 characters'), 'description');
|
|
}
|
|
|
|
// If we are a remote dataset do some additional checks
|
|
if ($this->isRemote === 1) {
|
|
if (!v::stringType()->notEmpty()->validate($this->uri)) {
|
|
throw new InvalidArgumentException(__('A remote DataSet must have a URI.'), 'uri');
|
|
}
|
|
|
|
if ($this->rowLimit > $this->config->getSetting('DATASET_HARD_ROW_LIMIT')) {
|
|
throw new InvalidArgumentException(__('DataSet row limit cannot be larger than the CMS dataSet row limit'));
|
|
}
|
|
|
|
// Check if the length is within the current URI character limit
|
|
if (!v::stringType()->length(null, 250)->validate($this->uri)) {
|
|
throw new InvalidArgumentException(__('URI can not be longer than 250 characters'), 'uri');
|
|
}
|
|
}
|
|
|
|
// Does this dataset have existing columns?
|
|
// Additional pre-checking for column headings when copying existing datasets
|
|
if (!empty($this->columns)) {
|
|
foreach ($this->columns as $column) {
|
|
if (!v::stringType()->notEmpty()->noWhitespace()->validate($column->heading)) {
|
|
throw new InvalidArgumentException(__(
|
|
'Cannot copy this Dataset due to invalid column headings. Please remove any spaces.'),
|
|
'dataSet'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
$existing = $this->dataSetFactory->getByName($this->dataSet, $this->userId);
|
|
|
|
if ($this->dataSetId == 0 || $this->dataSetId != $existing->dataSetId) {
|
|
throw new DuplicateEntityException(sprintf(__('There is already dataSet called %s. Please choose another name.'), $this->dataSet));
|
|
}
|
|
}
|
|
catch (NotFoundException $e) {
|
|
// This is good
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load all known information
|
|
*/
|
|
public function load()
|
|
{
|
|
if ($this->loaded || $this->dataSetId == 0)
|
|
return;
|
|
|
|
// Load Columns
|
|
$this->columns = $this->dataSetColumnFactory->getByDataSetId($this->dataSetId);
|
|
|
|
// Load Permissions
|
|
$this->permissions = $this->permissionFactory->getByObjectId(get_class($this), $this->getId());
|
|
|
|
$this->loaded = true;
|
|
}
|
|
|
|
/**
|
|
* Save this DataSet
|
|
* @param array $options
|
|
* @throws InvalidArgumentException
|
|
* @throws DuplicateEntityException
|
|
*/
|
|
public function save($options = [])
|
|
{
|
|
$options = array_merge([
|
|
'validate' => true,
|
|
'saveColumns' => true,
|
|
'activate' => true,
|
|
'notify' => true,
|
|
], $options);
|
|
|
|
if ($options['validate']) {
|
|
$this->validate();
|
|
}
|
|
|
|
if ($this->dataSetId == 0) {
|
|
$this->add();
|
|
} else {
|
|
$this->edit();
|
|
}
|
|
|
|
// Columns
|
|
if ($options['saveColumns']) {
|
|
foreach ($this->columns as $column) {
|
|
$column->dataSetId = $this->dataSetId;
|
|
$column->save($options);
|
|
}
|
|
}
|
|
|
|
// We've been touched
|
|
if ($options['activate']) {
|
|
$this->setActive();
|
|
}
|
|
|
|
// Notify Displays?
|
|
if ($options['notify']) {
|
|
$this->notify();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param int $time
|
|
* @return $this
|
|
*/
|
|
public function saveLastSync($time)
|
|
{
|
|
$this->lastSync = $time;
|
|
|
|
$this->getStore()->update('UPDATE `dataset` SET lastSync = :lastSync WHERE dataSetId = :dataSetId', [
|
|
'dataSetId' => $this->dataSetId,
|
|
'lastSync' => $this->lastSync
|
|
]);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param int $time
|
|
* @return $this
|
|
*/
|
|
public function saveLastClear($time)
|
|
{
|
|
$this->lastSync = $time;
|
|
|
|
$this->getStore()->update('UPDATE `dataset` SET lastClear = :lastClear WHERE dataSetId = :dataSetId', [
|
|
'dataSetId' => $this->dataSetId,
|
|
'lastClear' => $this->lastClear
|
|
]);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Is this DataSet active currently
|
|
* @return bool
|
|
*/
|
|
public function isActive()
|
|
{
|
|
$cache = $this->pool->getItem('/dataset/accessed/' . $this->dataSetId);
|
|
return $cache->isHit();
|
|
}
|
|
|
|
/**
|
|
* Indicate that this DataSet has been accessed recently
|
|
* @return $this
|
|
*/
|
|
public function setActive()
|
|
{
|
|
$this->getLog()->debug('Setting ' . $this->dataSetId . ' as active');
|
|
|
|
$cache = $this->pool->getItem('/dataset/accessed/' . $this->dataSetId);
|
|
$cache->set('true');
|
|
$cache->expiresAfter(intval($this->config->getSetting('REQUIRED_FILES_LOOKAHEAD')) * 1.5);
|
|
$this->pool->saveDeferred($cache);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Delete DataSet
|
|
* @throws ConfigurationException
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
public function delete()
|
|
{
|
|
$this->load();
|
|
|
|
if ($this->isLookup) {
|
|
throw new ConfigurationException(__('Lookup Tables cannot be deleted'));
|
|
}
|
|
|
|
// check if any other DataSet depends on this DataSet
|
|
if ($this->getStore()->exists(
|
|
'SELECT dataSetId FROM dataset WHERE runsAfter = :runsAfter AND dataSetId <> :dataSetId',
|
|
[
|
|
'runsAfter' => $this->dataSetId,
|
|
'dataSetId' => $this->dataSetId
|
|
])) {
|
|
throw new InvalidArgumentException(__('Cannot delete because this DataSet is set as dependent DataSet for another DataSet'), 'dataSetId');
|
|
}
|
|
|
|
// Make sure we're able to delete
|
|
if ($this->getStore()->exists('
|
|
SELECT widgetId
|
|
FROM `widgetoption`
|
|
WHERE `widgetoption`.type = \'attrib\'
|
|
AND `widgetoption`.option = \'dataSetId\'
|
|
AND `widgetoption`.value = :dataSetId
|
|
', ['dataSetId' => $this->dataSetId])) {
|
|
throw new InvalidArgumentException(__('Cannot delete because DataSet is in use on one or more Layouts.'), 'dataSetId');
|
|
}
|
|
|
|
if ($this->getStore()->exists('
|
|
SELECT `eventId`
|
|
FROM `schedule`
|
|
WHERE `dataSetId` = :dataSetId
|
|
', ['dataSetId' => $this->dataSetId])) {
|
|
throw new InvalidArgumentException(
|
|
__('Cannot delete because DataSet is in use on one or more Data Connector schedules.'),
|
|
'dataSetId'
|
|
);
|
|
}
|
|
|
|
// Delete Permissions
|
|
foreach ($this->permissions as $permission) {
|
|
/* @var Permission $permission */
|
|
$permission->deleteAll();
|
|
}
|
|
|
|
// Delete Columns
|
|
foreach ($this->columns as $column) {
|
|
$column->delete(true);
|
|
}
|
|
|
|
// Delete any dataSet rss
|
|
$this->getStore()->update('DELETE FROM `datasetrss` WHERE dataSetId = :dataSetId', ['dataSetId' => $this->dataSetId]);
|
|
|
|
// Delete the data set
|
|
$this->getStore()->update('DELETE FROM `dataset` WHERE dataSetId = :dataSetId', ['dataSetId' => $this->dataSetId]);
|
|
|
|
// The last thing we do is drop the dataSet table
|
|
$this->dropTable();
|
|
}
|
|
|
|
/**
|
|
* Delete all data
|
|
*/
|
|
public function deleteData()
|
|
{
|
|
// The last thing we do is drop the dataSet table
|
|
$this->getStore()->update('TRUNCATE TABLE `dataset_' . $this->dataSetId . '`', []);
|
|
$this->getStore()->update('ALTER TABLE `dataset_' . $this->dataSetId . '` AUTO_INCREMENT = 1', []);
|
|
$this->getStore()->commitIfNecessary();
|
|
}
|
|
|
|
/**
|
|
* Add
|
|
*/
|
|
private function add()
|
|
{
|
|
$columns = 'DataSet, Description, UserID, `code`, `isLookup`, `isRemote`, `lastDataEdit`,';
|
|
$columns .= '`lastClear`, `folderId`, `permissionsFolderId`, `isRealTime`, `dataConnectorSource`';
|
|
$values = ':dataSet, :description, :userId, :code, :isLookup, :isRemote,';
|
|
$values .= ':lastDataEdit, :lastClear, :folderId, :permissionsFolderId, :isRealTime, :dataConnectorSource';
|
|
|
|
$params = [
|
|
'dataSet' => $this->dataSet,
|
|
'description' => $this->description,
|
|
'userId' => $this->userId,
|
|
'code' => ($this->code == '') ? null : $this->code,
|
|
'isLookup' => $this->isLookup,
|
|
'isRemote' => $this->isRemote,
|
|
'isRealTime' => $this->isRealTime,
|
|
'dataConnectorSource' => $this->dataConnectorSource,
|
|
'lastDataEdit' => 0,
|
|
'lastClear' => 0,
|
|
'folderId' => ($this->folderId === null) ? 1 : $this->folderId,
|
|
'permissionsFolderId' => ($this->permissionsFolderId == null) ? 1 : $this-> permissionsFolderId,
|
|
];
|
|
|
|
// Insert the extra columns we expect for a remote DataSet
|
|
if ($this->isRemote === 1) {
|
|
$columns .= ', `method`, `uri`, `postData`, `authentication`, `username`, `password`, `customHeaders`, `userAgent`, `refreshRate`, `clearRate`, `truncateOnEmpty`, `runsAfter`, `dataRoot`, `lastSync`, `summarize`, `summarizeField`, `sourceId`, `ignoreFirstRow`, `rowLimit`, `limitPolicy`, `csvSeparator`';
|
|
$values .= ', :method, :uri, :postData, :authentication, :username, :password, :customHeaders, :userAgent, :refreshRate, :clearRate, :truncateOnEmpty, :runsAfter, :dataRoot, :lastSync, :summarize, :summarizeField, :sourceId, :ignoreFirstRow, :rowLimit, :limitPolicy, :csvSeparator';
|
|
|
|
$params['method'] = $this->method;
|
|
$params['uri'] = $this->uri;
|
|
$params['postData'] = $this->postData;
|
|
$params['authentication'] = $this->authentication;
|
|
$params['username'] = $this->username;
|
|
$params['password'] = $this->password;
|
|
$params['customHeaders'] = $this->customHeaders;
|
|
$params['userAgent'] = $this->userAgent;
|
|
$params['refreshRate'] = $this->refreshRate;
|
|
$params['clearRate'] = $this->clearRate;
|
|
$params['truncateOnEmpty'] = $this->truncateOnEmpty ?? 0;
|
|
$params['runsAfter'] = $this->runsAfter;
|
|
$params['dataRoot'] = $this->dataRoot;
|
|
$params['summarize'] = $this->summarize;
|
|
$params['summarizeField'] = $this->summarizeField;
|
|
$params['sourceId'] = $this->sourceId;
|
|
$params['ignoreFirstRow'] = $this->ignoreFirstRow;
|
|
$params['lastSync'] = 0;
|
|
$params['rowLimit'] = $this->rowLimit;
|
|
$params['limitPolicy'] = $this->limitPolicy;
|
|
$params['csvSeparator'] = $this->csvSeparator;
|
|
}
|
|
|
|
// Do the insert
|
|
$this->dataSetId = $this->getStore()->insert('INSERT INTO `dataset` (' . $columns . ') VALUES (' . $values . ')', $params);
|
|
|
|
// Create the data table for this dataSet
|
|
$this->createTable();
|
|
}
|
|
|
|
/**
|
|
* Edit
|
|
*/
|
|
private function edit()
|
|
{
|
|
$sql = '
|
|
`DataSet` = :dataSet,
|
|
`Description` = :description,
|
|
`userId` = :userId,
|
|
`lastDataEdit` = :lastDataEdit,
|
|
`code` = :code,
|
|
`isLookup` = :isLookup,
|
|
`isRemote` = :isRemote,
|
|
`isRealTime` = :isRealTime,
|
|
`dataConnectorSource` = :dataConnectorSource,
|
|
`folderId` = :folderId,
|
|
`permissionsFolderId` = :permissionsFolderId
|
|
';
|
|
$params = [
|
|
'dataSetId' => $this->dataSetId,
|
|
'dataSet' => $this->dataSet,
|
|
'description' => $this->description,
|
|
'userId' => $this->userId,
|
|
'lastDataEdit' => $this->lastDataEdit,
|
|
'code' => $this->code,
|
|
'isLookup' => $this->isLookup,
|
|
'isRemote' => $this->isRemote,
|
|
'isRealTime' => $this->isRealTime,
|
|
'dataConnectorSource' => $this->dataConnectorSource,
|
|
'folderId' => $this->folderId,
|
|
'permissionsFolderId' => $this->permissionsFolderId
|
|
];
|
|
|
|
if ($this->isRemote) {
|
|
$sql .= ', method = :method, uri = :uri, postData = :postData, authentication = :authentication, `username` = :username, `password` = :password, `customHeaders` = :customHeaders, `userAgent` = :userAgent, refreshRate = :refreshRate, clearRate = :clearRate, truncateOnEmpty = :truncateOnEmpty, runsAfter = :runsAfter, `dataRoot` = :dataRoot, `summarize` = :summarize, `summarizeField` = :summarizeField, `sourceId` = :sourceId, `ignoreFirstRow` = :ignoreFirstRow , `rowLimit` = :rowLimit, `limitPolicy` = :limitPolicy, `csvSeparator` = :csvSeparator ';
|
|
|
|
$params['method'] = $this->method;
|
|
$params['uri'] = $this->uri;
|
|
$params['postData'] = $this->postData;
|
|
$params['authentication'] = $this->authentication;
|
|
$params['username'] = $this->username;
|
|
$params['password'] = $this->password;
|
|
$params['customHeaders'] = $this->customHeaders;
|
|
$params['userAgent'] = $this->userAgent;
|
|
$params['refreshRate'] = $this->refreshRate;
|
|
$params['clearRate'] = $this->clearRate;
|
|
$params['truncateOnEmpty'] = $this->truncateOnEmpty ?? 0;
|
|
$params['runsAfter'] = $this->runsAfter;
|
|
$params['dataRoot'] = $this->dataRoot;
|
|
$params['summarize'] = $this->summarize;
|
|
$params['summarizeField'] = $this->summarizeField;
|
|
$params['sourceId'] = $this->sourceId;
|
|
$params['ignoreFirstRow'] = $this->ignoreFirstRow;
|
|
$params['rowLimit'] = $this->rowLimit;
|
|
$params['limitPolicy'] = $this->limitPolicy;
|
|
$params['csvSeparator'] = $this->csvSeparator;
|
|
}
|
|
|
|
$this->getStore()->update('UPDATE dataset SET ' . $sql . ' WHERE DataSetID = :dataSetId', $params);
|
|
}
|
|
|
|
/**
|
|
* Create the realised table structure for this DataSet
|
|
*/
|
|
private function createTable()
|
|
{
|
|
// Create the data table for this dataset
|
|
$this->getStore()->update('
|
|
CREATE TABLE `dataset_' . $this->dataSetId . '` (
|
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
PRIMARY KEY (`id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1
|
|
', []);
|
|
}
|
|
|
|
private function dropTable()
|
|
{
|
|
$this->getStore()->update('DROP TABLE IF EXISTS dataset_' . $this->dataSetId, [], 'isolated', false, false, true);
|
|
}
|
|
|
|
/**
|
|
* Rebuild the dataSet table
|
|
* @throws GeneralException
|
|
*/
|
|
public function rebuild()
|
|
{
|
|
$this->load();
|
|
|
|
// Drop the data table
|
|
$this->dropTable();
|
|
|
|
// Add the data table
|
|
$this->createTable();
|
|
|
|
foreach ($this->columns as $column) {
|
|
/* @var \Xibo\Entity\DataSetColumn $column */
|
|
$column->dataSetId = $this->dataSetId;
|
|
$column->save(['rebuilding' => true]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notify displays of this campaign change
|
|
*/
|
|
public function notify()
|
|
{
|
|
$this->getLog()->debug('DataSet ' . $this->dataSetId . ' wants to notify');
|
|
|
|
$this->getDisplayNotifyService()->collectNow()->notifyByDataSetId($this->dataSetId);
|
|
}
|
|
|
|
/**
|
|
* Add a row
|
|
* @param array $row
|
|
* @return int
|
|
*/
|
|
public function addRow($row)
|
|
{
|
|
$this->getLog()->debug('Adding row ' . var_export($row, true));
|
|
|
|
// Update the last edit date on this dataSet
|
|
$this->lastDataEdit = Carbon::now()->format('U');
|
|
|
|
// Build a query to insert
|
|
$params = [];
|
|
$keys = array_keys($row);
|
|
|
|
$sql = 'INSERT INTO `dataset_' . $this->dataSetId
|
|
. '` (`' . implode('`, `', $keys) . '`) VALUES (';
|
|
|
|
$i = 0;
|
|
foreach ($row as $value) {
|
|
$i++;
|
|
$sql .= ':value' . $i . ',';
|
|
$params['value' . $i] = $value;
|
|
}
|
|
$sql = rtrim($sql, ',');
|
|
$sql .= ')';
|
|
|
|
return $this->getStore()->insert($sql, $params);
|
|
}
|
|
|
|
/**
|
|
* Edit a row
|
|
* @param int $rowId
|
|
* @param array $row
|
|
*/
|
|
public function editRow($rowId, $row)
|
|
{
|
|
$this->getLog()->debug(sprintf('Editing row %s', var_export($row, true)));
|
|
|
|
// Update the last edit date on this dataSet
|
|
$this->lastDataEdit = Carbon::now()->format('U');
|
|
|
|
// Params
|
|
$params = ['id' => $rowId];
|
|
|
|
// Generate a SQL statement
|
|
$sql = 'UPDATE `dataset_' . $this->dataSetId . '` SET';
|
|
|
|
$i = 0;
|
|
foreach ($row as $key => $value) {
|
|
$i++;
|
|
$sql .= ' `' . $key . '` = :value' . $i . ',';
|
|
$params['value' . $i] = $value;
|
|
}
|
|
|
|
$sql = rtrim($sql, ',');
|
|
|
|
$sql .= ' WHERE `id` = :id ';
|
|
|
|
|
|
|
|
$this->getStore()->update($sql, $params);
|
|
}
|
|
|
|
/**
|
|
* Delete Row
|
|
* @param $rowId
|
|
*/
|
|
public function deleteRow($rowId)
|
|
{
|
|
$this->lastDataEdit = Carbon::now()->format('U');
|
|
|
|
$this->getStore()->update('DELETE FROM `dataset_' . $this->dataSetId . '` WHERE id = :id', [
|
|
'id' => $rowId
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Copy Row
|
|
* @param int $dataSetIdSource
|
|
* @param int $dataSetIdTarget
|
|
*/
|
|
public function copyRows($dataSetIdSource, $dataSetIdTarget)
|
|
{
|
|
$this->getStore()->insert('INSERT INTO `dataset_' . $dataSetIdTarget . '` SELECT * FROM `dataset_' . $dataSetIdSource . '` ' ,[]);
|
|
}
|
|
|
|
/**
|
|
* Clear DataSet cache
|
|
*/
|
|
public function clearCache()
|
|
{
|
|
$this->getLog()->debug('Force sync detected, clear cache for remote dataSet ID ' . $this->dataSetId);
|
|
$this->pool->deleteItem('/dataset/cache/' . $this->dataSetId);
|
|
}
|
|
|
|
private function getScriptPath(): string
|
|
{
|
|
return $this->config->getSetting('LIBRARY_LOCATION')
|
|
. 'data_connectors' . DIRECTORY_SEPARATOR
|
|
. 'dataSet_' . $this->dataSetId . '.js';
|
|
}
|
|
|
|
public function getScript(): string
|
|
{
|
|
if ($this->isRealTime == 0) {
|
|
return '';
|
|
}
|
|
|
|
$path = $this->getScriptPath();
|
|
return (file_exists($path))
|
|
? file_get_contents($path)
|
|
: '';
|
|
}
|
|
|
|
public function saveScript(string $script): void
|
|
{
|
|
if ($this->isRealTime == 1) {
|
|
$path = $this->getScriptPath();
|
|
file_put_contents($path, $script);
|
|
file_put_contents($path . '.md5', md5_file($path));
|
|
}
|
|
}
|
|
}
|