-
Notifications
You must be signed in to change notification settings - Fork 200
Expand file tree
/
Copy pathprocess.php
More file actions
315 lines (300 loc) · 10.2 KB
/
Copy pathprocess.php
File metadata and controls
315 lines (300 loc) · 10.2 KB
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
<?php declare(strict_types=1);
/**
* This file is used by the Configuration module to update
* or insert values into the Config table.
*
* FIXME This code should be refactored away from using the separate 'ajax'
* file model toward using a more robust Loris Module-based approached.
*
* PHP version 7
*
* @category Main
* @package Loris
* @author Tara Campbell <tara.campbell@mail.mcgill.ca>
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
* @link https://github.com/aces/Loris
*/
ini_set('default_charset', 'utf-8');
require_once "Database.class.inc";
require_once 'NDB_Client.class.inc';
require_once "Utility.class.inc";
if (!\User::singleton()->hasPermission('config')) {
header("HTTP/1.1 403 Forbidden");
return;
}
$client = new NDB_Client();
$client->makeCommandLine();
$client->initialize();
$factory = \NDB_Factory::singleton();
$DB = $factory->database();
$mappingValues = [];
foreach ($_POST as $key => $value) {
$key = (string) $key;
if (strpos($key, 'mapping-') === 0) {
$mappingValues[substr($key, strlen('mapping-'))] = $value;
}
}
foreach ($_POST as $key => $value) {
$key = (string) $key;
if (strpos($key, 'mapping-') === 0) {
continue;
}
if (is_numeric($key)) {
// When a $key is numeric, it means we are updating the entry in the
// Config table with ID == $key.
if ($value == "") {
// A blank value is the same as deleting.
$DB->delete('Config', ['ID' => $key]);
} else {
if (! noDuplicateInDropdown($key, $value)) {
// Don't alter the table if the same key was passed twice.
continue;
}
// Get all the IDs in Config with a relation to an entry in the
// ConfigSettings table that has the web_path data type.
$pathIDs = getPathIDs('Config');
if (in_array($key, $pathIDs)) {
// If updating a path config setting, make sure that the
// value is a valid path. This helps to prevent malicious
// input.
if (!validPath($value)) {
$err = 'Directory `'
. htmlspecialchars(
$value,
ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5,
'UTF-8',
false
)
. '` is invalid';
// Set response code and display error message.
displayError(400, $err);
return;
}
}
// Update the config setting to the new value.
$DB->unsafeUpdate(
'Config',
['Value' => $value],
['ID' => $key]
);
if (array_key_exists($key, $mappingValues)) {
saveMappingValue($key, $mappingValues[$key]);
}
}
} else {
// This branch is executed when the key is prefixed with the string
// 'add' or 'remove' (i.e. the full key is not numeric and triggers this
// else branch).
// An example $key is 'add-38' which means "Add an entry in the Config
// table using foriegn key 38 from the ConfigSettings table." The
// number refers to ConfigSettings.ID which is different from
// Config.ID.
// This is different from the above is_numeric case; this makes use of
// Config.ID, not Config.ConfigID (which is a FK to ConfigSettings.ID).
// The Config table is the one that will be modified here.
$keySplit = explode("-", $key); // e.g. 'add-17-1' or 'remove'
$action = $keySplit[0];
//assert(count($keySplit) == 2);
if ($action == 'add') {
$ConfigSettingsID = $keySplit[1] ?? null;
if ($ConfigSettingsID === null) {
displayError(400, 'Invalid action');
return;
}
// This branch adds a new entry to the Config table.
if ($value === "") {
continue;
}
if (isDuplicate($ConfigSettingsID, $value)) {
displayError(
400,
"Duplicate value submitted: "
. htmlspecialchars(
$value,
ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5,
'UTF-8',
false
)
);
exit(0);
}
// Get all the IDs in ConfigSettings with the web_path data type.
$pathIDs = getPathIDs('ConfigSettings');
if (in_array($ConfigSettingsID, $pathIDs)) {
if (!validPath($value)) {
$err = 'Directory `' . htmlspecialchars(
$value,
ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5,
'UTF-8',
false
)
. '` is invalid';
// Set response code and display error message.
displayError(400, $err);
return;
}
}
// Add the new setting
$DB->unsafeInsert(
'Config',
[
'ConfigID' => $ConfigSettingsID, // FK to ConfigSettings.
'Value' => $value,
]
);
if (array_key_exists($key, $mappingValues)) {
saveMappingValue($DB->lastInsertID, $mappingValues[$key]);
}
} elseif ($action == 'remove') {
$valueSplit = explode("-", $value); // e.g. "remove-74"
$removeID = $valueSplit[1] ?? null;
if ($removeID === null) {
displayError(400, 'Invalid action');
return;
}
// Delete an entry from the Config table.
$DB->delete(
'Config',
['ID' => $removeID]
);
} else {
displayError(400, 'Invalid action');
}
}
unset($pathIDs);
}
/**
* Save the mapped value for a row in the Config table.
*
* @param string $configID The Config.ID value
* @param string $value The right-hand mapped value
*
* @return void
*/
function saveMappingValue($configID, $value): void
{
$factory = \NDB_Factory::singleton();
$DB = $factory->database();
$DB->delete('ConfigMappings', ['ConfigID' => $configID]);
$DB->unsafeInsert(
'ConfigMappings',
[
'ConfigID' => $configID,
'Value' => $value,
]
);
}
/**
* Check Duplicate value
*
* @param string $key The value of the key
* @param string $value The value of the value
*
* @return bool $result
*/
function isDuplicate($key, $value): bool
{
$factory = \NDB_Factory::singleton();
$DB = $factory->database();
$result = $DB->pselectOne(
"SELECT COUNT(*) FROM Config WHERE ConfigID =:ConfigID AND Value =:Value",
[
'ConfigID' => $key,
'Value' => $value,
]
);
return intval($result) > 0;
}
/**
* Check dropdown list Duplicate value
*
* @param string $id The value of the id
* @param string $value The value of the value
*
* @return boolean return true if there is no Duplicate value
*/
function noDuplicateInDropdown($id,$value)
{
$factory = \NDB_Factory::singleton();
$DB = $factory->database();
// ConfigID can be found in the Config table by searching new id.
$ConfigID = $DB->pselectOne(
"SELECT ConfigID FROM Config WHERE ID =:ID",
['ID' => $id]
);
// IDBefore means that row ID contains the same configID and value pair.
$IDBefore = $DB->pselectOne(
"SELECT ID FROM Config WHERE ConfigID =:ConfigID AND Value =:Value",
[
'ConfigID' => $ConfigID,
'Value' => $value,
]
);
//If the new "id" equals "IDBefore" in Config table means
//it can be updated in the table. Otherwise, it means Dropdown menu has
// already had the same configID and value pair.
//If the "IDBefore" is empty in the table means
//we can insert the new configID and value pair into the table.Otherwise,
// it means Dropdown menu has already had the same configID and value pair.
return ($id == $IDBefore || $IDBefore == null);
}
/**
* Query DB for config settings that correspond to filepaths.
* Depending on the context, either the Config or ConfigSettings table can be
* used. This is determined by what IDs the code currently has access to.
*
* @param string $table 'Config' or 'ConfigSettings'.
*
* @return array IDs corresponding to path settings.
*/
function getPathIDs(string $table): array
{
if (! in_array($table, ['Config', 'ConfigSettings', true])) {
throw new \LorisException('Table must be "Config" or "ConfigSettings"');
}
$query = '';
switch ($table) {
case 'Config':
/* Query adapated from:
* https://github.com/aces/Loris/wiki/Project-Customization
*/
$query = "SELECT c.ID FROM Config c "
. "LEFT JOIN ConfigSettings cs ON (c.ConfigID = cs.ID) "
. "JOIN ConfigSettings csp ON (cs.Parent = csp.ID) "
. "WHERE cs.DataType = 'web_path';";
break;
case 'ConfigSettings':
$query = "SELECT ID FROM ConfigSettings "
. "WHERE DataType = 'web_path';";
break;
}
return \NDB_Factory::singleton()->database()->pselectCol($query, []);
}
/**
* Return a json-encoded error message and response code when an error occurs
*
* @param int $code A valid HTTP Response Code.
* @param string $msg Error text to display.
*
* @return void
*/
function displayError(int $code, string $msg): void
{
// Display error message and return
http_response_code($code);
echo "ERROR: $msg";
}
/**
* Ensures that the passed value is a readable directory.
*
* @param string $value The value to test.
*
* @return bool Whether the value is a readable directory.
*/
function validPath($value)
{
if (! (is_dir($value) && is_readable($value))) {
return false;
}
return true;
}