PluginProbe
wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin / trunk
wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin vtrunk
6.5.1.6 6.5.1.5 6.5.1.4 6.5.1.3 6.5.1.2 6.5.1.1 6.5.0.9 6.5.0.8 6.5.0.7 6.5.0.6 trunk 3.4.2.40 3.4.2.41 3.4.2.42 3.4.2.43 3.4.2.44 3.4.2.45 3.4.2.46 3.4.2.47 3.4.2.48 3.4.2.49 3.4.2.50 6.3.2 6.3.3.1 6.3.3.10 All 46 releases
wpdatatables / source / class.wpdatatablecache.php

class.wpdatatablecache.php in wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin trunk, at source/class.wpdatatablecache.php

428 lines 12.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use PhpOffice\PhpSpreadsheet\Shared\Date;
4
5 defined('ABSPATH') or die('Access denied.');
6
7 class WPDataTableCache
8 {
9
10 public static function maybeCache($createCache, $tableID)
11 {
12 if ($tableID) {
13
14 if (!$createCache) {
15 self::delete($tableID);
16 return false;
17 }
18
19 $cache = self::isCacheDataExist($tableID);
20
21 if (!$cache) {
22 return false;
23 }
24
25 if (isset($_POST['action']) && $_POST['action'] === 'wpdatatables_save_table_config') {
26 self::delete($tableID);
27 return false;
28 }
29
30 if (!isset($cache->data))
31 return false;
32
33 return json_decode($cache->data, true);
34 }
35
36 return false;
37 }
38
39 private static function updateData($tableID, $sourceData)
40 {
41 global $wpdb;
42 if ($tableID) {
43 $sourceData = self::filterUserData($sourceData, $tableID);
44 $wpdb->update(
45 $wpdb->prefix . "wpdatatables_cache",
46 array(
47 'data' => json_encode($sourceData, JSON_NUMERIC_CHECK),
48 'updated_time' => current_time('mysql'),
49 ),
50 array('table_id' => $tableID)
51 );
52 if ($wpdb->last_error !== '') {
53 self::_logErrors(
54 'Update cache error:',
55 $wpdb->last_error,
56 1,
57 $tableID
58 );
59 }
60 } else {
61 self::_logErrors(
62 'Update cache error:',
63 'Table ID is not set for auto update data.',
64 1,
65 $tableID
66 );
67 }
68 }
69 private static function filterUserData ($sourceData, $tableID){
70 $filterSourceData= apply_filters('wpdatatables_filter_source_data_on_auto_update_cache', true, $tableID);
71 if (!current_user_can('unfiltered_html') && $filterSourceData) {
72 $tempSourceData = $sourceData;
73 $sourceData = [];
74 foreach ($tempSourceData as $index => $tempData) {
75 foreach ($tempData as $key => $data) {
76 $sourceData[$index][wp_kses_post($key)] = is_null($data) ? sanitize_text_field($data) : wp_kses_post($data);
77 }
78 }
79 return $sourceData;
80 }
81 return $sourceData;
82 }
83
84 public static function maybeSaveData($tableID, $tableType, $tableContent, $autoUpdate, $sourceData, $isCache)
85 {
86 global $wpdb;
87 if ($tableID && $isCache) {
88 if (!empty($sourceData)) {
89 $sourceData = self::filterUserData($sourceData, $tableID);
90 $wpdb->insert(
91 $wpdb->prefix . "wpdatatables_cache",
92 array(
93 'table_id' => $tableID,
94 'table_type' => $tableType,
95 'table_content' => $tableContent,
96 'auto_update' => $autoUpdate,
97 'data' => json_encode($sourceData, JSON_NUMERIC_CHECK)
98 )
99 );
100 if ($wpdb->last_error !== '')
101 self::_logErrors('Save cache error:', $wpdb->last_error, 0, $tableID);
102
103 }
104 }
105 }
106
107 private static function delete($tableID)
108 {
109 if ($tableID) {
110 global $wpdb;
111 $wpdb->delete(
112 $wpdb->prefix . "wpdatatables_cache",
113 array(
114 'table_id' => $tableID
115 ),
116 array(
117 '%d'
118 )
119 );
120 if ($wpdb->last_error !== '')
121 self::_logErrors('Delete cache error:', $wpdb->last_error, 0, $tableID);
122 }
123 }
124
125 private static function isCacheDataExist($tableID)
126 {
127 global $wpdb;
128 $cacheQuery = $wpdb->prepare(
129 "SELECT data
130 FROM " . $wpdb->prefix . "wpdatatables_cache
131 WHERE table_id = %d",
132 $tableID
133 );
134
135 $cache = $wpdb->get_row($cacheQuery);
136
137 if ($wpdb->last_error !== '') {
138 self::_logErrors('Get cache data error:', $wpdb->last_error, 0, $tableID);
139 return false;
140 }
141
142 if ($cache === null) {
143 return false;
144 }
145
146 return $cache;
147 }
148
149 private static function getTablesWithCacheForAutoUpdate()
150 {
151 global $wpdb;
152 $tablesForAutoUpdateQuery = "SELECT table_id, table_type, table_content, updated_time, data
153 FROM " . $wpdb->prefix . "wpdatatables_cache
154 WHERE auto_update = 1
155 ORDER BY id";
156
157 $tablesForAutoUpdate = $wpdb->get_results($tablesForAutoUpdateQuery, ARRAY_A);
158
159 if ($wpdb->last_error !== '') {
160 self::_logErrors('Error get tables with cache:', $wpdb->last_error, 0, 0);
161 return false;
162 }
163
164 if ($tablesForAutoUpdate === null) {
165 return false;
166 }
167
168 return $tablesForAutoUpdate;
169 }
170
171 public static function addAutoUpdateHooks()
172 {
173 add_action('wp_ajax_wdtable_update_cache', array(__CLASS__, 'maybeAutoUpdate'));
174 add_action('wp_ajax_nopriv_wdtable_update_cache', array(__CLASS__, 'maybeAutoUpdate'));
175 }
176
177 private static function _logErrors($title, $log, $autoUpdate, $tableID)
178 {
179 global $wpdb;
180 $logMessage = 'wpDataTables - ';
181
182 if ($title) {
183 $logMessage = $logMessage . $title;
184 }
185 $logMessage = $logMessage . ' ' . $log;
186
187 if ($tableID) {
188 $logMessage = $logMessage . ' Table ID=' . $tableID;
189 if ($autoUpdate) {
190 $logError = current_time('mysql') . ' - ' . $title . ' ' . $log;
191 $wpdb->query(
192 $wpdb->prepare(
193 "UPDATE " . $wpdb->prefix . "wpdatatables_cache
194 SET log_errors = %s WHERE table_id = %d",
195 $logError,
196 $tableID
197 )
198 );
199 }
200 }
201
202 error_log($logMessage);
203 }
204
205 public static function maybeAutoUpdate()
206 {
207 $autoUpdateHash = get_option('wdtAutoUpdateHash');
208
209 if ($autoUpdateHash !== $_GET['wdtable_cache_verify']) return;
210
211 $cacheTables = self::getTablesWithCacheForAutoUpdate();
212
213 if (!$cacheTables) {
214 return;
215 }
216
217 foreach ($cacheTables as $cacheTable) {
218
219 $result = self::_renderDataFromSource(
220 $cacheTable['table_id'],
221 $cacheTable['table_type'],
222 $cacheTable['table_content']
223 );
224
225 if (isset($result['status']) && $result['status'] === 'success') {
226 if (!isset($result['data'])) {
227 self::_logErrors(
228 'Auto update error message:',
229 'Data array from source is not rendered.',
230 1,
231 $cacheTable['table_id']
232 );
233 continue;
234 }
235
236 $cacheData = json_decode($cacheTable['data'], true);
237
238 if (!isset($cacheData[0])) {
239 self::_logErrors(
240 'Auto update error message:',
241 'Data array from cache is empty.',
242 1,
243 $cacheTable['table_id']
244 );
245 continue;
246 }
247
248 if (!isset($result['data'][0])) {
249 self::_logErrors(
250 'Auto update error message:',
251 'Data array from source is not rendered.',
252 1,
253 $cacheTable['table_id']
254 );
255 continue;
256 }
257
258 if (count($cacheData[0]) !== count($result['data'][0])) {
259 self::_logErrors(
260 'Auto update error message:',
261 'Data array from source and cache do not have same number of keys(columns).',
262 1,
263 $cacheTable['table_id']
264 );
265 continue;
266 }
267
268 if (array_keys($cacheData[0]) !== array_keys($result['data'][0])) {
269 self::_logErrors(
270 'Auto update error message:',
271 'Data array from source and cache do not have same keys(columns).',
272 1,
273 $cacheTable['table_id']
274 );
275 continue;
276 }
277
278 if ($cacheTable['data'] === json_encode($result['data'], JSON_NUMERIC_CHECK)) continue;
279
280 self::updateData($cacheTable['table_id'], $result['data']);
281 }
282 }
283 }
284
285 private static function _renderDataFromSource($table_id, $source_type, $source)
286 {
287 if (empty($source)) {
288 return [
289 'status' => 'error',
290 'error' => 'Source is empty.',
291 'data' => []
292 ];
293 }
294 try {
295 if (in_array($source_type, ['xlsx','ods', 'xls', 'csv'])) {
296 $tableData = WDTConfigController::loadTableFromDB($table_id);
297 $params = array(
298 'dateInputFormat' => array(),
299 'data_types' => array(),
300 );
301 if ($tableData) {
302 foreach ($tableData->columns as $column) {
303 if ($column->type !== 'autodetect') {
304 $params['data_types'][$column->orig_header] = $column->type;
305 }
306 $params['dateInputFormat'][$column->orig_header] =
307 isset($column->dateInputFormat) ? $column->dateInputFormat : null;
308 }
309 }
310
311 }
312 $dataArray = array();
313 switch ($source_type) {
314 case 'ods':
315 case 'xlsx':
316 case 'xls':
317 case 'csv':
318 ini_set('memory_limit', '2048M');
319 if (isset($tableData) && $tableData->file_location == 'wp_media_lib' && !file_exists($source)) {
320 self::_logErrors(
321 'Error message:',
322 'Provided file ' . stripcslashes($source) . ' does not exist!',
323 1,
324 $table_id
325 );
326 }
327 $format = substr(strrchr($source, "."), 1);
328 $objReader = WPDataTable::createObjectReader($source);
329 if (isset($tableData) && $tableData->file_location == 'wp_any_url'){
330 // Security Fix: Validate URL before file_get_contents to prevent SSRF/LFI
331 if (!filter_var($source, FILTER_VALIDATE_URL)) {
332 throw new Exception('Invalid URL format!');
333 }
334
335 // Prevent access to local files via file:// protocol
336 $parsedUrl = parse_url($source);
337 if (!isset($parsedUrl['scheme']) || !in_array(strtolower($parsedUrl['scheme']), array('http', 'https'), true)) {
338 throw new Exception('Only HTTP and HTTPS protocols are allowed!');
339 }
340
341 $file = @file_get_contents($source);
342 if ($file === false){
343 throw new Exception('There is an error opening the file!');
344 }
345 $tempFileName = 'tempfile.' . $format;
346 file_put_contents($tempFileName, $file);
347 $source = $tempFileName;
348 }
349 $objPHPExcel = $objReader->load($source);
350 $objWorksheet = $objPHPExcel->getActiveSheet();
351 $highestRow = $objWorksheet->getHighestRow();
352 $highestColumn = $objWorksheet->getHighestDataColumn();
353
354 $headingsArray = $objWorksheet->rangeToArray('A1:' . $highestColumn . '1', null, true, true, true);
355 $headingsArray = array_map('trim', $headingsArray[1]);
356
357 $r = -1;
358
359 $dataRows = $objWorksheet->rangeToArray('A2:' . $highestColumn . $highestRow, null, true, true, true);
360 for ($row = 2; $row <= $highestRow; ++$row) {
361 if (max($dataRows[$row]) !== null) {
362 ++$r;
363 foreach ($headingsArray as $dataColumnIndex => $dataColumnHeading) {
364 $dataColumnHeading = trim(preg_replace('/\s\s+/', ' ', str_replace("\n", " ", $dataColumnHeading)));
365 $dataArray[$r][$dataColumnHeading] = $dataRows[$row][$dataColumnIndex];
366 $currentDateFormat = isset($params['dateInputFormat'][$dataColumnHeading]) ? $params['dateInputFormat'][$dataColumnHeading] : null;
367 if (!empty($params['data_types'][$dataColumnHeading]) && in_array($params['data_types'][$dataColumnHeading], array('date', 'datetime', 'time'))) {
368 if ($format === 'xls' || $format === 'ods') {
369 $cell = $objPHPExcel->getActiveSheet()->getCell($dataColumnIndex . '' . $row);
370 if (Date::isDateTime($cell) && $cell->getValue() !== null) {
371 $dataArray[$r][$dataColumnHeading] = Date::excelToTimestamp($cell->getValue());
372 } else {
373 $dataArray[$r][$dataColumnHeading] = WDTTools::wdtConvertStringToUnixTimestamp($dataRows[$row][$dataColumnIndex], $currentDateFormat);
374 }
375 } elseif ($format === 'csv') {
376 $dataArray[$r][$dataColumnHeading] = WDTTools::wdtConvertStringToUnixTimestamp($dataRows[$row][$dataColumnIndex], $currentDateFormat);
377 }
378 }
379 }
380 }
381 }
382 break;
383 case 'xml':
384 $dataArray = WPDataTable::xmlRenderData($source, $table_id);
385 break;
386 case 'json':
387 $dataArray = WPDataTable::jsonRenderData($source, $table_id);
388 break;
389 case 'nested_json':
390 $dataArray = WPDataTable::nestedJsonRenderData($source, $table_id);
391 break;
392 case 'serialized':
393 $dataArray = WPDataTable::serializedPhpRenderData($source, $table_id);
394 break;
395 default:
396 self::_logErrors('Error message:', 'Source type is unknown', 1, $table_id);
397 return [
398 'status' => 'error',
399 'error' => 'Source type is unknown',
400 'data' => []
401 ];
402 }
403 } catch (Exception $e) {
404 self::_logErrors('Error message:', $e->getMessage(), 1, $table_id);
405 return [
406 'status' => 'error',
407 'error' => $e->getMessage(),
408 'data' => []
409 ];
410 }
411
412 if (empty($dataArray)) {
413 self::_logErrors('Error message:', 'Data from source is empty', 1, $table_id);
414 return [
415 'status' => 'error',
416 'error' => 'Data from source is empty',
417 'data' => []
418 ];
419 }
420
421 return [
422 'status' => 'success',
423 'error' => '',
424 'data' => $dataArray
425 ];
426 }
427 }
428 WPDataTableCache::addAutoUpdateHooks();