1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Analytics |
4
|
|
|
* |
5
|
|
|
* This file is licensed under the Affero General Public License version 3 or |
6
|
|
|
* later. See the LICENSE.md file. |
7
|
|
|
* |
8
|
|
|
* @author Marcel Scherello <[email protected]> |
9
|
|
|
* @copyright 2019-2022 Marcel Scherello |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace OCA\Analytics\Service; |
13
|
|
|
|
14
|
|
|
use OCA\Analytics\Db\DatasetMapper; |
15
|
|
|
use OCP\IDateTimeFormatter; |
16
|
|
|
use Psr\Log\LoggerInterface; |
17
|
|
|
|
18
|
|
|
class VariableService |
19
|
|
|
{ |
20
|
|
|
private $logger; |
21
|
|
|
private $DatasetMapper; |
22
|
|
|
private $IDateTimeFormatter; |
23
|
|
|
|
24
|
|
|
public function __construct( |
25
|
|
|
LoggerInterface $logger, |
26
|
|
|
DatasetMapper $DatasetMapper, |
27
|
|
|
IDateTimeFormatter $IDateTimeFormatter |
28
|
|
|
) |
29
|
|
|
{ |
30
|
|
|
$this->logger = $logger; |
31
|
|
|
$this->DatasetMapper = $DatasetMapper; |
32
|
|
|
$this->IDateTimeFormatter = $IDateTimeFormatter; |
33
|
|
|
} |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* replace %*% text variables in thresholds |
37
|
|
|
* |
38
|
|
|
* @param array $thresholds |
39
|
|
|
* @return array |
40
|
|
|
*/ |
41
|
|
|
public function replaceThresholdsVariables($thresholds) |
42
|
|
|
{ |
43
|
|
|
foreach ($thresholds as &$threshold) { |
44
|
|
|
$fields = ['dimension1', 'dimension2']; |
45
|
|
|
foreach ($fields as $field) { |
46
|
|
|
isset($threshold[$field]) ? $name = $threshold[$field] : $name = ''; |
47
|
|
|
$parsed = $this->parseFilter($name); |
48
|
|
|
if (!$parsed) break; |
|
|
|
|
49
|
|
|
$threshold[$field] = $parsed['6$startDate']; |
50
|
|
|
} |
51
|
|
|
} |
52
|
|
|
return $thresholds; |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* replace %*% text variables in name and subheader |
57
|
|
|
* |
58
|
|
|
* @param array $datasetMetadata |
59
|
|
|
* @return array |
60
|
|
|
*/ |
61
|
|
|
public function replaceTextVariables($datasetMetadata) |
62
|
|
|
{ |
63
|
|
|
$fields = ['name', 'subheader']; |
64
|
|
|
foreach ($fields as $field) { |
65
|
|
|
isset($datasetMetadata[$field]) ? $name = $datasetMetadata[$field] : $name = ''; |
66
|
|
|
|
67
|
|
|
preg_match_all("/%.*?%/", $name, $matches); |
68
|
|
|
if (count($matches[0]) > 0) { |
69
|
|
|
foreach ($matches[0] as $match) { |
70
|
|
|
$replace = null; |
71
|
|
|
if ($match === '%currentDate%') { |
72
|
|
|
$replace = $this->IDateTimeFormatter->formatDate(time(), 'short'); |
73
|
|
|
} elseif ($match === '%currentTime%') { |
74
|
|
|
$replace = $this->IDateTimeFormatter->formatTime(time(), 'short'); |
75
|
|
|
} elseif ($match === '%now%') { |
76
|
|
|
$replace = time(); |
77
|
|
|
} elseif ($match === '%lastUpdateDate%') { |
78
|
|
|
$timestamp = $this->DatasetMapper->getLastUpdate($datasetMetadata['dataset']); |
79
|
|
|
$replace = $this->IDateTimeFormatter->formatDate($timestamp, 'short'); |
80
|
|
|
} elseif ($match === '%lastUpdateTime%') { |
81
|
|
|
$timestamp = $this->DatasetMapper->getLastUpdate($datasetMetadata['dataset']); |
82
|
|
|
$replace = $this->IDateTimeFormatter->formatTime($timestamp, 'short'); |
83
|
|
|
} elseif ($match === '%owner%') { |
84
|
|
|
$owner = $this->DatasetMapper->getOwner($datasetMetadata['dataset']); |
85
|
|
|
$replace = $owner; |
86
|
|
|
} |
87
|
|
|
if ($replace !== null) { |
88
|
|
|
$datasetMetadata[$field] = preg_replace('/' . $match . '/', $replace, $datasetMetadata[$field]); |
89
|
|
|
} |
90
|
|
|
} |
91
|
|
|
} |
92
|
|
|
} |
93
|
|
|
return $datasetMetadata; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* replace variables in single field |
98
|
|
|
* used in: API |
99
|
|
|
* |
100
|
|
|
* @param $field |
101
|
|
|
* @return array |
102
|
|
|
*/ |
103
|
|
|
public function replaceTextVariablesSingle($field) |
104
|
|
|
{ |
105
|
|
|
if ($field !== null) { |
106
|
|
|
preg_match_all("/%.*?%/", $field, $matches); |
107
|
|
|
if (count($matches[0]) > 0) { |
108
|
|
|
foreach ($matches[0] as $match) { |
109
|
|
|
$replace = null; |
110
|
|
|
if ($match === '%currentDate%') { |
111
|
|
|
$replace = $this->IDateTimeFormatter->formatDate(time(), 'short'); |
112
|
|
|
} elseif ($match === '%currentTime%') { |
113
|
|
|
$replace = $this->IDateTimeFormatter->formatTime(time(), 'short'); |
114
|
|
|
} elseif ($match === '%now%') { |
115
|
|
|
$replace = time(); |
116
|
|
|
} |
117
|
|
|
if ($replace !== null) { |
118
|
|
|
$field = preg_replace('/' . $match . '/', $replace, $field); |
119
|
|
|
} |
120
|
|
|
} |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
return $field; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* replace variables in single field |
128
|
|
|
* used in: API |
129
|
|
|
* |
130
|
|
|
* @param $columns |
131
|
|
|
* @return string |
132
|
|
|
*/ |
133
|
|
|
public function replaceDatasourceColumns($columns) |
134
|
|
|
{ |
135
|
|
|
$parsed = $this->parseFilter($columns); |
136
|
|
|
$format = $this->parseFormat($columns); |
137
|
|
|
if (!$parsed) return $columns; |
|
|
|
|
138
|
|
|
return date($format, $parsed['value']); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* replace variables in filters and apply format |
143
|
|
|
* |
144
|
|
|
* @param $reportMetadata |
145
|
|
|
* @return array |
146
|
|
|
*/ |
147
|
|
|
public function replaceFilterVariables($reportMetadata) |
148
|
|
|
{ |
149
|
|
|
if ($reportMetadata['filteroptions'] !== null) { |
150
|
|
|
$filteroptions = json_decode($reportMetadata['filteroptions'], true); |
151
|
|
|
if (isset($filteroptions['filter'])) { |
152
|
|
|
foreach ($filteroptions['filter'] as $key => $value) { |
153
|
|
|
$parsed = $this->parseFilter($value['value']); |
154
|
|
|
$format = $this->parseFormat($value['value']); |
155
|
|
|
|
156
|
|
|
if (!$parsed) break; |
|
|
|
|
157
|
|
|
|
158
|
|
|
// if a parser is selected in the chart options, it should also be valid here automatically |
159
|
|
|
if (isset($reportMetadata['chartoptions'])) { |
160
|
|
|
$chartOptions = json_decode($reportMetadata['chartoptions'], true); |
161
|
|
|
if(isset($chartOptions['scales']['xAxes']['time']['parser'])) { |
162
|
|
|
$format = $chartOptions['scales']['xAxes']['time']['parser']; |
163
|
|
|
} |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
// translate commonly known X timestamp format to U for php |
167
|
|
|
if ($format === 'X') $format = 'U'; |
168
|
|
|
|
169
|
|
|
$filteroptions['filter'][$key]['value'] = date($format, $parsed['value']); |
170
|
|
|
//$filteroptions['filter'][$key]['option'] = $parsed['option']; |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
$reportMetadata['filteroptions'] = json_encode($filteroptions); |
174
|
|
|
} |
175
|
|
|
return $reportMetadata; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* parsing of %*% variables |
180
|
|
|
* |
181
|
|
|
* @param $filter |
182
|
|
|
* @return array|bool |
183
|
|
|
*/ |
184
|
|
|
private function parseFilter($filter) { |
185
|
|
|
preg_match_all("/(?<=%).*(?=%)/", $filter, $matches); |
186
|
|
|
if (count($matches[0]) > 0) { |
187
|
|
|
$filter = $matches[0][0]; |
188
|
|
|
preg_match('/(last|next|current|to|yester)?/', $filter, $directionMatch); // direction |
189
|
|
|
preg_match('/[0-9]+/', $filter, $offsetMatch); // how much |
190
|
|
|
preg_match('/(day|days|week|weeks|month|months|year|years)$/', $filter, $unitMatch); // unit |
191
|
|
|
|
192
|
|
|
if (!$directionMatch[0] || !$unitMatch[0]) { |
193
|
|
|
// no known text variables found |
194
|
|
|
return false; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
// if no offset is specified, apply 1 as default |
198
|
|
|
!isset($offsetMatch[0]) ? $offset = 1: $offset = $offsetMatch[0]; |
199
|
|
|
|
200
|
|
|
// remove "s" to unify e.g. weeks => week |
201
|
|
|
$unit = rtrim($unitMatch[0], 's'); |
202
|
|
|
|
203
|
|
|
if ($directionMatch[0] === "last" || $directionMatch[0] === "yester") { |
204
|
|
|
// go back |
205
|
|
|
$direction = '-'; |
206
|
|
|
} elseif ($directionMatch[0] === "next") { |
207
|
|
|
// go forward |
208
|
|
|
$direction = '+'; |
209
|
|
|
} else { |
210
|
|
|
// current |
211
|
|
|
$direction = '+'; |
212
|
|
|
$offset = 0; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
// create a usable string for php like "+3 days" |
216
|
|
|
$timeString = $direction . $offset . ' ' . $unit; |
217
|
|
|
// get a timestamp of the target date |
218
|
|
|
$baseDate = strtotime($timeString); |
219
|
|
|
|
220
|
|
|
// get the correct format depending of the unit. e.g. first day of the month in case unit is "month" |
221
|
|
|
if ($unit === 'day') { |
222
|
|
|
$startString = 'today'; |
223
|
|
|
} else { |
224
|
|
|
$startString = 'first day of this ' . $unit; |
225
|
|
|
} |
226
|
|
|
$startTS = strtotime($startString, $baseDate); |
227
|
|
|
$start = date("Y-m-d", $startTS); |
228
|
|
|
|
229
|
|
|
$return = [ |
230
|
|
|
'value' => $startTS, |
231
|
|
|
'option' => 'GT', |
232
|
|
|
'1$filter' => $filter, |
233
|
|
|
'2$timestring' => $timeString, |
234
|
|
|
'3$target' => $baseDate, |
235
|
|
|
'4$target_clean' => date("Y-m-d", $baseDate), |
236
|
|
|
'5$startString' => $startString, |
237
|
|
|
'6$startDate' => $start, |
238
|
|
|
'7$startTS' => $startTS, |
239
|
|
|
]; |
240
|
|
|
//$this->logger->debug('parseFilter: '. json_encode($return)); |
241
|
|
|
} else { |
242
|
|
|
$return = false; |
243
|
|
|
} |
244
|
|
|
return $return; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* parsing of ( ) format instructions |
249
|
|
|
* |
250
|
|
|
* @param $filter |
251
|
|
|
* @return string |
252
|
|
|
*/ |
253
|
|
|
private function parseFormat($filter) { |
254
|
|
|
preg_match_all("/(?<=\().*(?=\))/", $filter, $matches); |
255
|
|
|
if (count($matches[0]) > 0) { |
256
|
|
|
return $matches[0][0]; |
257
|
|
|
} else { |
258
|
|
|
return 'Y-m-d H:m:s'; |
259
|
|
|
} |
260
|
|
|
} |
261
|
|
|
} |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.