Current oav website

This commit is contained in:
Charlie Root
2023-03-20 12:18:38 +01:00
commit a096ce07cf
3270 changed files with 261778 additions and 0 deletions

View File

@ -0,0 +1,43 @@
<?php
/**
* @package Clearbricks
* @subpackage Common
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
define('CLEARBRICKS_VERSION', '1.1');
# Autoload
$__autoload = [
'crypt' => dirname(__FILE__) . '/lib.crypt.php',
'dt' => dirname(__FILE__) . '/lib.date.php',
'files' => dirname(__FILE__) . '/lib.files.php',
'path' => dirname(__FILE__) . '/lib.files.php',
'form' => dirname(__FILE__) . '/lib.form.php',
'formSelectOption' => dirname(__FILE__) . '/lib.form.php',
'html' => dirname(__FILE__) . '/lib.html.php',
'http' => dirname(__FILE__) . '/lib.http.php',
'text' => dirname(__FILE__) . '/lib.text.php'
];
# autoload for clearbricks
function cb_autoload($name)
{
global $__autoload;
if (isset($__autoload[$name])) {
require_once $__autoload[$name];
}
}
spl_autoload_register("cb_autoload");
# We only need l10n __() function
require_once dirname(__FILE__) . '/lib.l10n.php';
# We set default timezone to avoid warning
dt::setTZ('UTC');
# JSON functions for PHP < 5.2 or PHP > 5.2 compiling without json
require_once dirname(__FILE__) . '/lib.json.php';

View File

@ -0,0 +1,79 @@
<?php
/**
* @class crypt
* @brief Functions to handle passwords or sensitive data
*
* @package Clearbricks
* @subpackage Common
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
class crypt
{
/**
* SHA1 or MD5 + HMAC
*
* Returns an HMAC encoded value of <var>$data</var>, using the said <var>$key</var>
* and <var>$hashfunc</var> as hash method (sha1 or md5 are accepted if hash_hmac function not exists.)
*
* @param string $key Hash key
* @param string $data Data
* @param string $hashfunc Hash function (md5 or sha1)
* @return string
*/
public static function hmac($key, $data, $hashfunc = 'sha1')
{
if (function_exists('hash_hmac')) {
if (!in_array($hashfunc, hash_algos())) {
$hashfunc = 'sha1';
}
return hash_hmac($hashfunc, $data, $key);
}
return self::hmac_legacy($key, $data, $hashfunc);
}
public static function hmac_legacy($key, $data, $hashfunc = 'sha1')
{
// Legacy way
if ($hashfunc != 'sha1') {
$hashfunc = 'md5';
}
$blocksize = 64;
if (strlen($key) > $blocksize) {
$key = pack('H*', $hashfunc($key));
}
$key = str_pad($key, $blocksize, chr(0x00));
$ipad = str_repeat(chr(0x36), $blocksize);
$opad = str_repeat(chr(0x5c), $blocksize);
$hmac = pack('H*', $hashfunc(($key ^ $opad) . pack('H*', $hashfunc(($key ^ $ipad) . $data))));
return bin2hex($hmac);
}
/**
* Password generator
*
* Returns an n characters random password.
*
* @param integer $length required length
* @return string
*/
public static function createPassword($length = 8)
{
$pwd = [];
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
$chars2 = '$!@';
for ($i = 0; $i < $length; $i++) {
$pwd[] = $chars[rand(0, strlen($chars) - 1)];
}
for ($j = 0; $j < (int)($length / 4); $j++) {
$pos = rand(0, 3) + 4 * $j;
$pwd[$pos] = $chars2[rand(0, strlen($chars2) - 1)];
}
return implode('', $pwd);
}
}

View File

@ -0,0 +1,288 @@
<?php
/**
* @class dt
* @brief Date/time utilities
*
* @package Clearbricks
* @subpackage Common
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
class dt
{
private static $timezones = null;
/**
* Timestamp formating
*
* Returns a date formated like PHP <a href="http://www.php.net/manual/en/function.strftime.php">strftime</a>
* function.
* Special cases %a, %A, %b and %B are handled by {@link l10n} library.
*
* @param string $p Format pattern
* @param integer $ts Timestamp
* @param string $tz Timezone
* @return string
*/
public static function str($p, $ts = null, $tz = null)
{
if ($ts === null) {$ts = time();}
$hash = '799b4e471dc78154865706469d23d512';
$p = preg_replace('/(?<!%)%(a|A)/', '{{' . $hash . '__$1%w__}}', $p);
$p = preg_replace('/(?<!%)%(b|B)/', '{{' . $hash . '__$1%m__}}', $p);
if ($tz) {
$T = self::getTZ();
self::setTZ($tz);
}
$res = strftime($p, $ts);
if ($tz) {
self::setTZ($T);
}
$res = preg_replace_callback('/{{' . $hash . '__(a|A|b|B)([0-9]{1,2})__}}/', ['self', '_callback'], $res);
return $res;
}
/**
* Date to date
*
* Format a literal date to another literal date.
*
* @param string $p Format pattern
* @param string $dt Date
* @param string $tz Timezone
* @return string
*/
public static function dt2str($p, $dt, $tz = null)
{
return dt::str($p, strtotime($dt), $tz);
}
/**
* ISO-8601 formatting
*
* Returns a timestamp converted to ISO-8601 format.
*
* @param integer $ts Timestamp
* @param string $tz Timezone
* @return string
*/
public static function iso8601($ts, $tz = 'UTC')
{
$o = self::getTimeOffset($tz, $ts);
$of = sprintf('%02u:%02u', abs($o) / 3600, (abs($o) % 3600) / 60);
return date('Y-m-d\\TH:i:s', $ts) . ($o < 0 ? '-' : '+') . $of;
}
/**
* RFC-822 formatting
*
* Returns a timestamp converted to RFC-822 format.
*
* @param integer $ts Timestamp
* @param string $tz Timezone
* @return string
*/
public static function rfc822($ts, $tz = 'UTC')
{
# Get offset
$o = self::getTimeOffset($tz, $ts);
$of = sprintf('%02u%02u', abs($o) / 3600, (abs($o) % 3600) / 60);
return strftime('%a, %d %b %Y %H:%M:%S ' . ($o < 0 ? '-' : '+') . $of, $ts);
}
/**
* Timezone set
*
* Set timezone during script execution.
*
* @param string $tz Timezone
*/
public static function setTZ($tz)
{
if (function_exists('date_default_timezone_set')) {
date_default_timezone_set($tz);
return;
}
if (!ini_get('safe_mode')) {
putenv('TZ=' . $tz);
}
}
/**
* Current timezone
*
* Returns current timezone.
*
* @return string
*/
public static function getTZ()
{
if (function_exists('date_default_timezone_get')) {
return date_default_timezone_get();
}
return date('T');
}
/**
* Time offset
*
* Get time offset for a timezone and an optionnal $ts timestamp.
*
* @param string $tz Timezone
* @param integer $ts Timestamp
* @return integer
*/
public static function getTimeOffset($tz, $ts = false)
{
if (!$ts) {
$ts = time();
}
$server_tz = self::getTZ();
$server_offset = date('Z', $ts);
self::setTZ($tz);
$cur_offset = date('Z', $ts);
self::setTZ($server_tz);
return $cur_offset - $server_offset;
}
/**
* UTC conversion
*
* Returns any timestamp from current timezone to UTC timestamp.
*
* @param integer $ts Timestamp
* @return integer
*/
public static function toUTC($ts)
{
return $ts + self::getTimeOffset('UTC', $ts);
}
/**
* Add timezone
*
* Returns a timestamp with its timezone offset.
*
* @param string $tz Timezone
* @param integer $ts Timestamp
* @return integer
*/
public static function addTimeZone($tz, $ts = false)
{
if ($ts === false) {
$ts = time();
}
return $ts + self::getTimeOffset($tz, $ts);
}
/**
* Timzones
*
* Returns an array of supported timezones, codes are keys and names are values.
*
* @param boolean $flip Names are keys and codes are values
* @param boolean $groups Return timezones in arrays of continents
* @return array
*/
public static function getZones($flip = false, $groups = false)
{
if (is_null(self::$timezones)) {
// Read timezones from file
if (!is_readable($f = dirname(__FILE__) . '/tz.dat')) {
return [];
}
$tz = file(dirname(__FILE__) . '/tz.dat');
$res = [];
foreach ($tz as $v) {
$v = trim($v);
if ($v) {
$res[$v] = str_replace('_', ' ', $v);
}
}
// Store timezones for further accesses
self::$timezones = $res;
} else {
// Timezones already read from file
$res = self::$timezones;
}
if ($flip) {
$res = array_flip($res);
if ($groups) {
$tmp = [];
foreach ($res as $k => $v) {
$g = explode('/', $k);
$tmp[$g[0]][$k] = $v;
}
$res = $tmp;
}
}
return $res;
}
private static function _callback($args)
{
$b = [
1 => '_Jan',
2 => '_Feb',
3 => '_Mar',
4 => '_Apr',
5 => '_May',
6 => '_Jun',
7 => '_Jul',
8 => '_Aug',
9 => '_Sep',
10 => '_Oct',
11 => '_Nov',
12 => '_Dec'];
$B = [
1 => 'January',
2 => 'February',
3 => 'March',
4 => 'April',
5 => 'May',
6 => 'June',
7 => 'July',
8 => 'August',
9 => 'September',
10 => 'October',
11 => 'November',
12 => 'December'];
$a = [
1 => '_Mon',
2 => '_Tue',
3 => '_Wed',
4 => '_Thu',
5 => '_Fri',
6 => '_Sat',
0 => '_Sun'];
$A = [
1 => 'Monday',
2 => 'Tuesday',
3 => 'Wednesday',
4 => 'Thursday',
5 => 'Friday',
6 => 'Saturday',
0 => 'Sunday'];
return __(${$args[1]}[(integer) $args[2]]);
}
}

View File

