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'; | |||||