/
global.php
566 lines (501 loc) · 16.2 KB
/
global.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
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
<?php
/*
require(__DIR__ . (disabled) "/locale.php");
require(__DIR__ . (disabled) "/standard.php");
*/
require(__DIR__ . "/../vendor/autoload.php");
require(__DIR__ . "/classes.php");
require(__DIR__ . "/config.php");
define('LIGHTOPENID_TIMEOUT', get_site_config('get_openid_timeout') * 1000);
// set up (db, page) metrics
// (need to do this before performance_metrics_page_start())
Openclerk\MetricsHandler::init(db());
// before loading sessions
require(__DIR__ . "/performance.php");
performance_metrics_page_start();
require(__DIR__ . "/security.php");
require(__DIR__ . "/email.php");
require(__DIR__ . "/crypto.php");
require(__DIR__ . "/premium.php");
require(__DIR__ . "/heavy.php");
require(__DIR__ . "/kb.php");
require(__DIR__ . "/countries.php");
require(__DIR__ . "/routes.php");
// issue #152: support i18n
require(__DIR__ . "/i18n.php");
$db_instance = null;
function db() {
global $db_instance;
if ($db_instance === null) {
if (config("database_slave")) {
$db_instance = new \Db\ReplicatedConnection(
config("database_host_master"),
config("database_host_slave"),
config("database_name"),
config("database_username"),
config("database_password"),
config("database_port"),
config("database_timezone")
);
} else {
$db_instance = new \Db\SoloConnection(
config("database_name"),
config("database_username"),
config("database_password"),
config("database_host_master"),
config("database_port"),
config("database_timezone")
);
}
}
return $db_instance;
}
function db_master() {
return db()->getMaster();
}
function db_slave() {
return db()->getSlave();
}
function require_get($key, $default = null) {
if (isset($_GET[$key])) {
return $_GET[$key];
} else if ($default !== null) {
return $default;
} else {
throw new Exception("Required get parameter '$key' not available");
}
}
function require_post($key, $default = null) {
if (isset($_POST[$key])) {
return $_POST[$key];
} else if ($default !== null) {
return $default;
} else {
throw new Exception("Required post parameter '$key' not available");
}
}
function require_session($key, $default = null) {
if (isset($_SESSION[$key])) {
return $_SESSION[$key];
} else if ($default !== null) {
return $default;
} else {
throw new Exception("Required session parameter '$key' not available");
}
}
function redirect($url) {
if (strpos($url, "\n") !== false) {
throw new Exception("Invalid multiline URL '$url'");
}
header('Location: ' . $url);
die();
}
function xml_header() {
header("Content-Type: text/xml");
echo "<" . "?" . "xml version=\"1.0\"" . "?" . ">\n";
}
function iso_date($date = null) {
if ($date == null)
return date('c');
elseif (is_numeric($date))
return date('c', $date);
else
return date('c', strtotime($date));
}
/**
* Format the given date in a format suitable for MySQL. If null, returns the current date.
*/
function db_date($date = null) {
$format = 'Y-m-d H:i:s'; // 2010-01-01 01:01:01, i.e. no timezone data. TODO assumes that the database is in the same timezone as the app
if ($date == null)
return date($format);
elseif (is_numeric($date))
return date($format, $date);
else
return date($format, strtotime($date));
}
function array_join($a1, $a2) {
if (!is_array($a2))
throw new InvalidArgumentException("Argument '$a2' is not an array");
foreach ($a2 as $value) {
$a1[] = $value;
}
return $a1;
}
/**
* Returns {@code true} if the two arrays have the same values, in any order.
* @param $strict if {@code true}, then search will be via identity (===)
*/
function array_equals($a, $b, $strict = false) {
foreach ($a as $aa) {
if (($key = array_search($aa, $b, $strict)) !== false) {
unset($b[$key]);
} else {
return false; // we found a key in $a that isn't in $b
}
}
if (!$b) {
// all of $b was in $a, so the arrays are equal
return true;
} else {
return false;
}
}
function recent_format($date = null, $suffix = false, $future_suffix = false) {
if ($date == null || $date == 0)
return "<em>" . t("never") . "</em>";
if (!is_numeric($date))
$date = strtotime($date);
$secs = time() - $date;
if ($secs == 0) {
return "<em>" . ht("now") . "</em>";
} elseif ($secs < 0) {
if ($future_suffix === false) {
return t(":time in the future", array(':time' => seconds_to_string(-$secs)));
} else if ($future_suffix === "") {
return seconds_to_string(-$secs);
} else {
// this form shouldn't be used
return seconds_to_string(-$secs) . $future_suffix;
}
} else {
if ($suffix === false) {
return t(":time ago", array(':time' => seconds_to_string($secs)));
} else if ($future_suffix === "") {
return seconds_to_string($secs);
} else {
// this form shouldn't be used
return seconds_to_string($secs) . $suffix;
}
}
}
function seconds_to_string($secs) {
if ($secs == 0)
return "<em>" . ht("now") . "</em>";
else if ($secs < 60)
return plural("sec", "sec", ($secs));
else if ($secs < 60 * 60)
return plural("min", "min", ($secs / 60));
else if ($secs < (60 * 60 * 24))
return plural("hour", "hours", ($secs / (60 * 60)));
else if ($secs < (60 * 60 * 24 * 31))
return plural("day", "days", ($secs / (60 * 60 * 24)));
else if (year_count($secs) < 1)
return plural("month", "months", (int) ($secs / (60 * 60 * 24 * (365.242/12))));
else
return plural("year", "years", (year_count($secs)), 1);
}
function recent_format_html($date, $suffix = false, $future_suffix = false) {
return '<span title="' . ($date ? htmlspecialchars(iso_date($date)) : ht("Never")) . '">' . recent_format($date, $suffix, $future_suffix) . '</span>';
}
function expected_delay_html($minutes) {
if ($minutes == 0) {
return "<i>" . ht("none") . "</i>";
} else if ($minutes < 60) {
return "< " . plural("min", ceil($minutes));
} else if ($minutes < (60 * 60)) {
return "< " . plural("hour", ceil($minutes / 60));
} else {
return "< " . plural("day", ceil($minutes / (60 * 60)));
}
}
function year_count($sec) {
return $sec / (60 * 60 * 24 * 365.242);
}
/**
* Translates an array into e.g.:
* 'a'
* 'a and b'
* 'a, b and c'
* 'a, b, c and d'
*/
function implode_english($result, $or = false) {
$s = "";
for ($i = 0; $i < count($result) - 2; $i++) {
$s .= $result[$i] . ", ";
}
for ($i = count($result) - 2; $i >= 0 && $i < count($result) - 1; $i++) {
$s .= $result[$i] .
((count($result) > 2 && strpos($result[$i], " ")) !== false ? "," : "") . // for phrased terms and long lists, add an extra comma
" " . ($or ? "or" : "and") . " ";
}
for ($i = count($result) - 1; $i >= 0 && $i < count($result); $i++) {
$s .= $result[$i];
}
return $s;
}
function capitalize($s) {
$split = explode(" ", $s);
foreach ($split as $i => $value) {
$split[$i] = strtoupper(substr($value, 0, 1)) . substr($value, 1);
}
return implode(" ", $split);
}
/**
* Wrap the given number to the given number of decimal places.
* Probably returns 0 if this is not a number.
*/
function wrap_number($n, $dp) {
return number_format($n, $dp, ".", "");
}
/**
* Escape the given XML string.
*/
function xmlescape($str) {
// TODO implement
return $str;
}
/**
* Return a string with all " characters encoded.
* {@code addslashes()} just quotes ALL special characters (including '), which is not suitable
* for encoding a PHP string.
*/
function phpescapestring($s) {
return str_replace("\"", "\\\"", $s);
}
/**
* Display an XML error.
*/
function display_xml_error($e) {
xml_header();
?>
<error time="<?php echo iso_date(); ?>">
<?php echo xmlescape($e->getMessage()); ?>
</error>
<?php
die();
}
/**
* Can be cached.
*/
$global_calculate_relative_path = null;
function calculate_relative_path() {
global $global_calculate_relative_path;
if ($global_calculate_relative_path === null) {
// construct a relative path for this request based on the request URI, but only if it is set
if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] && !defined('FORCE_NO_RELATIVE')) {
$uri = $_SERVER['REQUEST_URI'];
// strip out the hostname from the absolute_url
$intended = substr(get_site_config('absolute_url'), strpos(get_site_config('absolute_url'), '://') + 4);
$intended = substr($intended, strpos($intended, '/'));
// if we're in this path, remove it
// now generate ../s as necessary
if (strtolower(substr($uri, 0, strlen($intended))) == strtolower($intended)) {
$uri = substr($uri, strlen($intended));
}
// but strip out any parameters, which might have /s in them, which will completely mess this up
// (see issue #13)
if (strpos($uri, "?") !== false) {
$uri = substr($uri, 0, strpos($uri, "?"));
}
$global_calculate_relative_path = str_repeat('../', substr_count($uri, '/'));
} else {
$global_calculate_relative_path = "";
}
}
return $global_calculate_relative_path;
}
function link_to($url, $text = false) {
if ($text === false) {
return link_to($url, $url);
}
return "<a href=\"" . htmlspecialchars($url) . "\">" . htmlspecialchars($text) . "</a>";
}
/**
* Returns the current request URL along with hostname and $_GET parameters.
*/
function request_url() {
return ((isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") ? "https" : "http") . "://" .
(isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : $_SERVER['SERVER_ADDR']) .
$_SERVER["REQUEST_URI"];
}
/**
* Returns the current request path without any hostname or $_GET parameters.
* Returns the current request URL along with $_GET parameters, relative to
* {@code get_site_config('absolute_url')}.
*/
function request_url_relative() {
$url = str_replace("https://", "http://", request_url());
if (strpos($url, "?") !== false) {
$url = substr($url, 0, strpos($url, "?"));
}
$absolute = str_replace("https://", "http://", get_site_config('absolute_url'));
$result = str_replace($absolute, "", $url);
if (!$result) {
$result = "index";
}
return $result;
}
/**
* Uses PHP's filter_var() to validate e-mail addresses, and also ensures the e-mail address
* is shorter than 255 characters (limit in our database for e-mail addresses).
*
* NOTE this requires MySQL 5+ which uses VARCHAR(32) to define 32 characters, not 32 bytes,
* therefore we can use mb_strlen().
*
* To support UTF-8 email addresses, we aren't too picky about edge cases; if it looks like
* an e-mail address, accept it.
*/
function is_valid_email($e) {
return mb_strlen($e) <= 255 && preg_match("/^[^@ ]+@([^@ ]+\\.)+[^@ ]+$/u", $e);
}
function is_valid_url($e) {
$e = mb_strtolower($e);
return mb_strlen($e) <= 255 &&
(mb_substr($e, 0, mb_strlen("http://")) == "http://" || mb_substr($e, 0, mb_strlen("https://")) == "https://");
}
/**
* Is this user a 'new user' w.r.t. {@code new_user_premium_update_hours}?
*/
function user_is_new($user) {
return get_site_config('new_user_premium_update_hours') && strtotime($user['created_at']) > strtotime('-' . get_site_config('new_user_premium_update_hours') . ' hour');
}
/**
* Generate a random key of the specified length. This key needs to be
* alphanumeric. Case-sensitivity is not specified.
*/
function generate_key($length = 32) {
$new_password = "";
for ($i = 0; $i < $length; $i++) {
$new_password .= sprintf("%01x", rand(0,0xf));
}
return $new_password;
}
function get_openid_host() { return get_site_config('openid_host'); }
// from http://php.net/manual/en/function.stats-standard-deviation.php
function stdev($aValues, $bSample = false) {
$fMean = array_sum($aValues) / count($aValues);
$fVariance = 0.0;
foreach ($aValues as $i) {
$fVariance += pow($i - $fMean, 2);
}
$fVariance /= ( $bSample ? count($aValues) - 1 : count($aValues) );
return (float) sqrt($fVariance);
}
function number_format_precision($n, $precision) {
// if we have 100.x, we only want $precision = 6
if ($n > 1) {
$precision -= (log($n) / log(10) - 1);
}
return number_format_autoprecision($n, $precision);
}
/**
* Format a number to the lowest precision that's necessary, to a maximum of the
* given precision.
*/
function number_format_autoprecision($n, $precision = 8, $dec_point = ".", $thousands_sep = ",") {
if (!is_numeric($n) && $n /* anything falsey is okay to be numeric */ && is_localhost()) {
throw new Exception("'$n' is not numeric");
}
// find the lowest precision that we need
for ($i = 0; $i < $precision - 1; $i++) {
if (number_format($n, (int) $i, ".", "") == $n) {
$precision = (int) $i;
break;
}
}
return number_format($n, $precision, $dec_point, $thousands_sep);
}
/**
* Format a number to a human readable amount of precision.
*/
function number_format_human($n, $extra_precision = 0) {
if (abs($n) < 1e-4) {
return number_format_autoprecision($n, 8 + $extra_precision, '.', '');
} else if (abs($n) < 1e-2) {
return number_format_autoprecision($n, 6 + $extra_precision, '.', '');
} else if (abs($n) < 1e4) {
return number_format_autoprecision($n, 4 + $extra_precision, '.', '');
} else if (abs($n) < 1e6) {
return number_format_autoprecision($n, 2 + $extra_precision, '.', '');
} else {
return number_format_autoprecision($n, 0 + $extra_precision, '.', '');
}
}
// remove any commas; intended to be reverse of number_format()
function number_unformat($value) {
return str_replace(",", "", $value);
}
/**
* Tag the current page as one that can be cached by the client;
* sets Expires, Cache-Control etc headers.
*
* <p>Doesn't do anything with 304 Not Modified.
*
* <p>Uses {@code default_cache_seconds} seconds as a default cache period.
*/
function allow_cache($seconds = false) {
if ($seconds === false) {
$seconds = get_site_config('default_cache_seconds');
}
$gmdate = 'D, d M Y H:i:s';
header('Cache-Control: private'); // may only be cached in private cache.
header('Pragma: private');
header('Last-Modified: ' . gmdate($gmdate, time()) . ' GMT');
header('Expires: ' . gmdate($gmdate, time() + $seconds) . ' GMT');
}
/**
* @return the error message back
*/
function log_error($error) {
// TODO send an email, or insert something into the database
// for now, just echo something
echo '<div class="error">Error: ' . htmlspecialchars($error) . '</div>';
return $error;
}
class ServiceException extends Exception { }
class WebException extends Exception { }
class IllegalArgumentException extends Exception { }
function is_localhost() {
return $_SERVER['SERVER_NAME'] === "localhost" ||
$_SERVER['SERVER_NAME'] === "localhost.openclerk.org";
}
function set_temporary_messages($m) {
if (defined('NO_SESSION')) {
if ($m === null) {
// does nothing
return false;
}
throw new Exception("Cannot set temporary messages with no session");
}
if ($m === null) {
unset($_SESSION["temporary_messages"]);
} else {
if (!is_array($m))
$m = array($m);
$_SESSION["temporary_messages"] = $m;
}
}
$global_temporary_messages = isset($_SESSION["temporary_messages"]) ? $_SESSION["temporary_messages"] : null; // only lasts a single request
set_temporary_messages(null); // reset
function get_temporary_messages() {
global $global_temporary_messages;
return $global_temporary_messages === null ? array() : $global_temporary_messages;
}
function set_temporary_errors($m) {
if (defined('NO_SESSION')) {
if ($m === null) {
// does nothing
return false;
}
throw new Exception("Cannot set temporary errors with no session");
}
if ($m === null) {
unset($_SESSION["temporary_errors"]);
} else {
if (!is_array($m))
$m = array($m);
$_SESSION["temporary_errors"] = $m;
}
}
$global_temporary_errors = isset($_SESSION["temporary_errors"]) ? $_SESSION["temporary_errors"] : null; // only lasts a single request
set_temporary_errors(null); // reset
function get_temporary_errors() {
global $global_temporary_errors;
return $global_temporary_errors === null ? array() : $global_temporary_errors;
}
class EscapedException extends Exception { }
function safe_include_arg($arg) {
// take out any relative paths etc
return preg_replace("/[^a-z0-9_\-]/i", "", $arg);
}