@ -0,0 +1,640 @@
<?php
/**
* @class files
* @brief Files manipulation utilities
*
* @package Clearbricks
* @subpackage Common
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
class files
{
public static $dir_mode = null; ///< Default directories mode
public static $mimeType = ///< MIME types
[
'odt' => 'application/vnd.oasis.opendocument.text',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'sxw' => 'application/vnd.sun.xml.writer',
'sxc' => 'application/vnd.sun.xml.calc',
'sxi' => 'application/vnd.sun.xml.impress',
'ppt' => 'application/mspowerpoint',
'doc' => 'application/msword',
'xls' => 'application/msexcel',
'rtf' => 'application/rtf',
'pdf' => 'application/pdf',
'ps' => 'application/postscript',
'ai' => 'application/postscript',
'eps' => 'application/postscript',
'json' => 'application/json',
'xml' => 'application/xml',
'bin' => 'application/octet-stream',
'exe' => 'application/octet-stream',
'bz2' => 'application/x-bzip',
'deb' => 'application/x-debian-package',
'gz' => 'application/x-gzip',
'jar' => 'application/x-java-archive',
'rar' => 'application/rar',
'rpm' => 'application/x-redhat-package-manager',
'tar' => 'application/x-tar',
'tgz' => 'application/x-gtar',
'zip' => 'application/zip',
'aiff' => 'audio/x-aiff',
'ua' => 'audio/basic',
'mp3' => 'audio/mpeg3',
'mid' => 'audio/x-midi',
'midi' => 'audio/x-midi',
'ogg' => 'application/ogg',
'ra' => 'audio/x-pn-realaudio',
'ram' => 'audio/x-pn-realaudio',
'wav' => 'audio/x-wav',
'wma' => 'audio/x-ms-wma',
'swf' => 'application/x-shockwave-flash',
'swfl' => 'application/x-shockwave-flash',
'js' => 'application/javascript',
'bmp' => 'image/bmp',
'gif' => 'image/gif',
'ico' => 'image/vnd.microsoft.icon',
'jpeg' => 'image/jpeg',
'jpg' => 'image/jpeg',
'jpe' => 'image/jpeg',
'png' => 'image/png',
'svg' => 'image/svg+xml',
'tiff' => 'image/tiff',
'tif' => 'image/tiff',
'webp' => 'image/webp',
'xbm' => 'image/x-xbitmap',
'css' => 'text/css',
'csv' => 'text/csv',
'html' => 'text/html',
'htm' => 'text/html',
'txt' => 'text/plain',
'rtf' => 'text/richtext',
'rtx' => 'text/richtext',
'mpg' => 'video/mpeg',
'mpeg' => 'video/mpeg',
'mpe' => 'video/mpeg',
'ogv' => 'video/ogg',
'viv' => 'video/vnd.vivo',
'vivo' => 'video/vnd.vivo',
'qt' => 'video/quicktime',
'mov' => 'video/quicktime',
'mp4' => 'video/mp4',
'm4v' => 'video/x-m4v',
'flv' => 'video/x-flv',
'avi' => 'video/x-msvideo',
'wmv' => 'video/x-ms-wmv'
];
/**
* Directory scanning
*
* Returns a directory child files and directories.
*
* @param string $d Path to scan
* @param boolean $order Order results
* @return array
*/
public static function scandir($d, $order = true)
{
$res = [];
$dh = @opendir($d);
if ($dh === false) {
throw new Exception(__('Unable to open directory.'));
}
while (($f = readdir($dh)) !== false) {
$res[] = $f;
}
closedir($dh);
if ($order) {
sort($res);
}
return $res;
}
/**
* File extension
*
* Returns a file extension.
*
* @param string $f File name
* @return string
*/
public static function getExtension($f)
{
if (function_exists('pathinfo')) {
return strtolower(pathinfo($f, PATHINFO_EXTENSION));
} else {
$f = explode('.', basename($f));
if (count($f) <= 1) {return '';}
return strtolower($f[count($f) - 1]);
}
}
/**
* MIME type
*
* Returns a file MIME type, based on static var {@link $mimeType}
*
* @param string $f File name
* @return string
*/
public static function getMimeType($f)
{
$ext = self::getExtension($f);
$types = self::mimeTypes();
if (isset($types[$ext])) {
return $types[$ext];
} else {
return 'application/octet-stream';
}
}
/**
* MIME types
*
* Returns all defined MIME types.
*
* @return array
*/
public static function mimeTypes()
{
return self::$mimeType;
}
/**
* New MIME types
*
* Append new MIME types to defined MIME types.
*
* @param array $tab New MIME types.
*/
public static function registerMimeTypes($tab)
{
self::$mimeType = array_merge(self::$mimeType, $tab);
}
/**
* Is a file or directory deletable.
*
* Returns true if $f is a file or directory and is deletable.
*
* @param string $f File or directory
* @return boolean
*/
public static function isDeletable($f)
{
if (is_file($f)) {
return is_writable(dirname($f));
} elseif (is_dir($f)) {
return (is_writable(dirname($f)) && count(files::scandir($f)) <= 2);
}
return false;
}
/**
* Recursive removal
*
* Remove recursively a directory.
*
* @param string $dir Directory patch
* @return boolean
*/
public static function deltree($dir)
{
$current_dir = opendir($dir);
while ($entryname = readdir($current_dir)) {
if (is_dir($dir . '/' . $entryname) and ($entryname != '.' and $entryname != '..')) {
if (!files::deltree($dir . '/' . $entryname)) {
return false;
}
} elseif ($entryname != '.' and $entryname != '..') {
if (!@unlink($dir . '/' . $entryname)) {
return false;
}
}
}
closedir($current_dir);
return @rmdir($dir);
}
/**
* Touch file
*
* Set file modification time to now.
*
* @param string $f File to change
*/
public static function touch($f)
{
if (is_writable($f)) {
if (function_exists('touch')) {
@touch($f);
} else {
# Very bad hack
@file_put_contents($f, file_get_contents($f));
}
}
}
/**
* Directory creation.
*
* Creates directory $f. If $r is true, attempts to create needed parents
* directories.
*
* @param string $f Directory to create
* @param boolean $r Create parent directories
*/
public static function makeDir($f, $r = false)
{
if (empty($f)) {
return;
}
if (DIRECTORY_SEPARATOR == '\\') {
$f = str_replace('/', '\\', $f);
}
if (is_dir($f)) {
return;
}
if ($r) {
$dir = path::real($f, false);
$dirs = [];
while (!is_dir($dir)) {
array_unshift($dirs, basename($dir));
$dir = dirname($dir);
}
foreach ($dirs as $d) {
$dir .= DIRECTORY_SEPARATOR . $d;
if ($d != '' && !is_dir($dir)) {
self::makeDir($dir);
}
}
} else {
if (@mkdir($f) === false) {
throw new Exception(__('Unable to create directory.'));
}
self::inheritChmod($f);
}
}
/**
* Mode inheritage
*
* Sets file or directory mode according to its parent.
*
* @param string $file File to change
*/
public static function inheritChmod($file)
{
if (!function_exists('fileperms') || !function_exists('chmod')) {
return false;
}
if (self::$dir_mode != null) {
return @chmod($file, self::$dir_mode);
} else {
return @chmod($file, fileperms(dirname($file)));
}
}
/**
* Changes file content.
*
* Writes $f_content into $f file.
*
* @param string $f File to edit
* @param string $f_content Content to write
*/
public static function putContent($f, $f_content)
{
if (file_exists($f) && !is_writable($f)) {
throw new Exception(__('File is not writable.'));
}
$fp = @fopen($f, 'w');
if ($fp === false) {
throw new Exception(__('Unable to open file.'));
}
fwrite($fp, $f_content, strlen($f_content));
fclose($fp);
return true;
}
/**
* Human readable file size.
*
* @param integer $size Bytes
* @return string
*/
public static function size($size)
{
$kb = 1024;
$mb = 1024 * $kb;
$gb = 1024 * $mb;
$tb = 1024 * $gb;
if ($size < $kb) {
return $size . " B";
} else if ($size < $mb) {
return round($size / $kb, 2) . " KB";
} else if ($size < $gb) {
return round($size / $mb, 2) . " MB";
} else if ($size < $tb) {
return round($size / $gb, 2) . " GB";
} else {
return round($size / $tb, 2) . " TB";
}
}
/**
* Converts a human readable file size to bytes.
*
* @param string $v Size
* @return integer
*/
public static function str2bytes($v)
{
$v = trim($v);
$last = strtolower(substr($v, -1, 1));
$v = (float) substr($v, 0, -1);
switch ($last) {
case 'g':
$v *= 1024;
case 'm':
$v *= 1024;
case 'k':
$v *= 1024;
}
return $v;
}
/**
* Upload status
*
* Returns true if upload status is ok, throws an exception instead.
*
* @param array $file File array as found in $_FILES
* @return boolean
*/
public static function uploadStatus($file)
{
if (!isset($file['error'])) {
throw new Exception(__('Not an uploaded file.'));
}
switch ($file['error']) {
case UPLOAD_ERR_OK:
return true;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
throw new Exception(__('The uploaded file exceeds the maximum file size allowed.'));
return false;
case UPLOAD_ERR_PARTIAL:
throw new Exception(__('The uploaded file was only partially uploaded.'));
return false;
case UPLOAD_ERR_NO_FILE:
throw new Exception(__('No file was uploaded.'));
return false;
case UPLOAD_ERR_NO_TMP_DIR:
throw new Exception(__('Missing a temporary folder.'));
return false;
case UPLOAD_ERR_CANT_WRITE:
throw new Exception(__('Failed to write file to disk.'));
return false;
case UPLOAD_ERR_EXTENSION:
throw new Exception(__('A PHP extension stopped the file upload.'));
return false;
default:
return true;
}
}
# Packages generation methods
#
/**
* Recursive directory scanning
*
* Returns an array of a given directory's content. The array contains
* two arrays: dirs and files. Directory's content is fetched recursively.
*
* @param string $dirName Directory name
* @param array $contents Contents array. Leave it empty
* @return array
*/
public static function getDirList($dirName, &$contents = null)
{
if (!$contents) {
$contents = ['dirs' => [], 'files' => []];
}
$exclude_list = ['.', '..', '.svn'];
$dirName = preg_replace('|/$|', '', $dirName);
if (!is_dir($dirName)) {
throw new Exception(sprintf(__('%s is not a directory.'), $dirName));
}
$contents['dirs'][] = $dirName;
$d = @dir($dirName);
if ($d === false) {
throw new Exception(__('Unable to open directory.'));
}
while ($entry = $d->read()) {
if (!in_array($entry, $exclude_list)) {
if (is_dir($dirName . '/' . $entry)) {
files::getDirList($dirName . '/' . $entry, $contents);
} else {
$contents['files'][] = $dirName . '/' . $entry;
}
}
}
$d->close();
return $contents;
}
/**
* Filename cleanup
*
* Removes unwanted characters in a filename.
*
* @param string $n Filename
* @return string
*/
public static function tidyFileName($n)
{
$n = text::deaccent($n);
$n = preg_replace('/^[.]/u', '', $n);
return preg_replace('/[^A-Za-z0-9._-]/u', '_', $n);
}
}
/**
* @class path
* @brief Path manipulation utilities
*
* @package Clearbricks
* @subpackage Common
*/
class path
{
/**
* Returns the real path of a file.
*
* If parameter $strict is true, file should exist. Returns false if
* file does not exist.
*
* @param string $p Filename
* @param boolean $strict File should exists
* @return string
*/
public static function real($p, $strict = true)
{
$os = (DIRECTORY_SEPARATOR == '\\') ? 'win' : 'nix';
# Absolute path?
if ($os == 'win') {
$_abs = preg_match('/^\w+:/', $p);
} else {
$_abs = substr($p, 0, 1) == '/';
}
# Standard path form
if ($os == 'win') {
$p = str_replace('\\', '/', $p);
}
# Adding root if !$_abs
if (!$_abs) {
$p = dirname($_SERVER['SCRIPT_FILENAME']) . '/' . $p;
}
# Clean up
$p = preg_replace('|/+|', '/', $p);
if (strlen($p) > 1) {
$p = preg_replace('|/$|', '', $p);
}
$_start = '';
if ($os == 'win') {
list($_start, $p) = explode(':', $p);
$_start .= ':/';
} else {
$_start = '/';
}
$p = substr($p, 1);
# Go through
$P = explode('/', $p);
$res = [];
for ($i = 0; $i < count($P); $i++) {
if ($P[$i] == '.') {
continue;
}
if ($P[$i] == '..') {
if (count($res) > 0) {
array_pop($res);
}
} else {
array_push($res, $P[$i]);
}
}
$p = $_start . implode('/', $res);
if ($strict && !@file_exists($p)) {
return false;
}
return $p;
}
/**
* Returns a clean file path
*
* @param string $p File path
* @return string
*/
public static function clean($p)
{
$p = str_replace('..', '', $p);
$p = preg_replace('|/{2,}|', '/', $p);
$p = preg_replace('|/$|', '', $p);
return $p;
}
/**
* Path information
*
* Returns an array of information:
* - dirname
* - basename
* - extension
* - base (basename without extension)
*
* @param string $f File path
*/
public static function info($f)
{
$p = pathinfo($f);
$res = [];
$res['dirname'] = $p['dirname'];
$res['basename'] = $p['basename'];
$res['extension'] = isset($p['extension']) ? $p['extension'] : '';
$res['base'] = preg_replace('/\.' . preg_quote($res['extension'], '/') . '$/', '', $res['basename']);
return $res;
}
/**
* Full path with root
*
* Returns a path with root concatenation unless path begins with a slash
*
* @param string $p File path
* @param string $root Root path
* @return string
*/
public static function fullFromRoot($p, $root)
{
if (substr($p, 0, 1) == '/') {
return $p;
}
return $root . '/' . $p;
}
}

