includes complete tests for `date.php`pull/1/head
@ -0,0 +1,2 @@ | |||||
vendor/ | |||||
.DS_Store |
@ -0,0 +1,8 @@ | |||||
language: php | |||||
php: | |||||
- 5.5 | |||||
- 5.6 | |||||
- 7.0 | |||||
- 7.1 | |||||
- nightly | |||||
before_script: composer install |
@ -0,0 +1,30 @@ | |||||
{ | |||||
"name": "p3k/utils", | |||||
"type": "library", | |||||
"description": "Some helpful functions used by https://p3k.io projects", | |||||
"license": "MIT", | |||||
"homepage": "https://github.com/aaronpk/p3k-utils", | |||||
"authors": [ | |||||
{ | |||||
"name": "Aaron Parecki", | |||||
"homepage": "https://aaronparecki.com" | |||||
} | |||||
], | |||||
"require": { | |||||
"php": ">=5.5" | |||||
}, | |||||
"require-dev": { | |||||
"phpunit/phpunit": ">=4.8.13" | |||||
}, | |||||
"autoload": { | |||||
"files": [ | |||||
"src/global.php", | |||||
"src/url.php", | |||||
"src/utils.php", | |||||
"src/date.php", | |||||
"src/cache.php" | |||||
] | |||||
}, | |||||
"autoload-dev": { | |||||
} | |||||
} |
@ -0,0 +1,18 @@ | |||||
<?xml version="1.0"?> | |||||
<phpunit | |||||
bootstrap="tests/bootstrap.php" | |||||
beStrictAboutTestsThatDoNotTestAnything="true"> | |||||
<testsuites> | |||||
<testsuite name="comments"> | |||||
<directory suffix="Test.php">tests</directory> | |||||
</testsuite> | |||||
</testsuites> | |||||
<filter> | |||||
<whitelist processUncoveredFilesFromWhitelist="true"> | |||||
<directory suffix=".php">src</directory> | |||||
</whitelist> | |||||
</filter> | |||||
<logging> | |||||
<log type="coverage-html" target="./coverage"/> | |||||
</logging> | |||||
</phpunit> |
@ -0,0 +1,60 @@ | |||||
<?php | |||||
namespace p3k; | |||||
class Cache { | |||||
private static $redis; | |||||
public static function redis($config=false) { | |||||
if(!isset(self::$redis)) { | |||||
if($config) { | |||||
self::$redis = new \Predis\Client(Config::$redis); | |||||
} else { | |||||
self::$redis = new \Predis\Client('tcp://127.0.0.1:6379'); | |||||
} | |||||
} | |||||
} | |||||
public static function set($key, $value, $exp=600) { | |||||
self::redis(); | |||||
if($exp) { | |||||
self::$redis->setex($key, $exp, json_encode($value)); | |||||
} else { | |||||
self::$redis->set($key, json_encode($value)); | |||||
} | |||||
} | |||||
public static function get($key) { | |||||
self::redis(); | |||||
$data = self::$redis->get($key); | |||||
if($data) { | |||||
return json_decode($data); | |||||
} else { | |||||
return null; | |||||
} | |||||
} | |||||
public static function delete($key) { | |||||
self::redis(); | |||||
return self::$redis->del($key); | |||||
} | |||||
public static function expire($key, $seconds=0) { | |||||
self::redis(); | |||||
if($seconds) | |||||
return self::$redis->expire($key, $seconds); | |||||
else | |||||
return self::$redis->del($key); | |||||
} | |||||
public static function incr($key, $value=1) { | |||||
self::redis(); | |||||
return self::$redis->incrby($key, $value); | |||||
} | |||||
public static function decr($key, $value=1) { | |||||
self::redis(); | |||||
return self::$redis->decrby($key, $value); | |||||
} | |||||
} | |||||
@ -0,0 +1,39 @@ | |||||
<?php | |||||
namespace p3k\date; | |||||
use DateTime, DateTimeZone; | |||||
// $format - one of the php.net/date format strings | |||||
// $date - a string that will be passed to DateTime() | |||||
// $offset - integer timezone offset in seconds | |||||
function format_local($format, $date, $offset) { | |||||
if($offset != 0) | |||||
$tz = new DateTimeZone(($offset < 0 ? '-' : '+') . sprintf('%02d:%02d', abs(floor($offset / 60 / 60)), (($offset / 60) % 60))); | |||||
else | |||||
$tz = new DateTimeZone('UTC'); | |||||
$d = new DateTime($date); | |||||
$d->setTimeZone($tz); | |||||
return $d->format($format); | |||||
} | |||||
function tz_offset_to_seconds($offset) { | |||||
if(preg_match('/([+-])(\d{2}):?(\d{2})/', $offset, $match)) { | |||||
$sign = ($match[1] == '-' ? -1 : 1); | |||||
return (($match[2] * 60 * 60) + ($match[3] * 60)) * $sign; | |||||
} else { | |||||
return 0; | |||||
} | |||||
} | |||||
function tz_seconds_to_offset($seconds) { | |||||
return ($seconds < 0 ? '-' : '+') . sprintf('%02d:%02d', abs($seconds/60/60), ($seconds/60)%60); | |||||
} | |||||
function tz_seconds_to_timezone($seconds) { | |||||
if($seconds != 0) | |||||
$tz = new DateTimeZone(tz_seconds_to_offset($seconds)); | |||||
else | |||||
$tz = new DateTimeZone('UTC'); | |||||
return $tz; | |||||
} | |||||
@ -0,0 +1,2 @@ | |||||
<?php | |||||
date_default_timezone_set('UTC'); |
@ -0,0 +1,136 @@ | |||||
<?php | |||||
namespace p3k\url; | |||||
function display_url($url) { | |||||
# remove scheme | |||||
$url = preg_replace('/^https?:\/\//', '', $url); | |||||
# if the remaining string has no path components but has a trailing slash, remove the trailing slash | |||||
$url = preg_replace('/^([^\/]+)\/$/', '$1', $url); | |||||
return $url; | |||||
} | |||||
function add_query_params_to_url($url, $add_params) { | |||||
$parts = parse_url($url); | |||||
if(array_key_exists('query', $parts) && $parts['query']) { | |||||
parse_str($parts['query'], $params); | |||||
} else { | |||||
$params = []; | |||||
} | |||||
foreach($add_params as $k=>$v) { | |||||
$params[$k] = $v; | |||||
} | |||||
$parts['query'] = http_build_query($params); | |||||
return build_url($parts); | |||||
} | |||||
// Input: Any URL or string like "aaronparecki.com" | |||||
// Output: Normlized URL (default to http if no scheme, force "/" path) | |||||
// or return false if not a valid URL | |||||
function normalize($url) { | |||||
$parts = parse_url($url); | |||||
if(array_key_exists('path', $parts) && $parts['path'] == '') | |||||
return false; | |||||
// parse_url returns just "path" for naked domains | |||||
if(count($parts) == 1 && array_key_exists('path', $parts)) { | |||||
$parts['host'] = $parts['path']; | |||||
unset($parts['path']); | |||||
} | |||||
if(!array_key_exists('scheme', $parts)) | |||||
$parts['scheme'] = 'http'; | |||||
if(!array_key_exists('path', $parts)) | |||||
$parts['path'] = '/'; | |||||
// Invalid scheme | |||||
if(!in_array($parts['scheme'], array('http','https'))) | |||||
return false; | |||||
return build_url($parts); | |||||
} | |||||
// Inverse of parse_url() | |||||
// http://php.net/parse_url | |||||
function build_url($parsed_url) { | |||||
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; | |||||
$host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; | |||||
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; | |||||
$user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; | |||||
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; | |||||
$pass = ($user || $pass) ? "$pass@" : ''; | |||||
$path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; | |||||
$query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; | |||||
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; | |||||
return "$scheme$user$pass$host$port$path$query$fragment"; | |||||
} | |||||
function parse($url) { | |||||
return parse_url($url); | |||||
} | |||||
function host_matches($a, $b) { | |||||
return parse_url($a, PHP_URL_HOST) == parse_url($b, PHP_URL_HOST); | |||||
} | |||||
function is_url($url) { | |||||
return is_string($url) && preg_match('/^https?:\/\/[a-z0-9\.\-]\/?/', $url); | |||||
} | |||||
function http_header_case($str) { | |||||
$str = str_replace('-', ' ', $str); | |||||
$str = ucwords($str); | |||||
$str = str_replace(' ', '-', $str); | |||||
return $str; | |||||
} | |||||
function is_public_ip($ip) { | |||||
// http://stackoverflow.com/a/30143143 | |||||
//Private ranges... | |||||
//http://www.iana.org/assignments/iana-ipv4-special-registry/ | |||||
$networks = array('10.0.0.0' => '255.0.0.0', //LAN. | |||||
'172.16.0.0' => '255.240.0.0', //LAN. | |||||
'192.168.0.0' => '255.255.0.0', //LAN. | |||||
'127.0.0.0' => '255.0.0.0', //Loopback. | |||||
'169.254.0.0' => '255.255.0.0', //Link-local. | |||||
'100.64.0.0' => '255.192.0.0', //Carrier. | |||||
'192.0.2.0' => '255.255.255.0', //Testing. | |||||
'198.18.0.0' => '255.254.0.0', //Testing. | |||||
'198.51.100.0' => '255.255.255.0', //Testing. | |||||
'203.0.113.0' => '255.255.255.0', //Testing. | |||||
'192.0.0.0' => '255.255.255.0', //Reserved. | |||||
'224.0.0.0' => '224.0.0.0', //Reserved. | |||||
'0.0.0.0' => '255.0.0.0'); //Reserved. | |||||
$ip = @inet_pton($ip); | |||||
if (strlen($ip) !== 4) { return false; } | |||||
//Is the IP in a private range? | |||||
foreach($networks as $network_address => $network_mask) { | |||||
$network_address = inet_pton($network_address); | |||||
$network_mask = inet_pton($network_mask); | |||||
assert(strlen($network_address) === 4); | |||||
assert(strlen($network_mask) === 4); | |||||
if (($ip & $network_mask) === $network_address) | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
function geo_to_latlng($uri) { | |||||
if(preg_match('/geo:([\-\+]?[0-9\.]+),([\-\+]?[0-9\.]+)/', $uri, $match)) { | |||||
return array( | |||||
'latitude' => (double)$match[1], | |||||
'longitude' => (double)$match[2], | |||||
); | |||||
} else { | |||||
return false; | |||||
} | |||||
} | |||||
@ -0,0 +1,179 @@ | |||||
<?php | |||||
namespace p3k; | |||||
function redis() { | |||||
static $client = false; | |||||
if(!$client) | |||||
$client = new Predis\Client(Config::$redis); | |||||
return $client; | |||||
} | |||||
function bs() | |||||
{ | |||||
static $pheanstalk; | |||||
if(!isset($pheanstalk)) | |||||
$pheanstalk = new Pheanstalk\Pheanstalk(Config::$beanstalkServer, Config::$beanstalkPort); | |||||
return $pheanstalk; | |||||
} | |||||
function initdb() { | |||||
ORM::configure('mysql:host=' . Config::$db['host'] . ';dbname=' . Config::$db['database']); | |||||
ORM::configure('username', Config::$db['username']); | |||||
ORM::configure('password', Config::$db['password']); | |||||
} | |||||
function e($text) { | |||||
return htmlspecialchars($text); | |||||
} | |||||
function k($a, $k, $default=null) { | |||||
if(is_array($k)) { | |||||
$result = true; | |||||
foreach($k as $key) { | |||||
$result = $result && array_key_exists($key, $a); | |||||
} | |||||
return $result; | |||||
} else { | |||||
if(is_array($a) && array_key_exists($k, $a)) | |||||
return $a[$k]; | |||||
elseif(is_object($a) && property_exists($a, $k)) | |||||
return $a->$k; | |||||
else | |||||
return $default; | |||||
} | |||||
} | |||||
function random_string($len) { | |||||
$charset='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | |||||
$str = ''; | |||||
$c = strlen($charset)-1; | |||||
for($i=0; $i<$len; $i++) { | |||||
$str .= $charset[mt_rand(0, $c)]; | |||||
} | |||||
return $str; | |||||
} | |||||
// Returns true if $needle is the end of the $haystack | |||||
function str_ends_with($haystack, $needle) { | |||||
if($needle == '' || $haystack == '') return false; | |||||
return strpos(strrev($haystack), strrev($needle)) === 0; | |||||
} | |||||
// Sets up the session. | |||||
// If create is true, the session will be created even if there is no cookie yet. | |||||
// If create is false, the session will only be set up in PHP if they already have a session cookie. | |||||
function session_setup($create=false, $lifetime=2592000) { | |||||
if($create || isset($_COOKIE[session_name()])) { | |||||
session_set_cookie_params($lifetime); | |||||
session_start(); | |||||
} | |||||
} | |||||
function session($key) { | |||||
if(array_key_exists($key, $_SESSION)) | |||||
return $_SESSION[$key]; | |||||
else | |||||
return null; | |||||
} | |||||
function flash($key) { | |||||
if(isset($_SESSION) && isset($_SESSION[$key])) { | |||||
$value = $_SESSION[$key]; | |||||
unset($_SESSION[$key]); | |||||
return $value; | |||||
} | |||||
} | |||||
function html_to_dom_document($html) { | |||||
// Parse the source body as HTML | |||||
$doc = new DOMDocument(); | |||||
libxml_use_internal_errors(true); # suppress parse errors and warnings | |||||
$body = mb_convert_encoding($html, 'HTML-ENTITIES', mb_detect_encoding($html)); | |||||
@$doc->loadHTML($body, LIBXML_NOWARNING|LIBXML_NOERROR); | |||||
libxml_clear_errors(); | |||||
return $doc; | |||||
} | |||||
function xml_to_dom_document($xml) { | |||||
// Parse the source body as XML | |||||
$doc = new DOMDocument(); | |||||
libxml_use_internal_errors(true); # suppress parse errors and warnings | |||||
// $body = mb_convert_encoding($xml, 'HTML-ENTITIES', mb_detect_encoding($xml)); | |||||
$body = $xml; | |||||
$doc->loadXML($body); | |||||
libxml_clear_errors(); | |||||
return $doc; | |||||
} | |||||
// Reads the exif rotation data and actually rotates the photo. | |||||
// Only does anything if the exif library is loaded, otherwise is a noop. | |||||
function correct_photo_rotation($filename) { | |||||
if(class_exists('IMagick')) { | |||||
try { | |||||
$image = new IMagick($filename); | |||||
$orientation = $image->getImageOrientation(); | |||||
switch($orientation) { | |||||
case IMagick::ORIENTATION_BOTTOMRIGHT: | |||||
$image->rotateImage(new ImagickPixel('#00000000'), 180); | |||||
break; | |||||
case IMagick::ORIENTATION_RIGHTTOP: | |||||
$image->rotateImage(new ImagickPixel('#00000000'), 90); | |||||
break; | |||||
case IMagick::ORIENTATION_LEFTBOTTOM: | |||||
$image->rotateImage(new ImagickPixel('#00000000'), -90); | |||||
break; | |||||
} | |||||
$image->setImageOrientation(IMagick::ORIENTATION_TOPLEFT); | |||||
$image->writeImage($filename); | |||||
} catch(Exception $e){} | |||||
} | |||||
} | |||||
/** | |||||
* Converts base 10 to base 60. | |||||
* http://tantek.pbworks.com/NewBase60 | |||||
* @param int $n | |||||
* @return string | |||||
*/ | |||||
function b10to60($n) | |||||
{ | |||||
$s = ""; | |||||
$m = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz"; | |||||
if ($n==0) | |||||
return 0; | |||||
while ($n>0) | |||||
{ | |||||
$d = $n % 60; | |||||
$s = $m[$d] . $s; | |||||
$n = ($n-$d)/60; | |||||
} | |||||
return $s; | |||||
} | |||||
/** | |||||
* Converts base 60 to base 10, with error checking | |||||
* http://tantek.pbworks.com/NewBase60 | |||||
* @param string $s | |||||
* @return int | |||||
*/ | |||||
function b60to10($s) | |||||
{ | |||||
$n = 0; | |||||
for($i = 0; $i < strlen($s); $i++) // iterate from first to last char of $s | |||||
{ | |||||
$c = ord($s[$i]); // put current ASCII of char into $c | |||||
if ($c>=48 && $c<=57) { $c=$c-48; } | |||||
else if ($c>=65 && $c<=72) { $c-=55; } | |||||
else if ($c==73 || $c==108) { $c=1; } // typo capital I, lowercase l to 1 | |||||
else if ($c>=74 && $c<=78) { $c-=56; } | |||||
else if ($c==79) { $c=0; } // error correct typo capital O to 0 | |||||
else if ($c>=80 && $c<=90) { $c-=57; } | |||||
else if ($c==95) { $c=34; } // underscore | |||||
else if ($c>=97 && $c<=107) { $c-=62; } | |||||
else if ($c>=109 && $c<=122) { $c-=63; } | |||||
else { $c = 0; } // treat all other noise as 0 | |||||
$n = (60 * $n) + $c; | |||||
} | |||||
return $n; | |||||
} |
@ -0,0 +1,78 @@ | |||||
<?php | |||||
class DateTest extends PHPUnit_Framework_TestCase { | |||||
public function testFormatLocalPositiveOffset() { | |||||
$local = p3k\date\format_local('c', '2017-05-01T13:30:00+0000', 7200); | |||||
$this->assertEquals('2017-05-01T15:30:00+02:00', $local); | |||||
} | |||||
public function testFormatLocalNegativeOffset() { | |||||
$local = p3k\date\format_local('c', '2017-05-01T13:30:00+0000', -25200); | |||||
$this->assertEquals('2017-05-01T06:30:00-07:00', $local); | |||||
} | |||||
public function testFormatLocalZeroOffset() { | |||||
$local = p3k\date\format_local('c', '2017-05-01T13:30:00+0200', 0); | |||||
$this->assertEquals('2017-05-01T11:30:00+00:00', $local); | |||||
} | |||||
public function testTZSecondsToTimezonePositive() { | |||||
$tz = p3k\date\tz_seconds_to_timezone(7200); | |||||
$this->assertInstanceOf(DateTimeZone::class, $tz); | |||||
$this->assertEquals('+02:00', $tz->getName()); | |||||
} | |||||
public function testTZSecondsToTimezoneNegative() { | |||||
$tz = p3k\date\tz_seconds_to_timezone(-25200); | |||||
$this->assertInstanceOf(DateTimeZone::class, $tz); | |||||
$this->assertEquals('-07:00', $tz->getName()); | |||||
} | |||||
public function testTZSecondsToTimezoneZero() { | |||||
$tz = p3k\date\tz_seconds_to_timezone(0); | |||||
$this->assertInstanceOf(DateTimeZone::class, $tz); | |||||
$this->assertEquals('UTC', $tz->getName()); | |||||
} | |||||
public function testTZOffsetToSecondsPositive() { | |||||
$seconds = p3k\date\tz_offset_to_seconds('+02:00'); | |||||
$this->assertEquals(7200, $seconds); | |||||
$seconds = p3k\date\tz_offset_to_seconds('+0200'); | |||||
$this->assertEquals(7200, $seconds); | |||||
} | |||||
public function testTZOffsetToSecondsNegative() { | |||||
$seconds = p3k\date\tz_offset_to_seconds('-07:00'); | |||||
$this->assertEquals(-25200, $seconds); | |||||
$seconds = p3k\date\tz_offset_to_seconds('-0700'); | |||||
$this->assertEquals(-25200, $seconds); | |||||
} | |||||
public function testTZOffsetToSecondsZero() { | |||||
$seconds = p3k\date\tz_offset_to_seconds('+00:00'); | |||||
$this->assertEquals(0, $seconds); | |||||
$seconds = p3k\date\tz_offset_to_seconds('+0000'); | |||||
$this->assertEquals(0, $seconds); | |||||
} | |||||
public function testTZOffsetToSecondsInvalid() { | |||||
$seconds = p3k\date\tz_offset_to_seconds('foo'); | |||||
$this->assertEquals(0, $seconds); | |||||
} | |||||
public function testTZSecondsToOffsetPositive() { | |||||
$offset = p3k\date\tz_seconds_to_offset(7200); | |||||
$this->assertEquals('+02:00', $offset); | |||||
} | |||||
public function testTZSecondsToOffsetNegative() { | |||||
$offset = p3k\date\tz_seconds_to_offset(-25200); | |||||
$this->assertEquals('-07:00', $offset); | |||||
} | |||||
public function testTZSecondsToOffsetZero() { | |||||
$offset = p3k\date\tz_seconds_to_offset(0); | |||||
$this->assertEquals('+00:00', $offset); | |||||
} | |||||
} |
@ -0,0 +1,2 @@ | |||||
<?php | |||||
require_once __DIR__ . '/../vendor/autoload.php'; |