forked from phpLicenseWatcher/phpLicenseWatcher
-
Notifications
You must be signed in to change notification settings - Fork 0
/
features_admin_db.php
337 lines (293 loc) · 11.6 KB
/
features_admin_db.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
<?php
require_once __DIR__ . "/common.php";
/* ----------------------------------------------------------------------------
* Notes
* Some functions will return an alert message, but it could be a string or
* array of strings depending on where the DB lookup result was requested.
*
* Alerts responding to Ajax requests should be a string message that will be
* printed via JS. There are no green/red/blue alert designations.
*
* Any alerts responding to the feature edit form (via POST) can be either a
* "success" alert or "failure" alert. This is sent via array with
* ['msg'] being the alert message, and ['lvl'] being "success" (green alert),
* "failure' (red alert), or "info" (blue alert). Alternatively, the feature
* edit page alert can be a string message (not in an array), and it will be
* assumed to be a blue info alert.
* ------------------------------------------------------------------------- */
/**
* Retrieve feature details by feature ID.
*
* This query is used by the feature edit form to pre-fill input elements.
*
* @param int $id Feature ID to lookup
* @return mixed either a feature's details in associative array or error message string on failure.
*/
function db_get_feature_details_by_id($id) {
if (!ctype_digit($id)) {
return false;
}
db_connect($db);
$sql = "SELECT `name`, `label`, `show_in_lists`, `is_tracked` FROM `features` WHERE `id`=?";
$params = array('i', $id);
$query = $db->prepare($sql);
$query->bind_param(...$params);
$query->execute();
$query->bind_result($feature['name'], $feature['label'], $feature['show_in_lists'], $feature['is_tracked']);
$query->fetch();
if (!empty($db->error_list)) {
$err_msg = htmlspecialchars($db->error);
return "DB Error: {$err_msg}.";
}
$query->close();
$db->close();
// Make sure that $feature isn't an empty set. Return error message when an empty set.
$validate_feature = array_filter($feature, function($val) { return strlen($val) > 0; });
return !empty($validate_feature) ? $feature : "DB returned empty set during feature lookup.";
} // END function db_get_feature_details_by_id()
/**
* Change the status of either 'show_in_lists' or is_tracked' for all features.
*
* Used via POST/AJAX.
*
* @return string alert message to display. Or empty string for no alert.
*/
function db_change_column() {
clean_post();
// validate
switch (false) {
case isset($_POST['val']) && preg_match("/^[01]$/", $_POST['val']):
case isset($_POST['col']) && preg_match("/^show_in_lists$|^is_tracked$/", $_POST['col']):
case isset($_POST['page']) && ctype_digit($_POST['page']):
case isset($_POST['search']):
// Return to main form. No DB process.
return "Validation failed for check/uncheck column";
}
// extract values from POST
$col = $_POST['col'];
$val = $_POST['val'];
$page = intval($_POST['page']);
$search_token = $_POST['search'];
$rows_per_page = ROWS_PER_PAGE; // defined in common.php
$first_row = ($page-1) * $rows_per_page; // starting row, zero based.
if ($search_token !== "") {
$regexp = "WHERE `name` REGEXP ?";
$params = array("siii", $search_token, $first_row, $rows_per_page, $val);
} else {
$regexp = "";
$params = array("iii", $first_row, $rows_per_page, $val);
}
// DB query.
$sql = <<<SQL
UPDATE `features` f, (
SELECT `id`
FROM `features`
{$regexp}
ORDER BY `name` ASC
LIMIT ?,?
) AS ftmp
SET f.`{$col}`=?
WHERE f.`id`=ftmp.`id`;
SQL;
db_connect($db);
$query = $db->prepare($sql);
if (is_bool($query)) {
return $db->error;
}
$query->bind_param(...$params);
$query->execute();
// result from query.
if (!empty($db->error_list)) {
$response_msg = "DB Error: {$db->error}.";
} else {
$response_msg = "OK";
}
$query->close();
$db->close();
return $response_msg;
} // END function db_change_column()
/**
* Change a single feature's 'show_in_lists' or 'is_tracked' column.
*
* Used via POST/AJAX.
*
* @return string response message to indicate success or error.
*/
function db_change_single() {
//validate
clean_post();
switch(false) {
case isset($_POST['id']) && ctype_digit($_POST['id']):
case isset($_POST['col']) && preg_match("/^show_in_lists$|^is_tracked$/", $_POST['col']):
case isset($_POST['state']) && preg_match("/^[01]$/", $_POST['state']):
// Return to main form. No DB process.
return "Validation failed for checkbox toggle.";
}
$id = $_POST['id'];
$new_state = $_POST['state'] === "0" ? 1 : 0;
$col = $_POST['col'];
$sql = "UPDATE `features` SET `{$col}`=? WHERE `id`=?";
$params = array("ii", $new_state, $id);
db_connect($db);
$query = $db->prepare($sql);
$query->bind_param(...$params);
$query->execute();
if (!empty($db->error_list)) {
$response_msg = "DB Error: {$db->error}.";
} else {
$response_msg = "OK"; // indicate success.
}
$query->close();
$db->close();
return $response_msg;
} // END function db_change_single()
/**
* DB operation to either add or edit a feature, based on $_POST['id']
*
* @return string response message from operation (either success or error message).
*/
function db_edit_feature() {
// Validate and set.
clean_post();
$id = isset($_POST['id']) ? $_POST['id'] : null;
$name = isset($_POST['name']) ? htmlspecialchars($_POST['name']) : null;
$label = isset($_POST['label']) && !empty($_POST['label']) ? htmlspecialchars($_POST['label']) : null;
$show_in_lists = isset($_POST['show_in_lists']) && ($_POST['show_in_lists'] === "on" || $_POST['show_in_lists'] === true) ? 1 : 0;
$is_tracked = isset($_POST['is_tracked']) && ($_POST['is_tracked'] === "on" || $_POST['is_tracked'] === true) ? 1 : 0;
// Further validate. On error, stop and return error message.
switch(false) {
// $id must be all numbers or the word "new"
case preg_match("/^\d+$|^new$/", $id):
return array('msg'=>"Invalid feature ID \"{$id}\"", 'lvl'=>"failure");
// $name cannot be blank
case !empty($name):
return array('msg'=>"Feature name cannot be blank", 'lvl'=>"failure");
}
// $label can be blank.
// END error check
if ($id === "new") {
// Adding a new server
$sql = "INSERT INTO `features` (`name`, `label`, `show_in_lists`, `is_tracked`) VALUES (?, ?, ?, ?)";
$params = array("ssii", $name, $label, $show_in_lists, $is_tracked);
$op = "added";
} else {
// Editing an existing server
$sql = "UPDATE `features` SET `name`=?, `label`=?, `show_in_lists`=?, `is_tracked`=? WHERE `ID`=?";
$params = array("ssiii", $name, $label, $show_in_lists, $is_tracked, $id);
$op = "updated";
}
db_connect($db);
$query = $db->prepare($sql);
$query->bind_param(...$params);
$query->execute();
if (empty($db->error_list)) {
if (!empty($label)) $label = " ({$label})";
$response_msg = array('msg' => "{$name}{$label} successfully {$op}.", 'lvl' => "success");
} else {
$response_msg = array('msg' => "(${name}) DB Error: {$db->error}.", 'lvl' => "failure");
}
$query->close();
$db->close();
return $response_msg;
} // END function db_process()
/**
* Delete feature from DB by feature's ID.
*
* @return array success/error message and page to return to.
*/
function db_delete_feature() {
// validate
clean_post();
switch (false) {
case isset($_POST['name']):
case isset($_POST['id']) && ctype_digit($_POST['id']):
// Do not process
return array('msg' => "Request to delete a feature has failed validation.", 'lvl' => "failure");
}
$name = htmlspecialchars($_POST['name']);
$id = $_POST['id'];
db_connect($db);
$sql = "DELETE FROM `features` WHERE `id`=?";
$params = array("i", $id);
$query = $db->prepare($sql);
$query->bind_param(...$params);
$query->execute();
if (empty($db->error_list)) {
$response_msg = array('msg' => "Successfully deleted {$name}", 'lvl' => "success");
} else {
$response_msg = array('msg' => "({$name}) DB Error: {$db->error}.", 'lvl' => "failure");
}
$query->close();
$db->close();
return $response_msg;
} //END function delete_feature()
/**
* Get features table results from DB based on $page and $search_token.
*
* Used via POST/AJAX. Page depends on constant ROWS_PER_PAGE, defined in common.php
* Result set is a single page subset of the Features table.
*
* @param string $page Page number of result subset.
* @param string $search_token Feature's `name` column search string for DB lookup.
* @return array ['alert'] => alert to display, ['features'] => DB result set, ['last_page'] => final page number in DB result set.
*/
function db_get_page_data($page, $search_token="") {
$rows_per_page = ROWS_PER_PAGE; // defined in common.php
$first_row = ($page-1) * $rows_per_page; // starting row, zero based.
$results = array();
// Used in 'feature_list' query. Constrain query by search token or select entire table.
if ($search_token === "") {
$where = "";
$order_by = "ORDER BY `name` ASC";
$params['feature_list'] = array("ii", $first_row, $rows_per_page);
$params['feature_count'] = null;
} else {
// REGEXP is not utf8 safe, so we are using LIKE to pattern match.
// Wildcard chars ('%' and '_') and '\' need to be escaped.
$search_token = preg_replace("/(%|_|\\\)/u", '\\\$0', $search_token); // escaping chars.
$search_token = "%{$search_token}%"; // adding wildcards to complete search pattern.
$where = "WHERE `name` LIKE ? OR `label` LIKE ?";
// ORDER BY `label` IS NULL ensures that NULL cols are sorted last.
// This works because 0 (false) is lower than 1 (true).
$order_by = "ORDER BY `label` IS NULL, `label` ASC, `name` ASC";
$params['feature_list'] = array ("ssii", $search_token, $search_token, $first_row, $rows_per_page);
$params['feature_count'] = array("ss", $search_token, $search_token);
}
// Query to get current page of features
$sql['feature_list'] = <<<SQL
SELECT * FROM `features`
{$where}
{$order_by}
LIMIT ?, ?
SQL;
// Query for how many features are in the DB. (to determine how many pages there are)
$sql['feature_count'] = "SELECT COUNT(*) FROM `features` {$where}";
db_connect($db);
// Run query to get features for current page
$query = $db->prepare($sql['feature_list']);
$query->bind_param(...$params['feature_list']);
$query->execute();
$query->bind_result($r_id, $r_name, $r_label, $r_lists, $r_tracked);
while ($query->fetch()) {
$results[] = array(
'id' => $r_id,
'name' => $r_name,
'label' => $r_label,
'show_in_lists' => $r_lists,
'is_tracked' => $r_tracked
);
}
$query->close();
// Run query to get feature count and determine how many pages there are.
$query = $db->prepare($sql['feature_count']);
if (!is_null($params['feature_count'])) $query->bind_param(...$params['feature_count']);
$query->execute();
$query->bind_result($r_count);
$query->fetch();
$total_pages = intval(ceil($r_count / $rows_per_page));
$alert = !empty($db->error_list) ? "DB Error: {$db->error}." : "";
$query->close();
$db->close();
return array('alert' => $alert, 'features' => $results, 'last_page' => $total_pages);
} //END function db_get_page_data()
?>