View File

@ -0,0 +1,746 @@
<?php
/**
* @class form
* @brief HTML Forms creation helpers
*
* @package Clearbricks
* @subpackage Common
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
class form
{
/**
* return id and name from given argument
*
* @param string|array $nid input argument
* @param string &$name returned name
* @param string &$id returned id
*
* @static
* @access private
*/
private static function getNameAndId($nid, &$name, &$id)
{
if (is_array($nid)) {
$name = $nid[0];
$id = !empty($nid[1]) ? $nid[1] : null;
} else {
$name = $id = $nid;
}
}
/**
* return an associative array of optional parameters of a class method
*
* @param string $class class name
* @param string $method method name
* @return array
*
* @static
* @access private
*/
private static function getDefaults($class, $method)
{
$options = [];
$reflect = new ReflectionMethod($class, $method);
foreach ($reflect->getParameters() as $param) {
if ($param->isOptional()) {
$options[$param->getName()] = $param->getDefaultValue();
}
}
return $options;
}
/**
* Select Box
*
* Returns HTML code for a select box.
* **$nid** could be a string or an array of name and ID.
* **$data** is an array with option titles keys and values in values
* or an array of object of type {@link formSelectOption}. If **$data** is an array of
* arrays, optgroups will be created.
*
* **$default** could be a string or an associative array of any of optional parameters:
*
* ```php
* form::combo(['name', 'id'], $data, ['class' => 'maximal', 'extra_html' => 'data-language="php"']);
* ```
*
* @uses formSelectOption
*
* @param string|array $nid Element ID and name
* @param mixed $data Select box data
* @param string|array $default Default value in select box | associative array of optional parameters
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
*
* @return string
*
* @static
*/
public static function combo($nid, $data, $default = '', $class = '', $tabindex = '',
$disabled = false, $extra_html = '') {
self::getNameAndId($nid, $name, $id);
if (func_num_args() > 2 && is_array($default)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
extract(array_merge($options, array_intersect_key($default, $options)));
}
return '<select name="' . $name . '" ' .
($id ? 'id="' . $id . '" ' : '') .
($class ? 'class="' . $class . '" ' : '') .
($tabindex ? 'tabindex="' . $tabindex . '" ' : '') .
($disabled ? 'disabled="disabled" ' : '') .
$extra_html .
'>' . "\n" .
self::comboOptions($data, $default) .
'</select>' . "\n";
}
private static function comboOptions($data, $default)
{
$res = '';
$option = '<option value="%1$s"%3$s>%2$s</option>' . "\n";
$optgroup = '<optgroup label="%1$s">' . "\n" . '%2$s' . "</optgroup>\n";
foreach ($data as $k => $v) {
if (is_array($v)) {
$res .= sprintf($optgroup, $k, self::comboOptions($v, $default));
} elseif ($v instanceof formSelectOption) {
$res .= $v->render($default);
} else {
$s = ((string) $v == (string) $default) ? ' selected="selected"' : '';
$res .= sprintf($option, $v, $k, $s);
}
}
return $res;
}
/**
* Radio button
*
* Returns HTML code for a radio button.
* $nid could be a string or an array of name and ID.
* $checked could be a boolean or an associative array of any of optional parameters
*
* @param string|array $nid Element ID and name
* @param string $value Element value
* @param boolean|array $checked True if checked | associative array of optional parameters
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
*
* @return string
*
* @static
*/
public static function radio($nid, $value, $checked = '', $class = '', $tabindex = '',
$disabled = false, $extra_html = '') {
self::getNameAndId($nid, $name, $id);
if (func_num_args() > 2 && is_array($checked)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
extract(array_merge($options, array_intersect_key($checked, $options)));
}
return '<input type="radio" name="' . $name . '" value="' . $value . '" ' .
($id ? 'id="' . $id . '" ' : '') .
($checked ? 'checked="checked" ' : '') .
($class ? 'class="' . $class . '" ' : '') .
($tabindex ? 'tabindex="' . $tabindex . '" ' : '') .
($disabled ? 'disabled="disabled" ' : '') .
$extra_html .
'/>' . "\n";
}
/**
* Checkbox
*
* Returns HTML code for a checkbox.
* $nid could be a string or an array of name and ID.
* $checked could be a boolean or an associative array of any of optional parameters
*
* @param string|array $nid Element ID and name
* @param string $value Element value
* @param boolean|array $checked True if checked | associative array of optional parameters
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
*
* @return string
*
* @static
*/
public static function checkbox($nid, $value, $checked = '', $class = '', $tabindex = '',
$disabled = false, $extra_html = '') {
self::getNameAndId($nid, $name, $id);
if (func_num_args() > 2 && is_array($checked)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
extract(array_merge($options, array_intersect_key($checked, $options)));
}
return '<input type="checkbox" name="' . $name . '" value="' . $value . '" ' .
($id ? 'id="' . $id . '" ' : '') .
($checked ? 'checked="checked" ' : '') .
($class ? 'class="' . $class . '" ' : '') .
($tabindex ? 'tabindex="' . $tabindex . '" ' : '') .
($disabled ? 'disabled="disabled" ' : '') .
$extra_html .
' />' . "\n";
}
/**
* Input field
*
* Returns HTML code for an input field.
* $nid could be a string or an array of name and ID.
* $default could be a string or an associative array of any of optional parameters
*
* @param string|array $nid Element ID and name
* @param integer $size Element size
* @param integer $max Element maxlength
* @param string|array $default Element value | associative array of optional parameters
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $type Input type
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function field($nid, $size, $max, $default = '', $class = '', $tabindex = '',
$disabled = false, $extra_html = '', $required = false, $type = 'text', $autocomplete = '') {
self::getNameAndId($nid, $name, $id);
if (func_num_args() > 3 && is_array($default)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
extract(array_merge($options, array_intersect_key($default, $options)));
}
return '<input type="' . $type . '" size="' . $size . '" name="' . $name . '" ' .
($id ? 'id="' . $id . '" ' : '') .
($max ? 'maxlength="' . $max . '" ' : '') .
($default || $default === '0' ? 'value="' . $default . '" ' : '') .
($class ? 'class="' . $class . '" ' : '') .
($tabindex ? 'tabindex="' . $tabindex . '" ' : '') .
($disabled ? 'disabled="disabled" ' : '') .
($required ? 'required ' : '') .
($autocomplete ? 'autocomplete="' . $autocomplete . '" ' : '') .
$extra_html .
' />' . "\n";
}
/**
* Password field
*
* Returns HTML code for a password field.
* $nid could be a string or an array of name and ID.
* $default could be a string or an associative array of any of optional parameters
*
* @uses form::field
*
* @param string|array $nid Element ID and name
* @param integer $size Element size
* @param integer $max Element maxlength
* @param string|array $default Element value | associative array of optional parameters
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant (new-password/current-password)
*
* @return string
*
* @static
*/
public static function password($nid, $size, $max, $default = '', $class = '', $tabindex = '',
$disabled = false, $extra_html = '', $required = false, $autocomplete = '') {
if (func_num_args() > 3 && is_array($default)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
extract(array_merge($options, array_intersect_key($default, $options)));
}
return self::field($nid, $size, $max, $default, $class, $tabindex, $disabled, $extra_html,
$required, 'password', $autocomplete);
}
/**
* HTML5 Color field
*
* Returns HTML code for an input color field.
* $nid could be a string or an array of name and ID.
* $size could be a integer or an associative array of any of optional parameters
*
* @uses form::field
*
* @param string|array $nid Element ID and name
* @param integer|array $size Element size | associative array of optional parameters
* @param integer $max Element maxlength
* @param string $default Element value
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function color($nid, $size = 7, $max = 7, $default = '', $class = '', $tabindex = '',
$disabled = false, $extra_html = '', $required = false, $autocomplete = '') {
if (func_num_args() > 1 && is_array($size)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
extract(array_merge($options, array_intersect_key($size, $options)));
}
return self::field($nid, $size, $max, $default, $class, $tabindex, $disabled, $extra_html,
$required, 'color', $autocomplete);
}
/**
* HTML5 Email field
*
* Returns HTML code for an input email field.
* $nid could be a string or an array of name and ID.
* $size could be a integer or an associative array of any of optional parameters
*
* @uses form::field
*
* @param string|array $nid Element ID and name
* @param integer|array $size Element size | associative array of optional parameters
* @param integer $max Element maxlength
* @param string $default Element value
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function email($nid, $size = 20, $max = 255, $default = '', $class = '', $tabindex = '',
$disabled = false, $extra_html = '', $required = false, $autocomplete = '') {
if (func_num_args() > 1 && is_array($size)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
extract(array_merge($options, array_intersect_key($size, $options)));
}
return self::field($nid, $size, $max, $default, $class, $tabindex, $disabled, $extra_html,
$required, 'email', $autocomplete);
}
/**
* HTML5 URL field
*
* Returns HTML code for an input (absolute) URL field.
* $nid could be a string or an array of name and ID.
* $size could be a integer or an associative array of any of optional parameters
*
* @uses form::field
*
* @param string|array $nid Element ID and name
* @param integer|array $size Element size | associative array of optional parameters
* @param integer $max Element maxlength
* @param string $default Element value
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function url($nid, $size = 20, $max = 255, $default = '', $class = '', $tabindex = '',
$disabled = false, $extra_html = '', $required = false, $autocomplete = '') {
if (func_num_args() > 1 && is_array($size)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
extract(array_merge($options, array_intersect_key($size, $options)));
}
return self::field($nid, $size, $max, $default, $class, $tabindex, $disabled, $extra_html,
$required, 'url', $autocomplete);
}
/**
* HTML5 Datetime (local) field
*
* Returns HTML code for an input datetime field.
* $nid could be a string or an array of name and ID.
* $size could be a integer or an associative array of any of optional parameters
*
* @uses form::field
*
* @param string|array $nid Element ID and name
* @param integer|array $size Element size | associative array of optional parameters
* @param integer $max Element maxlength
* @param string $default Element value (in YYYY-MM-DDThh:mm format)
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function datetime($nid, $size = 16, $max = 16, $default = '', $class = '', $tabindex = '',
$disabled = false, $extra_html = '', $required = false, $autocomplete = '') {
if (func_num_args() > 1 && is_array($size)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
extract(array_merge($options, array_intersect_key($size, $options)));
}
// Cope with unimplemented input type for some browser (type="text" + pattern + placeholder)
if (strpos(strtolower($extra_html), 'pattern=') === false) {
$extra_html .= ' pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}"';
}
if (strpos(strtolower($extra_html), 'placeholder') === false) {
$extra_html .= ' placeholder="1962-05-13T14:45"';
}
return self::field($nid, $size, $max, $default, $class, $tabindex, $disabled, $extra_html,
$required, 'datetime-local', $autocomplete);
}
/**
* HTML5 Date field
*
* Returns HTML code for an input date field.
* $nid could be a string or an array of name and ID.
* $size could be a integer or an associative array of any of optional parameters
*
* @uses form::field
*
* @param string|array $nid Element ID and name
* @param integer|array $size Element size | associative array of optional parameters
* @param integer $max Element maxlength
* @param string $default Element value (in YYYY-MM-DD format)
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function date($nid, $size = 10, $max = 10, $default = '', $class = '', $tabindex = '',
$disabled = false, $extra_html = '', $required = false, $autocomplete = '') {
if (func_num_args() > 1 && is_array($size)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
extract(array_merge($options, array_intersect_key($size, $options)));
}
// Cope with unimplemented input type for some browser (type="text" + pattern + placeholder)
if (strpos(strtolower($extra_html), 'pattern=') === false) {
$extra_html .= ' pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"';
}
if (strpos(strtolower($extra_html), 'placeholder') === false) {
$extra_html .= ' placeholder="1962-05-13"';
}
return self::field($nid, $size, $max, $default, $class, $tabindex, $disabled, $extra_html,
$required, 'date', $autocomplete);
}
/**
* HTML5 Time (local) field
*
* Returns HTML code for an input time field.
* $nid could be a string or an array of name and ID.
* $size could be a integer or an associative array of any of optional parameters
*
* @uses form::field
*
* @param string|array $nid Element ID and name
* @param integer|array $size Element size | associative array of optional parameters
* @param integer $max Element maxlength
* @param string $default Element value (in hh:mm format)
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function time($nid, $size = 5, $max = 5, $default = '', $class = '', $tabindex = '',
$disabled = false, $extra_html = '', $required = false, $autocomplete = '') {
if (func_num_args() > 1 && is_array($size)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
extract(array_merge($options, array_intersect_key($size, $options)));
}
// Cope with unimplemented input type for some browser (type="text" + pattern + placeholder)
if (strpos(strtolower($extra_html), 'pattern=') === false) {
$extra_html .= ' pattern="[0-9]{2}:[0-9]{2}"';
}
if (strpos(strtolower($extra_html), 'placeholder') === false) {
$extra_html .= ' placeholder="14:45"';
}
return self::field($nid, $size, $max, $default, $class, $tabindex, $disabled, $extra_html,
$required, 'time', $autocomplete);
}
/**
* HTML5 file field
*
* Returns HTML code for an input file field.
* $nid could be a string or an array of name and ID.
* $default could be a integer or an associative array of any of optional parameters
*
* @param string|array $nid Element ID and name
* @param string|array $default Element value | associative array of optional parameters
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
*
* @return string
*
* @static
*/
public static function file($nid, $default = '', $class = '', $tabindex = '', $disabled = false, $extra_html = '',
$required = false) {
self::getNameAndId($nid, $name, $id);
if (func_num_args() > 1 && is_array($default)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
extract(array_merge($options, array_intersect_key($default, $options)));
}
return '<input type="file" ' . '" name="' . $name . '" ' .
($id ? 'id="' . $id . '" ' : '') .
($default || $default === '0' ? 'value="' . $default . '" ' : '') .
($class ? 'class="' . $class . '" ' : '') .
($tabindex ? 'tabindex="' . $tabindex . '" ' : '') .
($disabled ? 'disabled="disabled" ' : '') .
($required ? 'required ' : '') .
$extra_html .
' />' . "\n";
}
/**
* HTML5 number input field
*
* Returns HTML code for an number input field.
* $nid could be a string or an array of name and ID.
* $min could be a string or an associative array of any of optional parameters
*
* @param string|array $nid Element ID and name
* @param integer|array $min Element min value (may be negative) | associative array of optional parameters
* @param integer $max Element max value (may be negative)
* @param string $default Element value
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function number($nid, $min = null, $max = null, $default = '', $class = '', $tabindex = '',
$disabled = false, $extra_html = '', $required = false, $autocomplete = '') {
self::getNameAndId($nid, $name, $id);
if (func_num_args() > 1 && is_array($min)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
extract(array_merge($options, array_intersect_key($min, $options)));
}
return '<input type="number" name="' . $name . '" ' .
($id ? 'id="' . $id . '" ' : '') .
($min !== null ? 'min="' . $min . '" ' : '') .
($max !== null ? 'max="' . $max . '" ' : '') .
($default || $default === '0' ? 'value="' . $default . '" ' : '') .
($class ? 'class="' . $class . '" ' : '') .
($tabindex ? 'tabindex="' . $tabindex . '" ' : '') .
($disabled ? 'disabled="disabled" ' : '') .
($required ? 'required ' : '') .
($autocomplete ? 'autocomplete="' . $autocomplete . '" ' : '') .
$extra_html .
' />' . "\n";
}
/**
* Textarea
*
* Returns HTML code for a textarea.
* $nid could be a string or an array of name and ID.
* $default could be a string or an associative array of any of optional parameters
*
* @param string|array $nid Element ID and name
* @param integer $cols Number of columns
* @param integer $rows Number of rows
* @param string|array $default Element value | associative array of optional parameters
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function textArea($nid, $cols, $rows, $default = '', $class = '',
$tabindex = '', $disabled = false, $extra_html = '', $required = false, $autocomplete = '') {
self::getNameAndId($nid, $name, $id);
if (func_num_args() > 3 && is_array($default)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
extract(array_merge($options, array_intersect_key($default, $options)));
}
return '<textarea cols="' . $cols . '" rows="' . $rows . '" name="' . $name . '" ' .
($id ? 'id="' . $id . '" ' : '') .
($tabindex != '' ? 'tabindex="' . $tabindex . '" ' : '') .
($class ? 'class="' . $class . '" ' : '') .
($disabled ? 'disabled="disabled" ' : '') .
($required ? 'required ' : '') .
($autocomplete ? 'autocomplete="' . $autocomplete . '" ' : '') .
$extra_html . '>' . $default . '</textarea>' . "\n";
}
/**
* Hidden field
*
* Returns HTML code for an hidden field. $nid could be a string or an array of
* name and ID.
*
* @param string|array $nid Element ID and name
* @param string $value Element value
*
* @return string
*
* @static
*/
public static function hidden($nid, $value)
{
self::getNameAndId($nid, $name, $id);
return '<input type="hidden" name="' . $name . '" value="' . $value . '" ' .
($id ? 'id="' . $id . '" ' : '') .
' />' . "\n";
}
}
/**
* @class formSelectOption
* @brief HTML Forms creation helpers
*
* @package Clearbricks
* @subpackage Common
*/
class formSelectOption
{
public $name; ///< Option name
public $value; ///< Option value
public $class_name; ///< Element class name
public $html; ///< Extra HTML attributes
/**
* sprintf template for option
* @var string $option
* @access private
*/
private $option = '<option value="%1$s"%3$s>%2$s</option>' . "\n";
/**
* Option constructor
*
* @param string $name Option name
* @param string $value Option value
* @param string $class_name Element class name
* @param string $html Extra HTML attributes
*/
public function __construct($name, $value, $class_name = '', $html = '')
{
$this->name = $name;
$this->value = $value;
$this->class_name = $class_name;
$this->html = $html;
}
/**
* Option renderer
*
* Returns option HTML code
*
* @param boolean $default Option is selected
* @return string
*/
public function render($default)
{
$attr = $this->html ? ' ' . $this->html : '';
$attr .= $this->class_name ? ' class="' . $this->class_name . '"' : '';
if ($this->value == $default) {
$attr .= ' selected="selected"';
}
return sprintf($this->option, $this->value, $this->name, $attr) . "\n";
}
}

