Browse Source

first commit of consolidated util functions

includes complete tests for `date.php`
pull/1/head
Aaron Parecki 7 years ago
commit
a7ef317c98
No known key found for this signature in database GPG Key ID: 276C2817346D6056
12 changed files with 1887 additions and 0 deletions
  1. +2
    -0
      .gitignore
  2. +8
    -0
      .travis.yml
  3. +30
    -0
      composer.json
  4. +1333
    -0
      composer.lock
  5. +18
    -0
      phpunit.xml
  6. +60
    -0
      src/cache.php
  7. +39
    -0
      src/date.php
  8. +2
    -0
      src/global.php
  9. +136
    -0
      src/url.php
  10. +179
    -0
      src/utils.php
  11. +78
    -0
      tests/DateTest.php
  12. +2
    -0
      tests/bootstrap.php

+ 2
- 0
.gitignore View File

@ -0,0 +1,2 @@
vendor/
.DS_Store

+ 8
- 0
.travis.yml View File

@ -0,0 +1,8 @@
language: php
php:
- 5.5
- 5.6
- 7.0
- 7.1
- nightly
before_script: composer install

+ 30
- 0
composer.json View File

@ -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": {
}
}

+ 1333
- 0
composer.lock
File diff suppressed because it is too large
View File


+ 18
- 0
phpunit.xml View File

@ -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>

+ 60
- 0
src/cache.php View File

@ -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);
}
}

+ 39
- 0
src/date.php View File

@ -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;
}

+ 2
- 0
src/global.php View File

@ -0,0 +1,2 @@
<?php
date_default_timezone_set('UTC');

+ 136
- 0
src/url.php View File

@ -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;
}
}

+ 179
- 0
src/utils.php View File

@ -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;
}

+ 78
- 0
tests/DateTest.php View File

@ -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);
}
}

+ 2
- 0
tests/bootstrap.php View File

@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';

Loading…
Cancel
Save