View File

@ -0,0 +1,179 @@
<?php
/**
* @class html
* @brief HTML utilities
*
* @package Clearbricks
* @subpackage Common
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
class html
{
public static $url_root;
public static $absolute_regs = []; ///< Array of regular expression for {@link absoluteURLs()}
/**
* HTML escape
*
* Replaces HTML special characters by entities.
*
* @param string $str String to escape
* @return string
*/
public static function escapeHTML($str)
{
return htmlspecialchars($str, ENT_COMPAT, 'UTF-8');
}
/**
* Decode HTML entities
*
* Returns a string with all entities decoded.
*
* @param string $str String to protect
* @param string $keep_special Keep special characters: &gt; &lt; &amp;
* @return string
*/
public static function decodeEntities($str, $keep_special = false)
{
if ($keep_special) {
$str = str_replace(
['&amp;', '&gt;', '&lt;'],
['&amp;amp;', '&amp;gt;', '&amp;lt;'],
$str);
}
# Some extra replacements
$extra = [
'&apos;' => "'"
];
$str = str_replace(array_keys($extra), array_values($extra), $str);
return html_entity_decode($str, ENT_QUOTES, 'UTF-8');
}
/**
* Remove markup
*
* Removes every tags, comments, cdata from string
*
* @param string $str String to clean
* @return string
*/
public static function clean($str)
{
$str = strip_tags($str);
return $str;
}
/**
* Javascript escape
*
* Returns a protected JavaScript string
*
* @param string $str String to protect
* @return string
*/
public static function escapeJS($str)
{
$str = htmlspecialchars($str, ENT_NOQUOTES, 'UTF-8');
$str = str_replace("'", "\'", $str);
$str = str_replace('"', '\"', $str);
return $str;
}
/**
* URL escape
*
* Returns an escaped URL string for HTML content
*
* @param string $str String to escape
* @return string
*/
public static function escapeURL($str)
{
return str_replace('&', '&amp;', $str);
}
/**
* URL sanitize
*
* Encode every parts between / in url
*
* @param string $str String to satinyze
* @return string
*/
public static function sanitizeURL($str)
{
return str_replace('%2F', '/', rawurlencode($str));
}
/**
* Remove host in URL
*
* Removes host part in URL
*
* @param string $url URL to transform
* @return string
*/
public static function stripHostURL($url)
{
return preg_replace('|^[a-z]{3,}://.*?(/.*$)|', '$1', $url);
}
/**
* Set links to absolute ones
*
* Appends $root URL to URIs attributes in $str.
*
* @param string $str HTML to transform
* @param string $root Base URL
* @return string
*/
public static function absoluteURLs($str, $root)
{
self::$url_root = $root;
$attr = 'action|background|cite|classid|code|codebase|data|download|formaction|href|longdesc|profile|src|usemap';
$str = preg_replace_callback('/((?:' . $attr . ')=")(.*?)(")/msu', ['self', 'absoluteURLHandler'], $str);
foreach (self::$absolute_regs as $r) {
$str = preg_replace_callback($r, ['self', 'absoluteURLHandler'], $str);
}
self::$url_root = null;
return $str;
}
private static function absoluteURLHandler($m)
{
$url = $m[2];
$link = str_replace('%', '%%', $m[1]) . '%s' . str_replace('%', '%%', $m[3]);
$host = preg_replace('|^([a-z]{3,}://)(.*?)/(.*)$|', '$1$2', self::$url_root);
$parse = parse_url($m[2]);
if (empty($parse['scheme'])) {
if (strpos($url, '//') === 0) {
// Nothing to do. Already an absolute URL.
} elseif (strpos($url, '/') === 0) {
// Beginning by a / return host + url
$url = $host . $url;
} elseif (strpos($url, '#') === 0) {
// Beginning by a # return root + hash
$url = self::$url_root . $url;
} elseif (preg_match('|/$|', self::$url_root)) {
// Root is ending by / return root + url
$url = self::$url_root . $url;
} else {
$url = dirname(self::$url_root) . '/' . $url;
}
}
return sprintf($link, $url);
}
}

View File

@ -0,0 +1,474 @@
<?php
/**
* @class http
* @brief HTTP utilities
*
* @package Clearbricks
* @subpackage Common
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
class http
{
public static $https_scheme_on_443 = false; ///< boolean: Force HTTPS scheme on server port 443 in {@link getHost()}
public static $cache_max_age = 0; ///< integer: Cache max age for {@link cache()}
public static $reverse_proxy = false; ///< bolean: use X-FORWARD headers on getHost();
/**
* Self root URI
*
* Returns current scheme, host and port.
*
* @return string
*/
public static function getHost()
{
if (self::$reverse_proxy && isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
//admin have choose to allow a reverse proxy,
//and HTTP_X_FORWARDED_FOR header means it's beeing using
if (!isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
throw new Exception('Reverse proxy parametter is setted, header HTTP_X_FORWARDED_FOR is found but not the X-Forwarded-Proto. Please check your reverse proxy server settings');
}
$scheme = $_SERVER['HTTP_X_FORWARDED_PROTO'];
$name_port_array = explode(":", $_SERVER['HTTP_HOST']);
$server_name = $name_port_array[0];
$port = isset($name_port_array[1]) ? ':' . $name_port_array[1] : '';
if (($port == ':80' && $scheme == 'http') || ($port == ':443' && $scheme == 'https')) {
$port = '';
}
return $scheme . '://' . $server_name . $port;
}
$server_name = explode(':', $_SERVER['HTTP_HOST']);
$server_name = $server_name[0];
if (self::$https_scheme_on_443 && $_SERVER['SERVER_PORT'] == '443') {
$scheme = 'https';
$port = '';
} elseif (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
$scheme = 'https';
$port = !in_array($_SERVER['SERVER_PORT'], ['80', '443']) ? ':' . $_SERVER['SERVER_PORT'] : '';
} else {
$scheme = 'http';
$port = ($_SERVER['SERVER_PORT'] != '80') ? ':' . $_SERVER['SERVER_PORT'] : '';
}
return $scheme . '://' . $server_name . $port;
}
/**
* Self root URI
*
* Returns current scheme and host from a static URL.
*
* @param string $url URL to retrieve the host from.
*
* @return string
*/
public static function getHostFromURL($url)
{
preg_match('~^(?:((?:[a-z]+:)?//)|:(//))?(?:([^:\r\n]*?)/[^:\r\n]*|([^:\r\n]*))$~', $url, $matches);
array_shift($matches);
return join($matches);
}
/**
* Self URI
*
* Returns current URI with full hostname.
*
* @return string
*/
public static function getSelfURI()
{
if (substr($_SERVER['REQUEST_URI'], 0, 1) != '/') {
return self::getHost() . '/' . $_SERVER['REQUEST_URI'];
} else {
return self::getHost() . $_SERVER['REQUEST_URI'];
}
}
/**
* Prepare a full redirect URI from a relative or absolute URL
*
* @param string $page Relative URL
* @return string full URI
*/
protected static function prepareRedirect($page)
{
if (preg_match('%^http[s]?://%', $page)) {
$redir = $page;
} else {
$host = self::getHost();
if (substr($page, 0, 1) == '/') {
$redir = $host . $page;
} else {
$dir = str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['PHP_SELF']));
if (substr($dir, -1) == '/') {
$dir = substr($dir, 0, -1);
}
if ($dir == '.') {
$dir = '';
}
$redir = $host . $dir . '/' . $page;
}
}
return $redir;
}
/**
* Redirect
*
* Performs a conforming HTTP redirect for a relative URL.
*
* @param string $page Relative URL
*/
public static function redirect($page)
{
# Close session if exists
if (session_id()) {
session_write_close();
}
header('Location: ' . self::prepareRedirect($page));
exit;
}
/**
* Concat URL and path
*
* Appends a path to a given URL. If path begins with "/" it will replace the
* original URL path.
*
* @param string $url URL
* @param string $path Path to append
* @return string
*/
public static function concatURL($url, $path)
{
if (substr($url, -1, 1) != '/') {
$url .= '/';
}
if (substr($path, 0, 1) != '/') {
return $url . $path;
}
return preg_replace('#^(.+?//.+?)/(.*)$#', '$1' . $path, $url);
}
/**
* Real IP
*
* Returns the real client IP (or tries to do its best).
*
* @return string
*/
public static function realIP()
{
return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
}
/**
* Client unique ID
*
* Returns a "almost" safe client unique ID.
*
* @param string $key HMAC key
* @return string
*/
public static function browserUID($key)
{
$uid = '';
$uid .= isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
$uid .= isset($_SERVER['HTTP_ACCEPT_CHARSET']) ? $_SERVER['HTTP_ACCEPT_CHARSET'] : '';
return crypt::hmac($key, $uid);
}
/**
* Client language
*
* Returns a two letters language code take from HTTP_ACCEPT_LANGUAGE.
*
* @return string
*/
public static function getAcceptLanguage()
{
$dlang = '';
if (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$acclang = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
$L = explode(';', $acclang[0]);
$dlang = substr(trim($L[0]), 0, 2);
}
return $dlang;
}
/**
* Client languages
*
* Returns an array of accepted langages ordered by priority.
* can be a two letters language code or a xx-xx variant.
*
* @return array
*/
public static function getAcceptLanguages()
{
$langs = [];
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
// break up string into pieces (languages and q factors)
preg_match_all(
'/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i',
$_SERVER['HTTP_ACCEPT_LANGUAGE'],
$lang_parse
);
if (count($lang_parse[1])) {
// create a list like "en" => 0.8
$langs = array_combine($lang_parse[1], $lang_parse[4]);
// set default to 1 for any without q factor
foreach ($langs as $lang => $val) {
if ($val === '') {
$langs[$lang] = 1;
}
}
// sort list based on value
arsort($langs, SORT_NUMERIC);
$langs = array_map('strtolower', array_keys($langs));
}
}
return $langs;
}
/**
* HTTP Cache
*
* Sends HTTP cache headers (304) according to a list of files and an optionnal.
* list of timestamps.
*
* @param array $files Files on which check mtime
* @param array $mod_ts List of timestamps
*/
public static function cache($files, $mod_ts = [])
{
if (empty($files) || !is_array($files)) {
return;
}
array_walk($files, function (&$v) {
$v = filemtime($v);
});
$array_ts = array_merge($mod_ts, $files);
rsort($array_ts);
$now = time();
$ts = min($array_ts[0], $now);
$since = null;
if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
$since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
$since = preg_replace('/^(.*)(Mon|Tue|Wed|Thu|Fri|Sat|Sun)(.*)(GMT)(.*)/', '$2$3 GMT', $since);
$since = strtotime($since);
$since = ($since <= $now) ? $since : null;
}
# Common headers list
$headers[] = 'Last-Modified: ' . gmdate('D, d M Y H:i:s', $ts) . ' GMT';
$headers[] = 'Cache-Control: must-revalidate, max-age=' . abs((integer) self::$cache_max_age);
$headers[] = 'Pragma:';
if ($since >= $ts) {
self::head(304, 'Not Modified');
foreach ($headers as $v) {
header($v);
}
exit;
} else {
header('Date: ' . gmdate('D, d M Y H:i:s', $now) . ' GMT');
foreach ($headers as $v) {
header($v);
}
}
}
/**
* HTTP Etag
*
* Sends HTTP cache headers (304) according to a list of etags in client request.
*/
public static function etag()
{
# We create an etag from all arguments
$args = func_get_args();
if (empty($args)) {
return;
}
$etag = '"' . md5(implode('', $args)) . '"';
unset($args);
header('ETag: ' . $etag);
# Do we have a previously sent content?
if (!empty($_SERVER['HTTP_IF_NONE_MATCH'])) {
foreach (explode(',', $_SERVER['HTTP_IF_NONE_MATCH']) as $i) {
if (stripslashes(trim($i)) == $etag) {
self::head(304, 'Not Modified');
exit;
}
}
}
}
/**
* HTTP Header
*
* Sends an HTTP code and message to client.
*
* @param string $code HTTP code
* @param string $msg Message
*/
public static function head($code, $msg = null)
{
$status_mode = preg_match('/cgi/', PHP_SAPI);
if (!$msg) {
$msg_codes = [
100 => 'Continue',
101 => 'Switching Protocols',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported'
];
$msg = isset($msg_codes[$code]) ? $msg_codes[$code] : '-';
}
if ($status_mode) {
header('Status: ' . $code . ' ' . $msg);
} else {
header($msg, true, $code);
}
}
/**
* Trim request
*
* Trims every value in GET, POST, REQUEST and COOKIE vars.
* Removes magic quotes if magic_quote_gpc is on.
*/
public static function trimRequest()
{
if (!empty($_GET)) {
array_walk($_GET, ['self', 'trimRequestInVar']);
}
if (!empty($_POST)) {
array_walk($_POST, ['self', 'trimRequestInVar']);
}
if (!empty($_REQUEST)) {
array_walk($_REQUEST, ['self', 'trimRequestInVar']);
}
if (!empty($_COOKIE)) {
array_walk($_COOKIE, ['self', 'trimRequestInVar']);
}
}
private static function trimRequestInVar(&$value, $key)
{
if (is_array($value)) {
foreach ($value as $k => &$v) {
if (is_array($v)) {
self::trimRequestInVar($v, $k);
} else {
$v = trim($v);
}
}
} else {
$value = trim($value);
}
}
/**
* Unset global variables
*
* If register_globals is on, removes every GET, POST, COOKIE, REQUEST, SERVER,
* ENV, FILES vars from GLOBALS.
*/
public static function unsetGlobals()
{
if (!ini_get('register_globals')) {
return;
}
if (isset($_REQUEST['GLOBALS'])) {
throw new Exception('GLOBALS overwrite attempt detected');
}
# Variables that shouldn't be unset
$no_unset = ['GLOBALS', '_GET', '_POST', '_COOKIE', '_REQUEST',
'_SERVER', '_ENV', '_FILES'];
$input = array_merge(
$_GET,
$_POST,
$_COOKIE,
$_SERVER,
$_ENV,
$_FILES,
(isset($_SESSION) && is_array($_SESSION) ? $_SESSION : [])
);
foreach ($input as $k => $v) {
if (!in_array($k, $no_unset) && isset($GLOBALS[$k])) {
$GLOBALS[$k] = null;
unset($GLOBALS[$k]);
}
}
}
}

View File

@ -0,0 +1,777 @@
<?php
/**
* @package Clearbricks
* @subpackage Common
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
/** @cond ONCE */
if (!function_exists('json_encode')) {
/** @endcond */
function json_encode($str)
{
try {
$GLOBALS['json_last_error'] = 0;
$json = new Services_JSON();
return $json->encode($str);
} catch (Exception $e) {
$GLOBALS['json_last_error'] = $e->getMessage();
return;
}
}
function json_decode($str)
{
try {
$GLOBALS['json_last_error'] = 0;
$json = new Services_JSON();
return $json->decode($str);
} catch (Exception $e) {
$GLOBALS['json_last_error'] = $e->getMessage();
return;
}
}
function json_last_error()
{
return empty($GLOBALS['json_last_error']) ? 0 : $GLOBALS['json_last_error'];
}
/** @cond ONCE */
}
/** @endcond */
/** @cond ONCE */
if (!class_exists('Services_JSON')) {
/** @endcond */
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_SLICE', 1);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_STR', 2);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_ARR', 3);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_OBJ', 4);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_CMT', 5);
/**
* Behavior switch for Services_JSON::decode()
*/
define('SERVICES_JSON_LOOSE_TYPE', 16);
/**
* Behavior switch for Services_JSON::decode()
*/
define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
/**
* @class Services_JSON
* @brief Converts to and from JSON format.
*
* This class library is from Michal Migurski's Services_JSON library (http://pear.php.net/pepr/pepr-proposal-show.php?id=198 <a href="http://www.opensource.org/licenses/bsd-license.php">BSD</a>)
*
* @package Clearbricks
* @subpackage Common
*/
class Services_JSON
{
/**
* constructs a new JSON instance
*
* @param int $use object behavior flags; combine with boolean-OR
*
* possible values:
* - SERVICES_JSON_LOOSE_TYPE: loose typing.
* "{...}" syntax creates associative arrays
* instead of objects in decode().
* - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
* Values which can't be encoded (e.g. resources)
* appear as NULL instead of throwing errors.
* By default, a deeply-nested resource will
* bubble up with an error, so all return values
* from encode() should be checked with isError()
*/
public function __construct($use = 0)
{
$this->use = $use;
}
/**
* convert a string from one UTF-16 char to one UTF-8 char
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
* that lack the multibye string extension.
*
* @param string $utf16 UTF-16 character
* @return string UTF-8 character
*/
public function utf162utf8($utf16)
{
// oh please oh please oh please oh please oh please
if (function_exists('mb_convert_encoding')) {
return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
}
$bytes = (ord($utf16[0]) << 8) | ord($utf16[1]);
switch (true) {
case ((0x7F & $bytes) == $bytes):
// this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x7F & $bytes);
case (0x07FF & $bytes) == $bytes:
// return a 2-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xC0 | (($bytes >> 6) & 0x1F))
. chr(0x80 | ($bytes & 0x3F));
case (0xFFFF & $bytes) == $bytes:
// return a 3-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xE0 | (($bytes >> 12) & 0x0F))
. chr(0x80 | (($bytes >> 6) & 0x3F))
. chr(0x80 | ($bytes & 0x3F));
}
// ignoring UTF-32 for now, sorry
return '';
}
/**
* convert a string from one UTF-8 char to one UTF-16 char
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
* that lack the multibye string extension.
*
* @param string $utf8 UTF-8 character
* @return string UTF-16 character
*/
public function utf82utf16($utf8)
{
// oh please oh please oh please oh please oh please
if (function_exists('mb_convert_encoding')) {
return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
}
switch (strlen($utf8)) {
case 1:
// this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return $utf8;
case 2:
// return a UTF-16 character from a 2-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x07 & (ord($utf8[0]) >> 2))
. chr((0xC0 & (ord($utf8[0]) << 6))
| (0x3F & ord($utf8[1])));
case 3:
// return a UTF-16 character from a 3-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr((0xF0 & (ord($utf8[0]) << 4))
| (0x0F & (ord($utf8[1]) >> 2)))
. chr((0xC0 & (ord($utf8[1]) << 6))
| (0x7F & ord($utf8[2])));
}
// ignoring UTF-32 for now, sorry
return '';
}
/**
* encodes an arbitrary variable into JSON format
*
* @param mixed $var any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior.
* if var is a strng, note that encode() always expects it
* to be in ASCII or UTF-8 format!
*
* @return mixed JSON string representation of input var or an error if a problem occurs
*/
public function encode($var)
{
switch (gettype($var)) {
case 'boolean':
return $var ? 'true' : 'false';
case 'NULL':
return 'null';
case 'integer':
return (int) $var;
case 'double':
case 'float':
return (float) $var;
case 'string':
// STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
$ascii = '';
$strlen_var = strlen($var);
/*
* Iterate over every character in the string,
* escaping with a slash or encoding to UTF-8 where necessary
*/
for ($c = 0; $c < $strlen_var; ++$c) {
$ord_var_c = ord($var[$c]);
switch (true) {
case $ord_var_c == 0x08:
$ascii .= '\b';
break;
case $ord_var_c == 0x09:
$ascii .= '\t';
break;
case $ord_var_c == 0x0A:
$ascii .= '\n';
break;
case $ord_var_c == 0x0C:
$ascii .= '\f';
break;
case $ord_var_c == 0x0D:
$ascii .= '\r';
break;
case $ord_var_c == 0x22:
case $ord_var_c == 0x2F:
case $ord_var_c == 0x5C:
// double quote, slash, slosh
$ascii .= '\\' . $var[$c];
break;
case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
// characters U-00000000 - U-0000007F (same as ASCII)
$ascii .= $var[$c];
break;
case (($ord_var_c & 0xE0) == 0xC0):
// characters U-00000080 - U-000007FF, mask 110XXXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c, ord($var[$c + 1]));
$c += 1;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xF0) == 0xE0):
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var[$c + 1]),
ord($var[$c + 2]));
$c += 2;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xF8) == 0xF0):
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]));
$c += 3;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xFC) == 0xF8):
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]),
ord($var[$c + 4]));
$c += 4;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xFE) == 0xFC):
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]),
ord($var[$c + 4]),
ord($var[$c + 5]));
$c += 5;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
}
}
return '"' . $ascii . '"';
case 'array':
/*
* As per JSON spec if any array key is not an integer
* we must treat the the whole array as an object. We
* also try to catch a sparsely populated associative
* array with numeric keys here because some JS engines
* will create an array with empty indexes up to
* max_index which can cause memory issues and because
* the keys, which may be relevant, will be remapped
* otherwise.
*
* As per the ECMA and JSON specification an object may
* have any string as a property. Unfortunately due to
* a hole in the ECMA specification if the key is a
* ECMA reserved word or starts with a digit the
* parameter is only accessible using ECMAScript's
* bracket notation.
*/
// treat as a JSON object
if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
$properties = array_map([$this, 'name_value'],
array_keys($var),
array_values($var));
foreach ($properties as $property) {
if (Services_JSON::isError($property)) {
return $property;
}
}
return '{' . join(',', $properties) . '}';
}
// treat it like a regular array
$elements = array_map([$this, 'encode'], $var);
foreach ($elements as $element) {
if (Services_JSON::isError($element)) {
return $element;
}
}
return '[' . join(',', $elements) . ']';
case 'object':
$vars = get_object_vars($var);
$properties = array_map([$this, 'name_value'],
array_keys($vars),
array_values($vars));
foreach ($properties as $property) {
if (Services_JSON::isError($property)) {
return $property;
}
}
return '{' . join(',', $properties) . '}';
default:
return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
? 'null'
: new Services_JSON_Error(gettype($var) . " can not be encoded as JSON string");
}
}
/**
* array-walking function for use in generating JSON-formatted name-value pairs
*
* @param string $name name of key to use
* @param mixed $value reference to an array element to be encoded
*
* @return string JSON-formatted name-value pair, like '"name":value'
*/
public function name_value($name, $value)
{
$encoded_value = $this->encode($value);
if (Services_JSON::isError($encoded_value)) {
return $encoded_value;
}
return $this->encode(strval($name)) . ':' . $encoded_value;
}
/**
* reduce a string by removing leading and trailing comments and whitespace
*
* @param $str string string value to strip of comments and whitespace
*
* @return string string value stripped of comments and whitespace
*/
public function reduce_string($str)
{
$str = preg_replace([
// eliminate single line comments in '// ...' form
'#^\s*//(.+)$#m',
// eliminate multi-line comments in '/* ... */' form, at start of string
'#^\s*/\*(.+)\*/#Us',
// eliminate multi-line comments in '/* ... */' form, at end of string
'#/\*(.+)\*/\s*$#Us'
], '', $str);
// eliminate extraneous space
return trim($str);
}
/**
* decodes a JSON string into appropriate variable
*
* @param string $str JSON-formatted string
*
* @return mixed number, boolean, string, array, or object
* corresponding to given JSON input string.
* See argument 1 to Services_JSON() above for object-output behavior.
* Note that decode() always returns strings
* in ASCII or UTF-8 format!
*/
public function decode($str)
{
$str = $this->reduce_string($str);
switch (strtolower($str)) {
case 'true':
return true;
case 'false':
return false;
case 'null':
return;
default:
$m = [];
if (is_numeric($str)) {
// Lookie-loo, it's a number
// This would work on its own, but I'm trying to be
// good about returning integers where appropriate:
// return (float)$str;
// Return float or int, as appropriate
return ((float) $str == (integer) $str)
? (integer) $str
: (float) $str;
} elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
// STRINGS RETURNED IN UTF-8 FORMAT
$delim = substr($str, 0, 1);
$chrs = substr($str, 1, -1);
$utf8 = '';
$strlen_chrs = strlen($chrs);
for ($c = 0; $c < $strlen_chrs; ++$c) {
$substr_chrs_c_2 = substr($chrs, $c, 2);
$ord_chrs_c = ord($chrs[$c]);
switch (true) {
case $substr_chrs_c_2 == '\b':
$utf8 .= chr(0x08);
++$c;
break;
case $substr_chrs_c_2 == '\t':
$utf8 .= chr(0x09);
++$c;
break;
case $substr_chrs_c_2 == '\n':
$utf8 .= chr(0x0A);
++$c;
break;
case $substr_chrs_c_2 == '\f':
$utf8 .= chr(0x0C);
++$c;
break;
case $substr_chrs_c_2 == '\r':
$utf8 .= chr(0x0D);
++$c;
break;
case $substr_chrs_c_2 == '\\"':
case $substr_chrs_c_2 == '\\\'':
case $substr_chrs_c_2 == '\\\\':
case $substr_chrs_c_2 == '\\/':
if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
($delim == "'" && $substr_chrs_c_2 != '\\"')) {
$utf8 .= $chrs[ ++$c];
}
break;
case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
// single, escaped unicode character
$utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
. chr(hexdec(substr($chrs, ($c + 4), 2)));
$utf8 .= $this->utf162utf8($utf16);
$c += 5;
break;
case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
$utf8 .= $chrs[$c];
break;
case ($ord_chrs_c & 0xE0) == 0xC0:
// characters U-00000080 - U-000007FF, mask 110XXXXX
//see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 2);
++$c;
break;
case ($ord_chrs_c & 0xF0) == 0xE0:
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 3);
$c += 2;
break;
case ($ord_chrs_c & 0xF8) == 0xF0:
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 4);
$c += 3;
break;
case ($ord_chrs_c & 0xFC) == 0xF8:
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 5);
$c += 4;
break;
case ($ord_chrs_c & 0xFE) == 0xFC:
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 6);
$c += 5;
break;
}
}
return $utf8;
} elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
// array, or object notation
if ($str[0] == '[') {
$stk = [SERVICES_JSON_IN_ARR];
$arr = [];
} else {
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$stk = [SERVICES_JSON_IN_OBJ];
$obj = [];
} else {
$stk = [SERVICES_JSON_IN_OBJ];
$obj = new stdClass();
}
}
array_push($stk, ['what' => SERVICES_JSON_SLICE,
'where' => 0,
'delim' => false]);
$chrs = substr($str, 1, -1);
$chrs = $this->reduce_string($chrs);
if ($chrs == '') {
if (reset($stk) == SERVICES_JSON_IN_ARR) {
return $arr;
} else {
return $obj;
}
}
//print("\nparsing {$chrs}\n");
$strlen_chrs = strlen($chrs);
for ($c = 0; $c <= $strlen_chrs; ++$c) {
$top = end($stk);
$substr_chrs_c_2 = substr($chrs, $c, 2);
if (($c == $strlen_chrs) || (($chrs[$c] == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
// found a comma that is not inside a string, array, etc.,
// OR we've reached the end of the character list
$slice = substr($chrs, $top['where'], ($c - $top['where']));
array_push($stk, ['what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false]);
//print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
if (reset($stk) == SERVICES_JSON_IN_ARR) {
// we are in an array, so just push an element onto the stack
array_push($arr, $this->decode($slice));
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
// we are in an object, so figure
// out the property name and set an
// element in an associative array,
// for now
$parts = [];
if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
// "name":value pair
$key = $this->decode($parts[1]);
$val = $this->decode($parts[2]);
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$obj[$key] = $val;
} else {
$obj->$key = $val;
}
} elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
// name:value pair, where name is unquoted
$key = $parts[1];
$val = $this->decode($parts[2]);
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$obj[$key] = $val;
} else {
$obj->$key = $val;
}
}
}
} elseif ((($chrs[$c] == '"') || ($chrs[$c] == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
// found a quote, and we are not inside a string
array_push($stk, ['what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs[$c]]);
//print("Found start of string at {$c}\n");
} elseif (($chrs[$c] == $top['delim']) &&
($top['what'] == SERVICES_JSON_IN_STR) &&
((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
// found a quote, we're in a string, and it's not escaped
// we know that it's not escaped becase there is _not_ an
// odd number of backslashes at the end of the string so far
array_pop($stk);
//print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
} elseif (($chrs[$c] == '[') &&
in_array($top['what'], [SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ])) {
// found a left-bracket, and we are in an array, object, or slice
array_push($stk, ['what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false]);
//print("Found start of array at {$c}\n");
} elseif (($chrs[$c] == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
// found a right-bracket, and we're in an array
array_pop($stk);
//print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
} elseif (($chrs[$c] == '{') &&
in_array($top['what'], [SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ])) {
// found a left-brace, and we are in an array, object, or slice
array_push($stk, ['what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false]);
//print("Found start of object at {$c}\n");
} elseif (($chrs[$c] == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
// found a right-brace, and we're in an object
array_pop($stk);
//print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
} elseif (($substr_chrs_c_2 == '/*') &&
in_array($top['what'], [SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ])) {
// found a comment start, and we are in an array, object, or slice
array_push($stk, ['what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false]);
$c++;
//print("Found start of comment at {$c}\n");
} elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
// found a comment end, and we're in one now
array_pop($stk);
$c++;
for ($i = $top['where']; $i <= $c; ++$i) {
$chrs = substr_replace($chrs, ' ', $i, 1);
}
//print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
}
}
if (reset($stk) == SERVICES_JSON_IN_ARR) {
return $arr;
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
return $obj;
}
}
}
}
public function isError($data, $code = null)
{
if (class_exists('pear')) {
return PEAR::isError($data, $code);
} elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
is_subclass_of($data, 'services_json_error'))) {
return true;
}
return false;
}
}
/** @cond ONCE */
if (class_exists('PEAR_Error')) {
/** @endcond */
class Services_JSON_Error extends PEAR_Error
{
public function __construct($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null) {
parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
}
}
/** @cond ONCE */
} else {
class Services_JSON_Error
{
public function __construct($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null) {
}
}
}
/** @endcond */
/** @cond ONCE */
}
/** @endcond */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,324 @@
<?php
/**
* @class text
* @brief Text utilities
*
* @package Clearbricks
* @subpackage Common
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
class text
{
/**
* Check email address
*
* Returns true if $email is a valid email address.
*
* @param string $email Email string
* @return boolean
*/
public static function isEmail($email)
{
return (filter_var($email, FILTER_VALIDATE_EMAIL) !== false);
}
/**
* Accents replacement
*
* Replaces some occidental accentuated characters by their ASCII
* representation.
*
* @param string $str String to deaccent
* @return string
*/
public static function deaccent($str)
{
$pattern['A'] = '\x{00C0}-\x{00C5}';
$pattern['AE'] = '\x{00C6}';
$pattern['C'] = '\x{00C7}';
$pattern['D'] = '\x{00D0}';
$pattern['E'] = '\x{00C8}-\x{00CB}';
$pattern['I'] = '\x{00CC}-\x{00CF}';
$pattern['N'] = '\x{00D1}';
$pattern['O'] = '\x{00D2}-\x{00D6}\x{00D8}';
$pattern['OE'] = '\x{0152}';
$pattern['S'] = '\x{0160}';
$pattern['U'] = '\x{00D9}-\x{00DC}';
$pattern['Y'] = '\x{00DD}';
$pattern['Z'] = '\x{017D}';
$pattern['a'] = '\x{00E0}-\x{00E5}';
$pattern['ae'] = '\x{00E6}';
$pattern['c'] = '\x{00E7}';
$pattern['d'] = '\x{00F0}';
$pattern['e'] = '\x{00E8}-\x{00EB}';
$pattern['i'] = '\x{00EC}-\x{00EF}';
$pattern['n'] = '\x{00F1}';
$pattern['o'] = '\x{00F2}-\x{00F6}\x{00F8}';
$pattern['oe'] = '\x{0153}';
$pattern['s'] = '\x{0161}';
$pattern['u'] = '\x{00F9}-\x{00FC}';
$pattern['y'] = '\x{00FD}\x{00FF}';
$pattern['z'] = '\x{017E}';
$pattern['ss'] = '\x{00DF}';
foreach ($pattern as $r => $p) {
$str = preg_replace('/[' . $p . ']/u', $r, $str);
}
return $str;
}
/**
* String to URL
*
* Transforms a string to a proper URL.
*
* @param string $str String to transform
* @param boolean $with_slashes Keep slashes in URL
* @return string
*/
public static function str2URL($str, $with_slashes = true)
{
$str = self::deaccent($str);
$str = preg_replace('/[^A-Za-z0-9_\s\'\:\/[\]-]/', '', $str);
return self::tidyURL($str, $with_slashes);
}
/**
* URL cleanup
*
* @param string $str URL to tidy
* @param boolean $keep_slashes Keep slashes in URL
* @param boolean $keep_spaces Keep spaces in URL
* @return string
*/
public static function tidyURL($str, $keep_slashes = true, $keep_spaces = false)
{
$str = strip_tags($str);
$str = str_replace(['?', '&', '#', '=', '+', '<', '>', '"', '%'], '', $str);
$str = str_replace("'", ' ', $str);
$str = preg_replace('/[\s]+/u', ' ', trim($str));
if (!$keep_slashes) {
$str = str_replace('/', '-', $str);
}
if (!$keep_spaces) {
$str = str_replace(' ', '-', $str);
}
$str = preg_replace('/[-]+/', '-', $str);
# Remove path changes in URL
$str = preg_replace('%^/%', '', $str);
$str = preg_replace('%\.+/%', '', $str);
return $str;
}
/**
* Cut string
*
* Returns a cuted string on spaced at given length $l.
*
* @param string $str String to cut
* @param integer $l Length to keep
* @return string
*/
public static function cutString($str, $l)
{
$s = preg_split('/([\s]+)/u', $str, -1, PREG_SPLIT_DELIM_CAPTURE);
$res = '';
$L = 0;
if (mb_strlen($s[0]) >= $l) {
return mb_substr($s[0], 0, $l);
}
foreach ($s as $v) {
$L = $L + mb_strlen($v);
if ($L > $l) {
break;
} else {
$res .= $v;
}
}
return trim($res);
}
/**
* Split words
*
* Returns an array of words from a given string.
*
* @param string $str Words to split
* @return array
*/
public static function splitWords($str)
{
$non_word = '\x{0000}-\x{002F}\x{003A}-\x{0040}\x{005b}-\x{0060}\x{007B}-\x{007E}\x{00A0}-\x{00BF}\s';
if (preg_match_all('/([^' . $non_word . ']{3,})/msu', html::clean($str), $match)) {
foreach ($match[1] as $i => $v) {
$match[1][$i] = mb_strtolower($v);
}
return $match[1];
}
return [];
}
/**
* Encoding detection
*
* Returns the encoding (in lowercase) of given $str.
*
* @param string $str String
* @return string
*/
public static function detectEncoding($str)
{
return strtolower(mb_detect_encoding($str . ' ',
'UTF-8,ISO-8859-1,ISO-8859-2,ISO-8859-3,' .
'ISO-8859-4,ISO-8859-5,ISO-8859-6,ISO-8859-7,ISO-8859-8,' .
'ISO-8859-9,ISO-8859-10,ISO-8859-13,ISO-8859-14,ISO-8859-15'));
}
/**
* UTF8 conversions
*
* Returns an UTF-8 converted string. If $encoding is not specified, the
* function will try to detect encoding.
*
* @param string $str String to convert
* @param string $encoding Optionnal "from" encoding
* @return string
*/
public static function toUTF8($str, $encoding = null)
{
if (!$encoding) {
$encoding = self::detectEncoding($str);
}
if ($encoding != 'utf-8') {
$str = iconv($encoding, 'UTF-8', $str);
}
return $str;
}
/**
* Find bad UTF8 tokens
*
* Locates the first bad byte in a UTF-8 string returning it's
* byte index in the string
* PCRE Pattern to locate bad bytes in a UTF-8 string
* Comes from W3 FAQ: Multilingual Forms
* Note: modified to include full ASCII range including control chars
*
* @copyright Harry Fuecks (http://phputf8.sourceforge.net <a href="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">GNU LGPL 2.1</a>)
*
* @param string $str String to search
* @return integer|false
*/
public static function utf8badFind($str)
{
$UTF8_BAD =
'([\x00-\x7F]' . # ASCII (including control chars)
'|[\xC2-\xDF][\x80-\xBF]' . # non-overlong 2-byte
'|\xE0[\xA0-\xBF][\x80-\xBF]' . # excluding overlongs
'|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}' . # straight 3-byte
'|\xED[\x80-\x9F][\x80-\xBF]' . # excluding surrogates
'|\xF0[\x90-\xBF][\x80-\xBF]{2}' . # planes 1-3
'|[\xF1-\xF3][\x80-\xBF]{3}' . # planes 4-15
'|\xF4[\x80-\x8F][\x80-\xBF]{2}' . # plane 16
'|(.{1}))'; # invalid byte
$pos = 0;
$badList = [];
while (preg_match('/' . $UTF8_BAD . '/S', $str, $matches)) {
$bytes = strlen($matches[0]);
if (isset($matches[2])) {
return $pos;
}
$pos += $bytes;
$str = substr($str, $bytes);
}
return false;
}
/**
* UTF8 cleanup
*
* Replaces non utf8 bytes in $str by $repl.
*
* @copyright Harry Fuecks (http://phputf8.sourceforge.net <a href="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">GNU LGPL 2.1</a>)
*
* @param string $str String to clean
* @param string $repl Replacement string
* @return string
*/
public static function cleanUTF8($str, $repl = '?')
{
while (($bad_index = self::utf8badFind($str)) !== false) {
$str = substr_replace($str, $repl, $bad_index, 1);
}
return $str;
}
/**
* BOM removal
*
* Removes BOM from the begining of a string if present.
*
* @param string $str String to clean
* @return string
*/
public static function removeBOM($str)
{
if (substr_count($str, '')) {
return str_replace('', '', $str);
}
return $str;
}
/**
* Quoted printable conversion
*
* Encodes given str to quoted printable
*
* @param string $str String to encode
* @return string
*/
public static function QPEncode($str)
{
$res = '';
foreach (preg_split("/\r?\n/msu", $str) as $line) {
$l = '';
preg_match_all('/./', $line, $m);
foreach ($m[0] as $c) {
$a = ord($c);
if ($a < 32 || $a == 61 || $a > 126) {
$c = sprintf('=%02X', $a);
}
$l .= $c;
}
$res .= $l . "\r\n";
}
return $res;
}
}

View File

@ -0,0 +1,407 @@
Africa/Abidjan
Africa/Accra
Africa/Addis_Ababa
Africa/Algiers
Africa/Asmera
Africa/Bamako
Africa/Bangui
Africa/Banjul
Africa/Bissau
Africa/Blantyre
Africa/Brazzaville
Africa/Bujumbura
Africa/Cairo
Africa/Casablanca
Africa/Ceuta
Africa/Conakry
Africa/Dakar
Africa/Dar_es_Salaam
Africa/Djibouti
Africa/Douala
Africa/El_Aaiun
Africa/Freetown
Africa/Gaborone
Africa/Harare
Africa/Johannesburg
Africa/Kampala
Africa/Khartoum
Africa/Kigali
Africa/Kinshasa
Africa/Lagos
Africa/Libreville
Africa/Lome
Africa/Luanda
Africa/Lubumbashi
Africa/Lusaka
Africa/Malabo
Africa/Maputo
Africa/Maseru
Africa/Mbabane
Africa/Mogadishu
Africa/Monrovia
Africa/Nairobi
Africa/Ndjamena
Africa/Niamey
Africa/Nouakchott
Africa/Ouagadougou
Africa/Porto-Novo
Africa/Sao_Tome
Africa/Timbuktu
Africa/Tripoli
Africa/Tunis
Africa/Windhoek
America/Adak
America/Anchorage
America/Anguilla
America/Antigua
America/Araguaina
America/Argentina/Buenos_Aires
America/Argentina/Catamarca
America/Argentina/ComodRivadavia
America/Argentina/Cordoba
America/Argentina/Jujuy
America/Argentina/La_Rioja
America/Argentina/Mendoza
America/Argentina/Rio_Gallegos
America/Argentina/San_Juan
America/Argentina/Tucuman
America/Argentina/Ushuaia
America/Aruba
America/Asuncion
America/Bahia
America/Barbados
America/Belem
America/Belize
America/Boa_Vista
America/Bogota
America/Boise
America/Cambridge_Bay
America/Campo_Grande
America/Cancun
America/Caracas
America/Cayenne
America/Cayman
America/Chicago
America/Chihuahua
America/Costa_Rica
America/Cuiaba
America/Curacao
America/Danmarkshavn
America/Dawson
America/Dawson_Creek
America/Denver
America/Detroit
America/Dominica
America/Edmonton
America/Eirunepe
America/El_Salvador
America/Fortaleza
America/Glace_Bay
America/Godthab
America/Goose_Bay
America/Grand_Turk
America/Grenada
America/Guadeloupe
America/Guatemala
America/Guayaquil
America/Guyana
America/Halifax
America/Havana
America/Hermosillo
America/Indiana/Indianapolis
America/Indiana/Knox
America/Indiana/Marengo
America/Indiana/Vevay
America/Indianapolis
America/Inuvik
America/Iqaluit
America/Jamaica
America/Juneau
America/Kentucky/Louisville
America/Kentucky/Monticello
America/La_Paz
America/Lima
America/Los_Angeles
America/Louisville
America/Maceio
America/Managua
America/Manaus
America/Martinique
America/Mazatlan
America/Menominee
America/Merida
America/Mexico_City
America/Miquelon
America/Monterrey
America/Montevideo
America/Montreal
America/Montserrat
America/Nassau
America/New_York
America/Nipigon
America/Nome
America/Noronha
America/North_Dakota/Center
America/Panama
America/Pangnirtung
America/Paramaribo
America/Phoenix
America/Port-au-Prince
America/Port_of_Spain
America/Porto_Velho
America/Puerto_Rico
America/Rainy_River
America/Rankin_Inlet
America/Recife
America/Regina
America/Rio_Branco
America/Santiago
America/Santo_Domingo
America/Sao_Paulo
America/Scoresbysund
America/Shiprock
America/St_Johns
America/St_Kitts
America/St_Lucia
America/St_Thomas
America/St_Vincent
America/Swift_Current
America/Tegucigalpa
America/Thule
America/Thunder_Bay
America/Tijuana
America/Toronto
America/Tortola
America/Vancouver
America/Whitehorse
America/Winnipeg
America/Yakutat
America/Yellowknife
Antarctica/Casey
Antarctica/Davis
Antarctica/DumontDUrville
Antarctica/Mawson
Antarctica/McMurdo
Antarctica/Palmer
Antarctica/Rothera
Antarctica/South_Pole
Antarctica/Syowa
Antarctica/Vostok
Arctic/Longyearbyen
Asia/Aden
Asia/Almaty
Asia/Amman
Asia/Anadyr
Asia/Aqtau
Asia/Aqtobe
Asia/Ashgabat
Asia/Baghdad
Asia/Bahrain
Asia/Baku
Asia/Bangkok
Asia/Beirut
Asia/Bishkek
Asia/Brunei
Asia/Calcutta
Asia/Choibalsan
Asia/Chongqing
Asia/Colombo
Asia/Damascus
Asia/Dhaka
Asia/Dili
Asia/Dubai
Asia/Dushanbe
Asia/Gaza
Asia/Harbin
Asia/Hong_Kong
Asia/Hovd
Asia/Irkutsk
Asia/Istanbul
Asia/Jakarta
Asia/Jayapura
Asia/Jerusalem
Asia/Kabul
Asia/Kamchatka
Asia/Karachi
Asia/Kashgar
Asia/Katmandu
Asia/Krasnoyarsk
Asia/Kuala_Lumpur
Asia/Kuching
Asia/Kuwait
Asia/Macau
Asia/Magadan
Asia/Makassar
Asia/Manila
Asia/Muscat
Asia/Nicosia
Asia/Novosibirsk
Asia/Omsk
Asia/Oral
Asia/Phnom_Penh
Asia/Pontianak
Asia/Pyongyang
Asia/Qatar
Asia/Qyzylorda
Asia/Rangoon
Asia/Riyadh
Asia/Saigon
Asia/Sakhalin
Asia/Samarkand
Asia/Seoul
Asia/Shanghai
Asia/Singapore
Asia/Taipei
Asia/Tashkent
Asia/Tbilisi
Asia/Tehran
Asia/Thimphu
Asia/Tokyo
Asia/Ulaanbaatar
Asia/Urumqi
Asia/Vientiane
Asia/Vladivostok
Asia/Yakutsk
Asia/Yekaterinburg
Asia/Yerevan
Atlantic/Azores
Atlantic/Bermuda
Atlantic/Canary
Atlantic/Cape_Verde
Atlantic/Faeroe
Atlantic/Jan_Mayen
Atlantic/Madeira
Atlantic/Reykjavik
Atlantic/South_Georgia
Atlantic/St_Helena
Atlantic/Stanley
Australia/Adelaide
Australia/Brisbane
Australia/Broken_Hill
Australia/Darwin
Australia/Hobart
Australia/Lindeman
Australia/Lord_Howe
Australia/Melbourne
Australia/Perth
Australia/Sydney
Europe/Amsterdam
Europe/Andorra
Europe/Athens
Europe/Belfast
Europe/Belgrade
Europe/Berlin
Europe/Bratislava
Europe/Brussels
Europe/Bucharest
Europe/Budapest
Europe/Chisinau
Europe/Copenhagen
Europe/Dublin
Europe/Gibraltar
Europe/Helsinki
Europe/Istanbul
Europe/Kaliningrad
Europe/Kiev
Europe/Lisbon
Europe/Ljubljana
Europe/London
Europe/Luxembourg
Europe/Madrid
Europe/Malta
Europe/Mariehamn
Europe/Minsk
Europe/Monaco
Europe/Moscow
Europe/Nicosia
Europe/Oslo
Europe/Paris
Europe/Prague
Europe/Riga
Europe/Rome
Europe/Samara
Europe/San_Marino
Europe/Sarajevo
Europe/Simferopol
Europe/Skopje
Europe/Sofia
Europe/Stockholm
Europe/Tallinn
Europe/Tirane
Europe/Uzhgorod
Europe/Vaduz
Europe/Vatican
Europe/Vienna
Europe/Vilnius
Europe/Warsaw
Europe/Zagreb
Europe/Zaporozhye
Europe/Zurich
Indian/Antananarivo
Indian/Chagos
Indian/Christmas
Indian/Cocos
Indian/Comoro
Indian/Kerguelen
Indian/Mahe
Indian/Maldives
Indian/Mauritius
Indian/Mayotte
Indian/Reunion
Pacific/Apia
Pacific/Auckland
Pacific/Chatham
Pacific/Easter
Pacific/Efate
Pacific/Enderbury
Pacific/Fakaofo
Pacific/Fiji
Pacific/Funafuti
Pacific/Galapagos
Pacific/Gambier
Pacific/Guadalcanal
Pacific/Guam
Pacific/Honolulu
Pacific/Johnston
Pacific/Kiritimati
Pacific/Kosrae
Pacific/Kwajalein
Pacific/Majuro
Pacific/Marquesas
Pacific/Midway
Pacific/Nauru
Pacific/Niue
Pacific/Norfolk
Pacific/Noumea
Pacific/Pago_Pago
Pacific/Palau
Pacific/Pitcairn
Pacific/Ponape
Pacific/Port_Moresby
Pacific/Rarotonga
Pacific/Saipan
Pacific/Tahiti
Pacific/Tarawa
Pacific/Tongatapu
Pacific/Truk
Pacific/Wake
Pacific/Wallis
Pacific/Yap