diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f68726 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor/ +.rnd \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 1446050..6ab3587 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,12 @@ language: php sudo: false php: - - 5.4 + - 5.6 before_script: - composer self-update - composer --version - composer install + +script: + - php build.php app:validate \ No newline at end of file diff --git a/README.md b/README.md index db24381..a32cbb5 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,18 @@ Firelit-Framework =============== [![Build Status](https://travis-ci.org/firelit/firelit-framework.png?branch=master)](https://travis-ci.org/firelit/firelit-framework) -Firelit's standard PHP framework provides a set of helpful classes for developing a website. They are created and namespaced so that they can easily be used with an auto-loader, following the [PSR-0 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md). +Firelit's standard PHP framework provides a set of helpful classes for developing a website. They are created and namespaced so that they can easily be used with an auto-loader, following the [PSR-4 standard](http://www.php-fig.org/psr/psr-4/). Requirements ------------ -- PHP version 5.4.0 and higher -- MultiByte PHP extension -- Mcrypt PHP extension (required for `Crypto` class) -- cURL PHP extension (required for `HttpRequest` class) -- PDO PHP extension (required for `Query` class) -- SQLite PHP extension (required for `Query` class unit tests) +- PHP version 5.4.0 or higher +- PHP version 5.6.0 or higher for testing + +External PHP Extensions: +- OpenSSL extension (required for `Crypto` and `CryptoKey` class) +- cURL extension (required for `HttpRequest` class) +- Database-specific PDO extension (e.g., `pdo-mysql`, required for `Query` class) How to Use ---------- @@ -22,13 +23,13 @@ The easiest way to use this library is to use [Composer](http://getcomposer.org/ Here is an example of how you'd add this package to your `composer.json` under the require key: ```js "require": { - "firelit/framework": "^1.0" + "firelit/framework": "^2.0" } ``` You could also add it from the command line as follows: ``` -php composer.phar require firelit/framework "^1.0" +php composer.phar require firelit/framework "^2.0" ``` Alternatively, you could go the manual way and setup your own autoloader and copy the project files from `lib/` into your project directory. @@ -37,12 +38,72 @@ MVC Architecture ---------------- This framework comes with classes to support building apps with the MVC architecture. -- `Firelit\View` class -- `Firelit\Controller` class -- `Firelit\DatabaseObject` (i.e., model) class -- `Firelit\Router` class +- [`Firelit\View`](#view) class +- [`Firelit\Controller`](#controller) class +- [`Firelit\DatabaseObject`](#databaseobject) (i.e., model) class +- `Firelit\Router` class (see example below) + +An example implementation using these classes in a single entry web app: + +```php +add('GET', '!^/Hello$!', function() { + // Simple route, you'd go to http://example.com/Hello and get this: + echo 'World!'; +}); + +$router->add('GET', '!^/redirect$!', function() use ($resp) { + // Redirect example + $resp->redirect('/to/here'); +}); + +$router->add('POST', '!^/forms!', function() { + // Process the POST request in a controller + Firelit\Controller::handoff('Controller\Forms', 'process'); +}); + +$router->add('GET', '!^/groups/([^0-9]+)$!', function($matches) { + // Match URL parts with regular expressions to extract information + echo 'You selected group #'. $matches[0]; +}); + +// You can nest routes to keep things organized +$myNestedRoutes = require_once('routes/api.php'); +$router->add('GET', '!^/api/!', $myNestedRoutes); + +$router->defaultRoute(function() use ($resp) { + // A default route is a backstop, catching any routes that don't match + $resp->code(404); + echo 'Sorry, no can do'; +}); -TODO: More documentation here! +$router->exceptionHandler(function($e) use ($resp) { + $resp->setCode(500); + echo $e->getMessage(); + exit; +}); + +$router->go(); +``` + +Note that this setup is considered single-entry so there must be a slight modification to web server to force it to use the main script (e.g., index.php) for all HTTP requests. Here's an example `.htaccess` (from the WordPress project) that will configure Apache to route all requests to a single entry script. + +```apache + +RewriteEngine On +RewriteBase / +RewriteRule ^index\.php$ - [L] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule . /index.php [L] + +``` Classes Included ---------------- @@ -107,19 +168,130 @@ if (Firelit\Cache::$cacheHit) Firelit\Cache::set('randomValue', null); ``` -### Crypto +### Controller -A symmetrical-key encryption/decryption helper class (uses MCRYPT_RIJNDAEL_256 aka AES) with HMAC and automatic initialization vector creation. +A minimal class to wrap controller logic. Extend to add additional controller-common methods and static data. It also has a "handoff" function call to simplify invocations to Controller objects. + +Handoff syntax: +`Firelit\Controller::handoff(ControllerClassName [, OptionalMethodName [, MethodParamater1 [, ... ]]])` + +```php + $userName)); + } + +} + +// You could invoke this as you would any class, but the handoff method makes it a one-liner: +Firelit\Controller::handoff('DashboardController', 'show', $userName); +``` + +### Crypto Classes + +Crypto, CryptoKey and CryptoPackage are encryption/decryption helper classes using OpenSSL (used in lieu of mcrypt based on [this article](https://paragonie.com/blog/2015/05/if-you-re-typing-word-mcrypt-into-your-code-you-re-doing-it-wrong)). These classes can generate cryptographically secure secure keys and encrypt and decrypt using industry-standard symmetric encryption (RSA) and private key encryption (AES) schemes. + +Note that AES encryption will not work for large strings (80 characters or more, depending on key bit size) due to the amount of processing power it takes -- it quickly becomes inefficient. For larger strings, the plain text should be encrypted with RSA and the encryption key should be encrypted with AES. This is exactly what CryptoPackage does for you on top of serializing/unserializing the subject to quickly store and retrieve variables of any type (string, object, array, etc) in an encrypted store. Example encryption/decryption usage: ```php encrypt($mySecret)->with(Firelit\Crypto::PUBLIC_KEY); + +$plaintext = $crypto->decrypt($ciphertext)->with(Firelit\Crypto::PRIVATE_KEY); + +// Symmetric key encryption +$key = Firelit\CryptoKey::newSymmetricKey(); // Can be 128, 192 or 256-bit +$crypto = new Firelit\Crypto($key); + +$ciphertext = $crypto->encrypt($mySecret); + +$plaintext = $crypto->decrypt($ciphertext); + +// Robust, mixed-type private key encryption +$key = Firelit\CryptoKey::newPrivateKey(); // Can be 1024, 2048 or 3072-bit +$crypto = new Firelit\CryptoPackage($key); + +$object = (object) array( + 'test' => true, + 'string' => $mySecret . $mySecret . $mySecret +); + +$ciphertext = $crypto->encrypt($object)->with(Firelit\Crypto::PUBLIC_KEY); + +$objectBack = $crypto->decrypt($ciphertext)->with(Firelit\Crypto::PRIVATE_KEY); +``` + +### DatabaseObject + +This class is a schema-less, active record-like class for creating, retrieving and manipulating a database row as an object. To set it up, extend the class for each table, specifing the primary key row and other special-value rows (e.g., serialized rows, date/time rows, etc.) to enable built-in pre-store and post-retrieval value manipulation. + +```php + 'Sally', + 'email' => 'sally@example.com', + 'nicknames' => array('Sal'), + 'created' => new DateTime() +)); + +// Find by the primary key +$person = Person::find(23); + +// Returns FALSE if not found +if (!$person) die('Not found'); + +// Serialized columns are serialized before being saved and unserialized on retrieval +$person->nicknames = array( + 'Bill', + 'Will', + 'William' +); + +// Easily save to database (uses primary key set during retrieval) +$person->save(); + +// Or find rows by other columns +$persons = Person::findBy('email', 'test@test.com'); + +// DatabaseObject::findBy returns an iterable object (Firelit\QueryIterator) for search results +foreach ($persons as $person) { + // DateTime columns are converted to DateTime objects on retrieval + echo $person->name .' was created '. $person->created->format('n/j/y') . PHP_EOL; +} -$decrypted = Firelit\Crypto::unpackage($encrypted, $mySecretPassword); ``` ### HttpRequest @@ -225,9 +397,10 @@ Example usage: ```php query("SELECT * FROM `TableName` WHERE `type`=:type", array('type' => $type)); @@ -325,23 +498,97 @@ A set of string helper functions wrapped into a class. Example usage: ```php 'Vars', + 'col_name' => 'Name', + 'col_value' => 'Value' +)); -$vars = new Firelit\Vars( Firelit\VarsStore::init('DB') ); +// Later usage +$vars = new Firelit\Vars::init(); -// Set a persistent application variable +// Set a persistent application variable with any name accepted by your store $vars->maintenanceMode = true; // Read a persistent application variable if ($vars->maintenanceMode) die('Sorry, under construction.'); ``` + +### View + +A class for managing views: View templates, view layouts (which wraps around a view template), partials (part of a view that is extracted for use elsewhere) and asset management (auto-inserting of required assets into the HTML ). + +Layouts are like view templates in which view templates reside. They typically contain the repeating HTML wrappers around the more view-specific elements. Layouts are an optional component. If no layout is specified, the view template is rendered on its own. + +An example layout, saved to `views\layouts\main.php`: + +```php + + + + Example Layout + + + yieldNow(); ?> + + +``` + +Now an example view file, saved to `views\templates\dashboard.php`: + +```php + +
+ +
+
+

Dashboard

+

Lorem Ipsum

+ includePart('partials\stuff'); ?> +
+``` + +When the view is invoked, the data used in the view (see `$name` above as an example) is specified using an associative array. + +```php +render(array('name' => 'John')); +``` diff --git a/build.php b/build.php new file mode 100644 index 0000000..9b62882 --- /dev/null +++ b/build.php @@ -0,0 +1,88 @@ +#!/usr/bin/php +setName('app:validate') + // the short description shown while running "php bin/console list" + ->setDescription('Runs all unit tests and code sniffing'); + + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln('Code sniffing...'); + + $exitCode = 0; + + passthru('vendor/bin/phpcs -n -p --standard=PSR2 --extensions=php src/ tests/', $exitCode); + + if ($exitCode) { + $output->writeln('Code sniffing failed!'); + return; + } + + $output->writeln('Unit tests...'); + + $exitCode = 0; + + passthru('vendor/bin/phpunit -c phpunit.xml tests/', $exitCode); + + if ($exitCode) { + $output->writeln('Unit tests failed!'); + return; + } + + $output->writeln('All good!'); + + } +} + +class FixCommand extends Command +{ + protected function configure() + { + $this + // the name of the command (the part after "bin/console") + ->setName('app:fix') + // the short description shown while running "php bin/console list" + ->setDescription('Auto-fix the code sniffing failures if possible'); + + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln('Fixing code formatting...'); + + $exitCode = 0; + + passthru('vendor/bin/phpcbf --standard=PSR2 --extensions=php src/ tests/', $exitCode); + + if ($exitCode) { + $output->writeln('Something broke!'); + return; + } + + $output->writeln('All Fixed!'); + + } +} + +// ... register commands +$application->add(new ValidateCommand); +$application->add(new FixCommand); + +$application->run(); \ No newline at end of file diff --git a/composer.json b/composer.json index bfb7da6..676b657 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "ext-mbstring": "*" }, "autoload": { - "psr-0": { "Firelit": "lib/" } + "psr-4": { "Firelit\\": "src/" } }, "authors": [ { @@ -18,5 +18,10 @@ "homepage": "http://www.firelit.com", "role": "Developer" } - ] + ], + "require-dev": { + "squizlabs/php_codesniffer": "2.*", + "phpunit/phpunit": "5.5.*", + "symfony/console": "3.*" + } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..16bac99 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1626 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "9127664c168bed4b4057468f1ec9466d", + "content-hash": "6cb7f2e830bb13d99ed8f1090ddd6bb4", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "myclabs/deep-copy", + "version": "1.5.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "ea74994a3dc7f8d2f65a06009348f2d63c81e61f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/ea74994a3dc7f8d2f65a06009348f2d63c81e61f", + "reference": "ea74994a3dc7f8d2f65a06009348f2d63c81e61f", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "doctrine/collections": "1.*", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "homepage": "https://github.com/myclabs/DeepCopy", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2016-09-16 13:37:59" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27 11:43:31" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2016-09-30 07:12:33" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2016-06-10 07:14:17" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2016-06-07 08:13:47" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5f3f7e736d6319d5f1fc402aff8b026da26709a3", + "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "^1.4.2", + "sebastian/code-unit-reverse-lookup": "~1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "~1.0|~2.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.4.0", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2016-07-26 14:39:29" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-06-21 13:08:43" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4|~5" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2016-05-12 18:03:57" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-09-15 10:49:45" + }, + { + "name": "phpunit/phpunit", + "version": "5.5.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "3f67cee782c9abfaee5e32fd2f57cdd54bc257ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3f67cee782c9abfaee5e32fd2f57cdd54bc257ba", + "reference": "3f67cee782c9abfaee5e32fd2f57cdd54bc257ba", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "^4.0.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "^1.3 || ^2.0", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/object-enumerator": "~1.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "~1.0|~2.0", + "symfony/yaml": "~2.1|~3.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-tidy": "*", + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2016-10-03 13:04:15" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "238d7a2723bce689c79eeac9c7d5e1d623bb9dc2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/238d7a2723bce689c79eeac9c7d5e1d623bb9dc2", + "reference": "238d7a2723bce689c79eeac9c7d5e1d623bb9dc2", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2016-10-09 07:01:45" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10 12:19:37" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2016-02-13 06:45:14" + }, + { + "name": "sebastian/comparator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2015-07-26 15:48:44" + }, + { + "name": "sebastian/diff", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-12-08 07:14:41" + }, + { + "name": "sebastian/environment", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-08-18 05:49:44" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17 09:04:28" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/object-enumerator", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "d4ca2fb70344987502567bc50081c03e6192fb26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/d4ca2fb70344987502567bc50081c03e6192fb26", + "reference": "d4ca2fb70344987502567bc50081c03e6192fb26", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2016-01-28 13:25:10" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-11-11 19:50:13" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28 20:34:47" + }, + { + "name": "sebastian/version", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", + "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-02-04 12:56:52" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "571e27b6348e5b3a637b2abc82ac0d01e6d7bbed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/571e27b6348e5b3a637b2abc82ac0d01e6d7bbed", + "reference": "571e27b6348e5b3a637b2abc82ac0d01e6d7bbed", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2016-09-01 23:53:02" + }, + { + "name": "symfony/console", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "6cb0872fb57b38b3b09ff213c21ed693956b9eb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/6cb0872fb57b38b3b09ff213c21ed693956b9eb0", + "reference": "6cb0872fb57b38b3b09ff213c21ed693956b9eb0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/debug": "~2.8|~3.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2016-09-28 00:11:12" + }, + { + "name": "symfony/debug", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "e2b3f74a67fc928adc3c1b9027f73e1bc01190a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/e2b3f74a67fc928adc3c1b9027f73e1bc01190a8", + "reference": "e2b3f74a67fc928adc3c1b9027f73e1bc01190a8", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2016-09-06 11:02:40" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "dff51f72b0706335131b00a7f49606168c582594" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594", + "reference": "dff51f72b0706335131b00a7f49606168c582594", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2016-05-18 14:26:46" + }, + { + "name": "symfony/yaml", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "368b9738d4033c8b93454cb0dbd45d305135a6d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/368b9738d4033c8b93454cb0dbd45d305135a6d3", + "reference": "368b9738d4033c8b93454cb0dbd45d305135a6d3", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-09-25 08:27:07" + }, + { + "name": "webmozart/assert", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bb2d123231c095735130cc8f6d31385a44c7b308" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bb2d123231c095735130cc8f6d31385a44c7b308", + "reference": "bb2d123231c095735130cc8f6d31385a44c7b308", + "shasum": "" + }, + "require": { + "php": "^5.3.3|^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2016-08-09 15:02:57" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.0", + "ext-mbstring": "*" + }, + "platform-dev": [] +} diff --git a/config/openssl.cnf b/config/openssl.cnf new file mode 100644 index 0000000..38b2d4f --- /dev/null +++ b/config/openssl.cnf @@ -0,0 +1,6 @@ +# minimalist openssl.cnf file + +HOME = . +RANDFILE = $ENV::HOME/.rnd + +[ v3_ca ] diff --git a/lib/Firelit/ApiResponse.php b/lib/Firelit/ApiResponse.php deleted file mode 100644 index a3e3f7a..0000000 --- a/lib/Firelit/ApiResponse.php +++ /dev/null @@ -1,159 +0,0 @@ -responseFormat = strtoupper($responseFormat); - - // Set appropriate output formats - if ($this->responseFormat == 'JSON') - $this->setContentType('application/json'); - - } - - /** - * Set the template or defaults for response - * @param Array $template Template response to send - */ - public function setTemplate($template) { - // Set an output template to be sure minimum required fields are returned (eg, there's always a 'success' property) - $this->response = array_merge($this->response, $template); - } - - /** - * Set response data - * @param Array $response Response to send - * @param Bool $replaceResponse Indicates if response should be completely replaced with previous param (instead of merged) - */ - public function set($response, $replaceResponse = false) { - if ($replaceResponse) $this->response = $response; - else $this->response = array_merge($this->response, $response); - } - - /** - * Sending the response, if it hasn't already been sent - * @param Array $response Response to send - * @param Bool $replaceResponse Indicates if response should be completely replaced with previous param (instead of merged) - */ - public function respond($response = array(), $replaceResponse = false) { - // Set any final data - $this->set($response, $replaceResponse); - - // Make sure it only responds once - if ($this->hasResponded()) return; - - // Clear anything that may have snuck in - $this->cleanBuffer(); - - if (self::$code == 204) { - // No-body response - $this->responseSent = true; - return; - } - - if (!empty($this->apiResponseCallback) && is_callable($this->apiResponseCallback)) { - $callback = &$this->apiResponseCallback; - $callback($this->response); - } - - // Format for specific output type - if ($this->responseFormat == 'JSON') { - - if ($this->jsonCallbackWrap) echo $this->jsonCallbackWrap . '('; - - echo json_encode($this->response); - - if ($this->jsonCallbackWrap) echo ');'; - - } else { - throw new \Exception('Invalid response format: '. $this->responseFormat); - } - - // Indicate the response as already sent - $this->responseSent = true; - } - - /** - * Already sent response? - * @return Bool - */ - public function hasResponded() { - return $this->responseSent; - } - - /** - * Cancel the ApiResponse (allows switch to output of non-api data if needed) - */ - public function cancel() { - // No longer need a response - $this->responseSent = true; - } - - /** - * Set a function to be wrapped around JSON response (for JSONP) - * @param String $callback The function name - */ - public function setJsonCallbackWrap($callback) { - - if (empty($callback)) { - $this->jsonCallbackWrap = false; - return; - } - - if (!is_string($callback)) - throw new \Exception('JSON callback wrap should be a string or false.'); - - $this->jsonCallbackWrap = $callback; - - } - - /** - * Set a function to be called upon response (or false to cancel) - * @param Funcation $callback The function to call with 1 parameter being the data to return (pass by reference to modify) - */ - public function setApiCallback($callback) { - - if (empty($callback)) { - $this->apiResponseCallback = false; - return; - } - - if (!is_callable($callback)) - throw new \Exception('Callback should be a function or false.'); - - $this->apiResponseCallback = $callback; - - } - - /** - * Most likely called at end of execution, outputs data as needed - */ - public function __destruct() { - - // Respond, if it hasn't yet - if (!$this->hasResponded()) - $this->respond(array(), false); - - // Parent's destructer returns all data in output buffer - parent::__destruct(); - - } - -} \ No newline at end of file diff --git a/lib/Firelit/Cache.php b/lib/Firelit/Cache.php deleted file mode 100644 index 096fd14..0000000 --- a/lib/Firelit/Cache.php +++ /dev/null @@ -1,156 +0,0 @@ - array( - 'enabled' => false, - 'servers' => array() - ) - ); - - protected static $memcached = false, $cache = array(); - - // Default settings for each memcached server - public static $memcachedServerDefaults = array( - 'host' => 'localhost', - 'port' => 11211, - 'persistent' => true, - 'weight' => 1, - 'timeout' => 1 - ); - - // Boolean indicating if a get() resulted in a cache hit - public static $cacheHit = false; - - /** - * __construct() - */ - public function __construct() { } - - /** - * config() - * @param array $config Updated configuration array - */ - public static function config($config) { - - self::$config = array_merge(self::$config, $config); - - } - - /** - * get() - * @param string $name Name of variable as stored in cache - * @param string $closure Optional closure to get value if cache miss - * @return mixed The cached value (or closure-returned value, if cache miss), null if cache-miss and no closure - */ - public static function get($name, $closure = false) { - - if (!is_string($name)) - throw new \Exception('Cache key must be a string.'); - - // First check php-memory cache - if (isset(self::$cache[$name])) { - - // Cache hit! - self::$cacheHit = true; - return self::$cache[$name]; - - } - - if (self::$config['memcached']['enabled']) { - - // Connect if not connected - self::memcachedConnect(); - - // Check if in memcached - $val = self::$memcached->get($name); - - if (self::$memcached->getResultCode() == \Memcached::RES_SUCCESS) { - - // Cache hit! - self::$cacheHit = true; - - // Set php-memory cache - self::$cache[$name] = $val; - return $val; - - } - - } - - self::$cacheHit = false; - - // If no other way to get value, return - if (!is_callable($closure)) return null; - - // Call given closure to get value - $val = $closure(); - - // Nothing returned, no need to store it - if (is_null($val)) return null; - - // Store closure-returned value in cache - self::set($name, $val); - - return $val; - - } - - /** - * set() - * @param string $name Name of variable to be stored in cache - * @param string $val Value of variable to be stored, null to delete value from cache - */ - public static function set($name, $val) { - - if (!is_string($name)) - throw new \Exception('Cache key must be a string.'); - - // Connect if not connected - if (self::$config['memcached']['enabled']) - self::memcachedConnect(); - - if (is_null($val)) { - // If $val is null, remove from cache - unset(self::$cache[$name]); - - // Remove from memcached - if (self::$config['memcached']['enabled']) - self::$memcached->delete($name); - - } else { - // Store in php-memory cache - self::$cache[$name] = $val; - - // Store in memcached - if (self::$config['memcached']['enabled']) - self::$memcached->set($name, $val); - - } - - } - - /** - * memcachedConnect() - */ - public static function memcachedConnect() { - - if (self::$memcached) return; - - self::$memcached = new \Memcached(); - - foreach (self::$config['memcached']['servers'] as $server) { - - extract(array_merge(self::$memcachedServerDefaults, $server)); - - self::$memcached->addServers($host, $port, $persistent, $weight, $timeout); - - } - - } - -} diff --git a/lib/Firelit/Controller.php b/lib/Firelit/Controller.php deleted file mode 100644 index 01944a2..0000000 --- a/lib/Firelit/Controller.php +++ /dev/null @@ -1,40 +0,0 @@ -newInstanceArgs($method ? array() : $args); - - if ($method) { - // Execute a method - $reflectMethod = new \ReflectionMethod($controller, $method); - return $reflectMethod->invokeArgs($newClass, $args); - } - - // If method specified, return the result of that method call (above) - // Else return the object itself - return $newClass; - - } - -} \ No newline at end of file diff --git a/lib/Firelit/Countries.php b/lib/Firelit/Countries.php deleted file mode 100644 index 7340c45..0000000 --- a/lib/Firelit/Countries.php +++ /dev/null @@ -1,321 +0,0 @@ - "Afghanistan", - "AX" => "Ã…land Islands", - "AL" => "Albania", - "DZ" => "Algeria", - "AS" => "American Samoa", - "AD" => "Andorra", - "AO" => "Angola", - "AI" => "Anguilla", - "AQ" => "Antarctica", - "AG" => "Antigua and Barbuda", - "AR" => "Argentina", - "AM" => "Armenia", - "AW" => "Aruba", - "AU" => "Australia", - "AT" => "Austria", - "AZ" => "Azerbaijan", - "BS" => "Bahamas", - "BH" => "Bahrain", - "BD" => "Bangladesh", - "BB" => "Barbados", - "BY" => "Belarus", - "BE" => "Belgium", - "BZ" => "Belize", - "BJ" => "Benin", - "BM" => "Bermuda", - "BT" => "Bhutan", - "BO" => "Bolivia", - "BA" => "Bosnia and Herzegovina", - "BW" => "Botswana", - "BV" => "Bouvet Island", - "BR" => "Brazil", - "IO" => "British Indian Ocean Territory", - "BN" => "Brunei Darussalam", - "BG" => "Bulgaria", - "BF" => "Burkina Faso", - "BI" => "Burundi", - "KH" => "Cambodia", - "CM" => "Cameroon", - "CA" => "Canada", - "CV" => "Cape Verde", - "KY" => "Cayman Islands", - "CF" => "Central African Republic", - "TD" => "Chad", - "CL" => "Chile", - "CN" => "China", - "CX" => "Christmas Island", - "CC" => "Cocos (Keeling) Islands", - "CO" => "Colombia", - "KM" => "Comoros", - "CG" => "Congo", - "CD" => "Congo, The Democratic Republic of", - "CK" => "Cook Islands", - "CR" => "Costa Rica", - "CI" => "Côte D'Ivoire", - "HR" => "Croatia", - "CU" => "Cuba", - "CY" => "Cyprus", - "CZ" => "Czech Republic", - "DK" => "Denmark", - "DJ" => "Djibouti", - "DM" => "Dominica", - "DO" => "Dominican Republic", - "EC" => "Ecuador", - "EG" => "Egypt", - "SV" => "El Salvador", - "GQ" => "Equatorial Guinea", - "ER" => "Eritrea", - "EE" => "Estonia", - "ET" => "Ethiopia", - "FK" => "Falkland Islands (Malvinas)", - "FO" => "Faroe Islands", - "FJ" => "Fiji", - "FI" => "Finland", - "FR" => "France", - "GF" => "French Guiana", - "PF" => "French Polynesia", - "TF" => "French Southern Territories", - "GA" => "Gabon", - "GM" => "Gambia", - "GE" => "Georgia", - "DE" => "Germany", - "GH" => "Ghana", - "GI" => "Gibraltar", - "GR" => "Greece", - "GL" => "Greenland", - "GD" => "Grenada", - "GP" => "Guadeloupe", - "GU" => "Guam", - "GT" => "Guatemala", - "GG" => "Guernsey", - "GN" => "Guinea", - "GW" => "Guinea-Bissau", - "GY" => "Guyana", - "HT" => "Haiti", - "HM" => "Heard Island and Mcdonald Islands", - "VA" => "Holy See", - "HN" => "Honduras", - "HK" => "Hong Kong", - "HU" => "Hungary", - "IS" => "Iceland", - "IN" => "India", - "ID" => "Indonesia", - "IR" => "Iran", - "IQ" => "Iraq", - "IE" => "Ireland", - "IM" => "Isle of Man", - "IL" => "Israel", - "IT" => "Italy", - "JM" => "Jamaica", - "JP" => "Japan", - "JE" => "Jersey", - "JO" => "Jordan", - "KZ" => "Kazakhstan", - "KE" => "Kenya", - "KI" => "Kiribati", - "KP" => "Korea, Democratic People's Rep of", - "KR" => "Korea, Republic of", - "KW" => "Kuwait", - "KG" => "Kyrgyzstan", - "LA" => "Lao People's Democratic Republic", - "LV" => "Latvia", - "LB" => "Lebanon", - "LS" => "Lesotho", - "LR" => "Liberia", - "LY" => "Libyan Arab Jamahiriya", - "LI" => "Liechtenstein", - "LT" => "Lithuania", - "LU" => "Luxembourg", - "MO" => "Macao", - "MK" => "Macedonia", - "MG" => "Madagascar", - "MW" => "Malawi", - "MY" => "Malaysia", - "MV" => "Maldives", - "ML" => "Mali", - "MT" => "Malta", - "MH" => "Marshall Islands", - "MQ" => "Martinique", - "MR" => "Mauritania", - "MU" => "Mauritius", - "YT" => "Mayotte", - "MX" => "Mexico", - "FM" => "Micronesia, Federated States of", - "MD" => "Moldova, Republic of", - "MC" => "Monaco", - "MN" => "Mongolia", - "ME" => "Montenegro", - "MS" => "Montserrat", - "MA" => "Morocco", - "MZ" => "Mozambique", - "MM" => "Myanmar", - "NA" => "Namibia", - "NR" => "Nauru", - "NP" => "Nepal", - "NL" => "Netherlands", - "AN" => "Netherlands Antilles", - "NC" => "New Caledonia", - "NZ" => "New Zealand", - "NI" => "Nicaragua", - "NE" => "Niger", - "NG" => "Nigeria", - "NU" => "Niue", - "NF" => "Norfolk Island", - "MP" => "Northern Mariana Islands", - "NO" => "Norway", - "OM" => "Oman", - "PK" => "Pakistan", - "PW" => "Palau", - "PS" => "Palestinian Territory, Occupied", - "PA" => "Panama", - "PG" => "Papua New Guinea", - "PY" => "Paraguay", - "PE" => "Peru", - "PH" => "Philippines", - "PN" => "Pitcairn", - "PL" => "Poland", - "PT" => "Portugal", - "PR" => "Puerto Rico", - "QA" => "Qatar", - "RE" => "Réunion", - "RO" => "Romania", - "RU" => "Russian Federation", - "RW" => "Rwanda", - "BL" => "Saint Barthélemy", - "SH" => "Saint Helena", - "KN" => "Saint Kitts and Nevis", - "LC" => "Saint Lucia", - "MF" => "Saint Martin", - "PM" => "Saint Pierre and Miquelon", - "VC" => "Saint Vincent and The Grenadines", - "WS" => "Samoa", - "SM" => "San Marino", - "ST" => "Sao Tome and Principe", - "SA" => "Saudi Arabia", - "SN" => "Senegal", - "RS" => "Serbia", - "SC" => "Seychelles", - "SL" => "Sierra Leone", - "SG" => "Singapore", - "SK" => "Slovakia", - "SI" => "Slovenia", - "SB" => "Solomon Islands", - "SO" => "Somalia", - "ZA" => "South Africa", - "GS" => "South Georgia (SGSSI)", - "ES" => "Spain", - "LK" => "Sri Lanka", - "SD" => "Sudan", - "SR" => "Suriname", - "SJ" => "Svalbard and Jan Mayen", - "SZ" => "Swaziland", - "SE" => "Sweden", - "CH" => "Switzerland", - "SY" => "Syrian Arab Republic", - "TW" => "Taiwan", - "TJ" => "Tajikistan", - "TZ" => "Tanzania", - "TH" => "Thailand", - "TL" => "Timor-Leste", - "TG" => "Togo", - "TK" => "Tokelau", - "TO" => "Tonga", - "TT" => "Trinidad and Tobago", - "TN" => "Tunisia", - "TR" => "Turkey", - "TM" => "Turkmenistan", - "TC" => "Turks and Caicos Islands", - "TV" => "Tuvalu", - "UG" => "Uganda", - "UA" => "Ukraine", - "AE" => "United Arab Emirates", - "GB" => "United Kingdom", - "US" => "United States", - "UM" => "U.S. Minor Outlying Islands", - "UY" => "Uruguay", - "UZ" => "Uzbekistan", - "VU" => "Vanuatu", - "VA" => "Vatican City State", - "VE" => "Venezuela", - "VN" => "Viet Nam", - "VG" => "Virgin Islands, British", - "VI" => "Virgin Islands, U.S.", - "WF" => "Wallis and Futuna", - "EH" => "Western Sahara", - "YE" => "Yemen", - "ZM" => "Zambia", - "ZW" => "Zimbabwe" - ); - - function __construct() { } - - /* - Useage Example: - - Countries::display( create_function('$abbrv,$name', 'return "";') ); - - */ - public static function display($callback, $subset = false) { - // $callback is the anonymous function for formating the data - // $subset should be an array of ok abbreviations, set to false for all countries - // returns true on success - - if (!is_callable($callback)) throw new Exception('Callback function is not callable.'); - if ($subset && !is_array($subset)) throw new Exception('Subset must be false or an array of acceptable country abbreviations.'); - - foreach (self::$list as $abbrv => $name) { - - if ($subset && !in_array($abbrv, $subset)) continue; - - echo $callback($abbrv, $name); - - } - - return true; - } - - public static function check($countryAbbrv) { - // Check if a country abbrevation is valid - - if (isset(self::$list[$countryAbbrv])) return true; - return false; - - } - - public static function getName($countryAbbrv, $html = true) { - // Get a country's name from its abbrevation - - if (!self::check($countryAbbrv)) - return false; - - $ret = self::$list[$countryAbbrv]; - - if ($html) { - - $find = array( - 'Ã…', - 'ô', - 'é' - ); - - $replace = array( - 'Å', - 'ô', - 'é' - ); - - $ret = str_replace($find, $replace, $ret); - } - - return $ret; - - } - -} \ No newline at end of file diff --git a/lib/Firelit/Crypto.php b/lib/Firelit/Crypto.php deleted file mode 100644 index 4fe8688..0000000 --- a/lib/Firelit/Crypto.php +++ /dev/null @@ -1,120 +0,0 @@ -= 0) return false; - $comp = version_compare($targetVersion, static::$version); - return ($comp >= 0); - } - - static public function checkVersionDown($currentVersion, $targetVersion) { - $comp = version_compare($currentVersion, static::$version); - if ($comp < 0) return false; - $comp = version_compare($targetVersion, static::$version); - return ($comp == -1); - } - -} \ No newline at end of file diff --git a/lib/Firelit/DatabaseMigrationManager.php b/lib/Firelit/DatabaseMigrationManager.php deleted file mode 100644 index baf6106..0000000 --- a/lib/Firelit/DatabaseMigrationManager.php +++ /dev/null @@ -1,130 +0,0 @@ -current = $currentVersion; - $this->target = $targetVersion; - $this->direction = $direction; - - } - - /** - * submitMigration() - * @param string $className The name of the migration class to be checked for inclusion and execution - */ - public function submitMigration($className) { - - if ($this->direction == 'up') { - if ($className::checkVersionUp($this->current, $this->target)) - $this->addMigration(new $className); - } - - elseif ($this->direction == 'down') { - if ($className::checkVersionDown($this->current, $this->target)) - $this->addMigration(new $className); - } - - } - - /** - * count() - * @return int Number of migrations included for execution - */ - public function count() { - return sizeof($this->migrations); - } - - /** - * addMigration() - * @param DatabaseMigration $mig Add a migration for execution - */ - public function addMigration(DatabaseMigration $mig) { - - $this->migrations[] = $mig; - - } - - /** - * sortMigrations() - */ - public function sortMigrations() { - - $dmm = $this; - - usort($this->migrations, function($a, $b) use ($dmm) { - - $av = $a->getVersion(); - $bv = $b->getVersion(); - - if ($dmm->direction == 'up') - return version_compare($av, $bv); - elseif ($dmm->direction == 'down') - return version_compare($bv, $av); - - }); - - } - - /** - * setPreExecCallback() - * @param function $function Set a callback function to run before each individual migration - */ - public function setPreExecCallback($function) { - $this->preCallback = $function; - } - - /** - * setPostExecCallback() - * @param function $function Set a callback function to run after each individual migration - */ - public function setPostExecCallback($function) { - $this->postCallback = $function; - } - - /** - * executeMigrations() - */ - public function executeMigrations() { - - $count = $this->count(); - - foreach ($this->migrations as $i => $mig) { - - if ($this->preCallback) { - $callback = $this->preCallback; - $callback($mig->getVersion(), $i, $count); - } - - if ($this->direction == 'up') - $mig->up(); - elseif ($this->direction == 'down') - $mig->down(); - - if ($this->postCallback) { - $callback = $this->postCallback; - $callback($mig->getVersion(), $i, $count); - } - - } - - } -} \ No newline at end of file diff --git a/lib/Firelit/DatabaseObject.php b/lib/Firelit/DatabaseObject.php deleted file mode 100644 index 7de289c..0000000 --- a/lib/Firelit/DatabaseObject.php +++ /dev/null @@ -1,293 +0,0 @@ -constructed = true; - - // If it doesn't come pre-loaded, it's new - // (Pre-loading made possible by PDOStatement::fetchObject) - if (!sizeof($this->_data)) $this->_new = true; - - if (is_array(static::$colsDateTime)) { - - if (!static::$defaultTz) - static::$defaultTz = new \DateTimeZone('UTC'); - - foreach (static::$colsDateTime as $aCol) { - // Change all pre-loaded date/time data into DateTime object - if (!empty($this->_data[$aCol]) && !is_object($this->_data[$aCol])) - $this->_data[$aCol] = new \DateTime($this->_data[$aCol], static::$defaultTz); - } - } - - $this->_dirty = array(); // Reset in case pre-loaded - - if (is_array($data)) - foreach ($data as $name => $value) - $this->__set($name, $value); - - } - - public static function setQueryObject(Query $query) { - // Used for testing - static::$query = $query; - } - - public function save() { - - if (!$this->_new && !sizeof($this->_dirty)) return; - if ($this->_readOnly) throw new \Exception('Cannot save a read-only object.'); - - $saveData = $this->_data; - - foreach ($saveData as $var => $val) { - - // JIT encoding: Serialize/JSON-encode just before saving - if (!is_null($val) && in_array($var, static::$colsSerialize)) { - $saveData[$var] = serialize($val); - } elseif (!is_null($val) && in_array($var, static::$colsJson)) { - $saveData[$var] = json_encode($val); - } - - } - - if (static::$query) $q = static::$query; - else $q = new Query(); - - if ($this->_new) { - - $q->insert(static::$tableName, $saveData); - - // If new and single primary key, set data id from autoincrement id - if (static::$primaryKey && !is_array(static::$primaryKey)) - $this->_data[static::$primaryKey] = $q->getNewId(); - - } else { - - if (!static::$primaryKey) - throw new \Exception('Cannot perform update without a primary key.'); - - if (is_array(static::$primaryKey)) - foreach (static::$primaryKey as $aKey) { - if (!isset($saveData[$aKey])) - throw new \Exception('Cannot perform update without all primary keys set.'); - } - - elseif (!isset($saveData[static::$primaryKey])) - throw new \Exception('Cannot perform update without primary key set.'); - - $updateData = array(); - - foreach ($this->_dirty as $key) { - $updateData[$key] = $saveData[$key]; - } - - if (is_array(static::$primaryKey)) { - foreach (static::$primaryKey as $aKey) { - if (isset($updateData[$aKey])) - throw new \Exception('Cannot perform update on primary key (it was marked dirty).'); - } - } elseif (isset($updateData[static::$primaryKey])) { - throw new \Exception('Cannot perform update on primary key (it was marked dirty).'); - } - - list($whereSql, $whereBinder) = $this->getWhere($saveData); - - $q->update(static::$tableName, $updateData, $whereSql, $whereBinder); - - } - - $this->_new = false; - $this->_dirty = array(); - - } - - static protected function getWhere($valueArray) { - - $whereBinder = array(); - - if (is_array(static::$primaryKey)) { - - $whereSql = "WHERE"; - - foreach (static::$primaryKey as $aKey) { - $binderName = ':primary_key_'.mt_rand(0,1000000); - $whereSql .= " `". $aKey ."`=". $binderName ." AND"; - $whereBinder[$binderName] = $valueArray[$aKey]; - } - - $whereSql = substr($whereSql, 0, -3). "LIMIT 1"; - - } else { - - $binderName = ':primary_key_'.mt_rand(0,1000000); - $whereSql = "WHERE `". static::$primaryKey ."`=". $binderName ." LIMIT 1"; - $whereBinder[$binderName] = $valueArray[static::$primaryKey]; - - } - - return array($whereSql, $whereBinder); - - } - - public function setNew() { - $this->_new = true; - $this->_dirty = array(); - } - - public function setNotNew() { - $this->_new = false; - } - - public function setReadOnly() { - $this->_new = false; - $this->_dirty = array(); - $this->_readOnly = true; - } - - public function isNew() { - return $this->_new; - } - - public function getDirty() { - return $this->_dirty; - } - - public function __get($var) { - - if (isset($this->_data[$var])) - return $this->_data[$var]; - - return null; - - } - - public function __isset($var) { - - return isset($this->_data[$var]); - - } - - public function __set($var, $val) { - - // If pre-construct loading - if (!$this->constructed) { - - // Take out of deep-freeze (as stored in DB) - if (!is_null($val) && in_array($var, static::$colsSerialize)) { - $val = unserialize($val); - } elseif (!is_null($val) && in_array($var, static::$colsJson)) { - $val = json_decode($val, true); - } - - $this->_data[$var] = $val; - return; - } - - if (isset($this->_data[$var]) && ($this->_data[$var] === $val)) return; - - $this->_data[$var] = $val; - - if (!$this->_new && !in_array($var, $this->_dirty)) - $this->_dirty[] = $var; - - } - - public function __clone() { - - if (static::$primaryKey) - unset($this->_data[static::$primaryKey]); - - $this->_new = true; - $this->_dirty = array(); - - } - - public function delete() { - - if ($this->_readOnly) throw new \Exception('Cannot delete a read-only object.'); - - if (!static::$primaryKey) - throw new \Exception('Cannot perform delete without a primary key.'); - - $this->_dirty = array(); - - if ($this->_new) return; - - list($whereSql, $whereBinder) = $this->getWhere($this->_data); - - $sql = "DELETE FROM `". static::$tableName ."` ". $whereSql; - - if (static::$query) $q = static::$query; - else $q = new Query(); - - $q->query($sql, $whereBinder); - - $this->_data = array(); - - } - - public static function create($data) { - - $class = get_called_class(); - - $do = new $class($data); - $do->save(); - - return $do; - - } - - public static function find($searchValue) { - - if (!static::$primaryKey) - throw new \Exception('Cannot perform find without a primary key.'); - - if (is_array($searchValue) != is_array(static::$primaryKey)) - throw new \Exception('If primary key is an array, must search by array and vice versa.'); - - if (is_array($searchValue) && (sizeof($searchValue) != sizeof(static::$primaryKey))) - throw new \Exception('Number of elements in search array must match primary key array.'); - - if (!is_array($searchValue)) - $searchValue = array(static::$primaryKey => $searchValue); - - list($whereSql, $whereBinder) = static::getWhere($searchValue); - - $sql = "SELECT * FROM `". static::$tableName ."` ". $whereSql; - - if (static::$query) $q = static::$query; - else $q = new Query(); - - $q->query($sql, $whereBinder); - - return $q->getObject(get_called_class()); - - } - -} \ No newline at end of file diff --git a/lib/Firelit/DatabaseSessionHandler.php b/lib/Firelit/DatabaseSessionHandler.php deleted file mode 100644 index 8bf5431..0000000 --- a/lib/Firelit/DatabaseSessionHandler.php +++ /dev/null @@ -1,74 +0,0 @@ - 'Sessions', // Table where it is all stored - 'colKey' => 'key', // The key column for the unique session ID - 'colData' => 'data', // The data column for storing session data - 'colExp' => 'expires', // The datetime column for expiration date - 'expSeconds' => 14400 // 4 hours - ); - - public function open($savePath, $sessionName) { - return true; - } - - public function close() { - return true; - } - - public function read($id) { - - $sql = "SELECT `". self::$config['colData'] ."` FROM `". self::$config['tableName'] ."` WHERE `". self::$config['colKey'] ."`=:session_id AND `". self::$config['colExp'] ."` > NOW() LIMIT 1"; - - $q = new Query($sql, array( - ':session_id' => $id - )); - - if (!$data = $q->getRow()) return false; - return $data[ self::$config['colData'] ]; - - } - - public function write($id, $data) { - - $q = new Query(); - - $q->replace(self::$config['tableName'], array( - self::$config['colKey'] => $id, - self::$config['colData'] => $data, - self::$config['colExp'] => Query::SQL('DATE_ADD(NOW(), INTERVAL '. self::$config['expSeconds'] .' SECOND)') - )); - - return true; - - } - - public function destroy($id) { - - $sql = "DELETE FROM `". self::$config['tableName'] ."` WHERE `". self::$config['colKey'] ."`=:session_id LIMIT 1"; - - new Query($sql, array( - ':session_id' => $id - )); - - return true; - - } - - public function gc($maxlifetime) { - // $maxlifetime not implemented. - - $sql = "DELETE FROM `". self::$config['tableName'] ."` WHERE `". self::$config['colExp'] ."` <= NOW()"; - - new Query($sql, array( - ':session_id' => $id - )); - - return true; - - } -} \ No newline at end of file diff --git a/lib/Firelit/HttpRequest.php b/lib/Firelit/HttpRequest.php deleted file mode 100644 index b53280d..0000000 --- a/lib/Firelit/HttpRequest.php +++ /dev/null @@ -1,187 +0,0 @@ - array( - 'connect' => 3, - 'response' => 10 - ), - 'userAgent' => false, - 'caInfo' => false, - 'followRedirect' => false - ); - - public function __construct() { - - if (function_exists('curl_init')) $this->handle = curl_init(); - else throw new \Exception('cURL required.'); - - if (!$this->handle) throw new \Exception('Could not initiate cURL.'); - - } - - public static function config($config) { - - self::$config = array_merge(self::$config, $config); - - } - - public function enableCookies($file = false, $delOnDestruct = true) { - - $this->cookies = true; - - if ($file && strlen($file)) - $this->cookieFile = $file; - else - $this->cookieFile = tempnam(".", "CURL-COOKIE-"); - - $this->delCookieFile = $delOnDestruct; - - } - - public function close() { - - if (!$this->handle) return; - - curl_close($this->handle); - - $this->handle = false; - - } - - public function clearCookies() { - - if ($this->cookies && $this->delCookieFile && file_exists($this->cookieFile)) - unlink($this->cookieFile); - - } - - public function customHeaders($headerArray) { - - curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headerArray); - - } - - public function setBasicAuth($user, $pass = null) { - - curl_setopt($this->handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); - curl_setopt($this->handle, CURLOPT_USERPWD, (!empty($pass) ? $user .':'. $pass : $user)); // Should be 'username:password' - - } - - // Three executing methods: - - public function get($url) { - - curl_setopt($this->handle, CURLOPT_POST, 0); // Added to clear past values - curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, 'GET'); // Added to clear past values - - return $this->execute($url); - - } - - public function post($url, $postData) { - - if (is_array($postData)) $postData = http_build_query($postData); - - curl_setopt($this->handle, CURLOPT_POST, 1); // Perform a POST - curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, 'POST'); // Added to clear past values - curl_setopt($this->handle, CURLOPT_POSTFIELDS, $postData); - - return $this->execute($url); - - } - - public function put($url, $putData) { - - if (is_array($putData)) $putData = http_build_query($putData); - - curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, 'PUT'); - curl_setopt($this->handle, CURLOPT_POSTFIELDS, $putData); - - return $this->execute($url); - - } - - public function delete($url) { - - curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, 'DELETE'); - - return $this->execute($url); - - } - - public function other($url, $type) { - // For example, HEAD request - curl_setopt($this->handle, CURLOPT_POST, 0); // Added to clear past values - curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $type); - - return $this->execute($url); - - } - - private function execute($url) { - - curl_setopt($this->handle, CURLOPT_URL, $url); // Set the URL - - if ($this->userAgent) curl_setopt($this->handle, CURLOPT_USERAGENT, $this->userAgent); - elseif (self::$config['userAgent']) curl_setopt($this->handle, CURLOPT_USERAGENT, self::$config['userAgent']); - - if (self::$config['caInfo']) curl_setopt($this->handle, CURLOPT_CAINFO, self::$config['caInfo']); // Name of the file to verify the server's cert against - if (self::$config['caInfo']) curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 1); // Verify the SSL certificate - if (self::$config['followRedirect']) curl_setopt($this->handle, CURLOPT_FOLLOWLOCATION, 1); // Follow redirects - - curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1); // If not set, curl prints output to the browser - curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, self::$config['timeout']['connect']); // How long to wait for a connection - curl_setopt($this->handle, CURLOPT_TIMEOUT, self::$config['timeout']['response']); // How long to wait for a response - - if ($this->cookies) { - curl_setopt($this->handle, CURLOPT_COOKIEJAR, $this->cookieFile); - curl_setopt($this->handle, CURLOPT_COOKIEFILE, $this->cookieFile); - } - - $dataBack = curl_exec($this->handle); - - $this->respInfo = curl_getinfo($this->handle); - $this->respCode = curl_getinfo($this->handle, CURLINFO_HTTP_CODE); - - return $dataBack; - - } - - public function respCode() { - - return $this->respCode; - - } - - public function respInfo() { - - return $this->respInfo; - - } - - public function __destruct() { - - $this->close(); - $this->clearCookies(); - - } - -} diff --git a/lib/Firelit/InitExtendable.php b/lib/Firelit/InitExtendable.php deleted file mode 100644 index 897eb76..0000000 --- a/lib/Firelit/InitExtendable.php +++ /dev/null @@ -1,18 +0,0 @@ -newInstanceArgs($args); - - } - -} diff --git a/lib/Firelit/InputValidator.php b/lib/Firelit/InputValidator.php deleted file mode 100644 index 84ff693..0000000 --- a/lib/Firelit/InputValidator.php +++ /dev/null @@ -1,451 +0,0 @@ -type = $type; - $this->value = filter_var(trim($value), FILTER_UNSAFE_RAW, array( - 'flags' => FILTER_FLAG_STRIP_LOW - )); - $this->region = $region; - $this->required = true; - } - - public function setRequired($required = true) { - $this->required = $required; - } - - public function isValid() { - return self::validate($this->type, $this->value, $this->region, $this->required); - } - - public function getNormalized($returnType = self::TYPE_DEFAULT) { - - switch ($this->type) { - case self::NAME: - case self::ORG_NAME: - case self::CITY: - - $name = $this->value; - - if (preg_match("/\b(Van|De|Di)[A-Z][a-z]+/", $name)) $compName = true; else $compName = false; // Will be all lower case, next - $name = mb_convert_case($name, MB_CASE_TITLE, 'UTF-8'); - - if (strlen($name) == 0) return ''; - - $name = str_replace(array('`','—','–',' ','--','And '), array("'",'-','-',' ','-','and '), $name); - - $name = preg_replace_callback("/([A-Za-z]+[\-'])([A-Za-z]{2})/", function($matches) { - return $matches[1] . mb_convert_case($matches[2], MB_CASE_TITLE, 'UTF-8'); - }, $name); - - $name = preg_replace_callback("/([a-z])\s*[\+&]\s*([a-z])/i", function($matches) { - return $matches[1] .' & '. mb_strtoupper($matches[2]); - }, $name); - - $name = preg_replace_callback("/(Mc)([a-z]+)/", function($matches) { - return $matches[1] . mb_convert_case($matches[2], MB_CASE_TITLE, 'UTF-8'); - }, $name); - - $name = preg_replace_callback("/(\b)(Ii|Iii|Iv)(\b)/", function($matches) { - return $matches[1] . mb_strtoupper($matches[2]) . $matches[3]; - }, $name); - - $name = preg_replace_callback("/(\b)(Llc)(\b)/", function($matches) { - return $matches[1] . mb_strtoupper($matches[2]) . $matches[3]; - }, $name); - - $name = preg_replace_callback("/(\b)(j|s)\.?r\.?$/i", function($matches) { - return $matches[1] . mb_strtoupper($matches[2]) . 'r'; - }, $name); - - if ($compName) { - $name = preg_replace_callback("/\b(Van|De|Di)([a-z]+)/", function($matches) { - return $matches[1] . mb_convert_case($matches[2], MB_CASE_TITLE, 'UTF-8'); - }, $name); - } - - return $name; - - case self::ADDRESS: - - $address = mb_strtolower($this->value); - if (strlen($address) == 0) return ''; - - $address = str_replace(array('`','—','–',' ','--'), array("'",'-','-',' ','-'), $address); - $address = preg_replace_callback("/([a-z]+[\-'])([a-z]{2})/", function($matches) { - return $matches[1] . mb_convert_case($matches[2], MB_CASE_TITLE, 'UTF-8'); - }, $address); - $address = preg_replace_callback("/([0-9#])([a-z])(\b|[0-9])/", function($matches) { - return $matches[1] . mb_strtoupper($matches[2]) . $matches[3]; - }, $address); - - $patterns = array('/p\.o\.(\s?)/i', '/^po\s/i', '/^po\.(\s?)/i'); - $replacew = array('PO ', 'PO ', 'PO '); - $address = preg_replace($patterns, $replacew, $address); - - $patterns = array( '/\bn(\.?\s?)e(\.?)\s/i', '/\bn(\.?\s?)e(\.?)$/i', - '/\bn(\.?\s?)w(\.?)\s/i', '/\bn(\.?\s?)w(\.?)$/i', - '/\bs(\.?\s?)e(\.?)\s/i', '/\bs(\.?\s?)e(\.?)$/i', - '/\bs(\.?\s?)w(\.?)\s/i', '/\bs(\.?\s?)w(\.?)$/i', - '/\br(\.?\s?)r(\.?)\s/i'); - $replacew = array('NE ', 'NE', 'NW ', 'NW', 'SE ', 'SE', 'SW ', 'SW', 'RR '); - $address = self::mb_ucwords(preg_replace($patterns, $replacew, $address)); - - return $address; - - case self::STATE: - - $state = $this->value; - - if (in_array($this->region, array('US', 'CA', 'MX'))) - return substr(mb_strtoupper(trim($state)), 0, 2); - - if (strlen($state) <= 3) - return mb_strtoupper($state); - - return self::mb_ucwords($state); - - case self::ZIP: - - $zip = mb_strtoupper($this->value); - if ($this->region == 'CA') - $zip = substr($zip, 0, 3) .' '. substr($zip, -3); - - return $zip; - - case self::COUNTRY: - - return substr(mb_strtoupper($this->value), 0, 2); - - case self::PHONE: - - $phone = preg_replace('/\s{2,}/', ' ', trim($this->value)); - - if (strlen($phone) < 5) return $phone; - - if (in_array($this->region, array('US', 'CA'))) { - - $phone = preg_replace('/[^0-9]+/', '', $phone); - - if ((strlen($phone) > 10) && (substr($phone, 0, 1) == '1')) - $phone = substr($phone, 1); - - $phone = "(". substr($phone, 0, 3) .") ". substr($phone, 3, 3) ."-". substr($phone, 6, 4) . trim( ' '. substr($phone, 10) ); - - } else { - - $phone = preg_replace('/[^0-9\-\+\(\) ]+/', '', $phone); - - } - - return $phone; - - case self::EMAIL: - - return mb_strtolower($this->value); - - case self::CREDIT_ACCT: - - $num = preg_replace('/\D+/', '', $this->value); - - if ($returnType == self::TYPE_GATEWAY) - return $num; - - return self::lastfour($num, 'x'); - - case self::CREDIT_EXP: - - $exp = $this->value; - - if (preg_match('/^\d{2}[\/-]?\d{2}$/', $exp)) { - $mo = substr($exp, 0, 2); - $yr = substr($exp, -2); - $exp = strtotime($mo.'/01/20'.$yr); - } elseif (preg_match('/^\d{1}[\/-]?\d{2}$/', $exp)) { - $mo = '0'.substr($exp, 0, 1); - $yr = substr($exp, -2); - $exp = strtotime($mo.'/01/20'.$yr); - } else { - // Shot in the dark - $exp = strtotime($exp); - } - - if ($returnType == self::TYPE_GATEWAY) - return date('my', $exp); - - if ($returnType == self::TYPE_DB) - return '20'. $yr .'-'. $mo .'-'. date('t', $exp); - - return date('m/y', $exp); - - case self::CREDIT_CVV: - - $num = preg_replace('/\D+/', '', $this->value); - - if ($returnType == self::TYPE_GATEWAY) - return $num; - - return str_pad('', strlen($num), 'x'); - - case self::ACH_ROUT: - - $num = preg_replace('/\D+/', '', $this->value); - - return $num; - - case self::ACH_ACCT: - - $num = preg_replace('/\D+/', '', $this->value); - - if ($returnType == self::TYPE_GATEWAY) - return $num; - - return self::lastfour($num, 'x'); - - case self::ACH_TYPE: - - $type = mb_strtoupper(substr($this->value, 0, 1)); - - if (in_array($returnType, array(self::TYPE_DB, self::TYPE_GATEWAY))) - return $type; - - if ($type == 'C') return 'Checking'; - elseif ($type == 'S') return 'Savings'; - else return ''; - - case self::URL: - - $url = parse_url($this->value); - if (empty($url['scheme'])) - $url = parse_url('http://'. $this->value); // Add a scheme for proper parsing - - if (empty($url['path'])) $url['path'] = '/'; - - return $url['scheme'] .'://'. strtolower($url['host']) . (!empty($url['port']) ? ':'. $url['port'] : '') . $url['path'] . (!empty($url['query']) ? '?'. $url['query'] : ''); - - default: - return false; - } - - } - - static public function validate($type, $value, $region = false, $required = true) { - if (empty($value)) return !$required; - - switch ($type) { - case self::NAME: - case self::CITY: - - if (preg_match('/^\d/', $value)) return false; - - return preg_match('/^\p{L}[\p{L} \-\'\+&\.]*[\p{L}\.]$/u', $value); - - case self::ORG_NAME: - - return preg_match('/^[\p{L}\d].*[\p{L}\d\.\)\]\!\?]$/u', $value); - - case self::ADDRESS: - - // Must contain at least one digit and 2 letters - return preg_match('/\d+/', $value) && preg_match('/[A-Za-z]{2,}/', $value); - - case self::STATE: - - if (in_array($region, array('US', 'CA', 'MX'))) { - - return ( States::check($region, mb_strtoupper($value)) !== false ); - - } else { - - return preg_match('/^(.+)$/', $value); - } - - case self::ZIP: - - if ($region == 'US') { - - return preg_match('/^\d{5}(\-\d{4})?$/', $value); - - } elseif ($region == 'CA') { - - return preg_match('/^\D\d\D(\s)?\d\D\d$/', $value); - - } elseif ($region == 'MX') { - - return preg_match('/^\d{5}$/', $value); - - } else { - - return preg_match('/^(.+)$/', $value); - - } - - case self::COUNTRY: - - return Countries::check($value); - - case self::PHONE: - - $temp = preg_replace('/\D+/', '', $value); - - if (in_array($region, array('US', 'CA'))) { - - return preg_match('/^(1)?[2-9]\d{9}$/', $temp); - - } else { - - return preg_match('/^\d{4,}$/', $temp); - - } - - case self::EMAIL: - - return (filter_var($value, FILTER_VALIDATE_EMAIL) !== false); - - case self::CREDIT_ACCT: - - $value = preg_replace('/\s+/', '', $value); - - if (!preg_match('/^\d{15,16}$/', $value)) return false; - if (!preg_match('/^[3-6]/', $value)) return false; - - return self::checkLuhn($value); - - case self::CREDIT_EXP: - - $value = preg_replace_callback('/^(\d{1,2})[\/\-]?(\d{2})$/', function($matches) { - if (strlen($matches[1]) == 1) $matches[1] = '0'.$matches[1]; - return $matches[1] . $matches[2]; - }, $value); - - $mo = intval(substr($value, 0, 2)); - $yr = intval(substr($value, -2)); - - if (($mo < 1) || ($mo > 12)) return false; - if (($yr < intval(date('y'))) || ($yr > (intval(date('y')) + 10))) return false; - - return preg_match('/^\d{4}$/', $value); - - case self::CREDIT_CVV: - - return preg_match('/^\d{3,4}$/', $value); - - case self::ACH_ROUT: - - if (!preg_match('/^\d{9}$/', $value)) return false; - - return self::checkChecksum($value); - - case self::ACH_ACCT: - - $value = preg_replace('/[\s\-]+/', '', $value); - - return preg_match('/^\d{4,25}$/', $value); - - case self::ACH_TYPE: - - return preg_match('/^(C|S)$/i', substr($value, 0, 1)); - - case self::URL: - - $url = parse_url($value); - - if (!$url) return false; - - if (empty($url['scheme'])) - $url = parse_url('http://'. $value); // Add a scheme for proper parsing - - if (strlen($url['scheme']) && !in_array($url['scheme'], array('http', 'https'))) return false; - if (!isset($url['host']) && isset($url['path'])) { $url['host'] = $url['path']; $url['path'] = ''; } - if (!preg_match('/^([a-z0-9\-]+\.)+([a-z]{2,})$/i', $url['host'])) return false; - - return true; - - default: - return false; - } - - } - - static public function lastfour($number, $padChar = 'x') { - - $len = strlen($number); - if ($len == 0) return ''; - - $lastLen = intval(floor($len / 2)); - if ($lastLen > 4) $lastLen = 4; - $lastFour = substr($number, -$lastLen, $lastLen); - $lastFour = str_pad($lastFour, $len, $padChar, STR_PAD_LEFT); - - return $lastFour; - - } - - static public function checkChecksum($number) { - - settype($number, 'string'); - - $sum = 3 * ( intval(substr($number, 0, 1)) + intval(substr($number, 3, 1)) + intval(substr($number, 6, 1)) ); - $sum += 7 * ( intval(substr($number, 1, 1)) + intval(substr($number, 4, 1)) + intval(substr($number, 7, 1)) ); - $sum += intval(substr($number, 2, 1)) + intval(substr($number, 5, 1)) + intval(substr($number, 8, 1)); - - return (($sum % 10) === 0); - - } - - static public function checkLuhn($number) { - - settype($number, 'string'); - - $sumTable = array( - array(0,1,2,3,4,5,6,7,8,9), - array(0,2,4,6,8,1,3,5,7,9) - ); - - $sum = 0; - $flip = 0; - - for ($i = strlen($number) - 1; $i >= 0; $i--) { - $sum += $sumTable[ $flip++ & 0x1 ][ $number[$i] ]; - } - - return (($sum % 10) === 0); - - } - - static public function mb_ucwords($str) { - // mb_convert_case($str, MB_CASE_TITLE, 'UTF-8') is doing a lower() first, not like ucwords() - return preg_replace_callback('/\b(\s?)(.)(\S*)\b/u', function($matches) { - return $matches[1] . mb_strtoupper($matches[2]) . $matches[3]; - }, $str); - - } -} \ No newline at end of file diff --git a/lib/Firelit/InvalidJsonException.php b/lib/Firelit/InvalidJsonException.php deleted file mode 100644 index 8a0bb4b..0000000 --- a/lib/Firelit/InvalidJsonException.php +++ /dev/null @@ -1,5 +0,0 @@ -query($sql, $binders); - - } - - public static function connect() { - - $reg = Registry::get('database'); - - try { - - if ($reg) { - if ($reg['type'] == 'mysql') { - - if (!isset($reg['port'])) $reg['port'] = 3306; - - self::$pdo = new \PDO('mysql:host='. $reg['host'] .';port='. $reg['port'] .';dbname='. $reg['name'], $reg['user'], $reg['pass']); - - } else { - - self::$pdo = new \PDO($reg['dsn']); - - } - } else { - - if (!isset($_SERVER['DB_PORT'])) $_SERVER['DB_PORT'] = 3306; - - self::$pdo = new \PDO('mysql:host='. $_SERVER['DB_HOST'] .';port='. $_SERVER['DB_PORT'] .';dbname='. $_SERVER['DB_NAME'], $_SERVER['DB_USER'], $_SERVER['DB_PASS']); - - } - - } catch (\Exception $e) { - self::$errorCount++; - throw $e; - } - - if (!self::$pdo) { - self::$errorCount++; - throw new \Exception('Could not connect to database.'); - } - - return self::$pdo; - - } - - public function query($sql, $binders = array()) { - - if (self::$errorCount > 10) { - trigger_error('QUERY ERROR LIMIT REACHED', E_USER_ERROR); - exit(1); - } - - if (is_string($sql)) - $this->cleanBinders($binders, $sql); - - $this->convertBinderValues($binders); - - if (!self::$pdo) static::connect(); - - // $sql can be a PDOStatement or a SQL string - if (is_string($sql)) { - $this->sql = self::$pdo->prepare($sql); - } elseif ($sql instanceof \PDOStatement) { - $this->sql = $sql; - } else { - self::$errorCount++; - throw new \Exception('Invalid parameter supplied to query method.'); - } - - foreach ($binders as $name => $value) { - // Fixes issue with innodb not interpreting false correctly (converts to empty string) - if (gettype($value) == 'boolean') $binders[$name] = intval($value); - } - - $this->res = $this->sql->execute($binders); - - if (!$this->res) { - self::$errorCount++; - throw new \Exception('Database error: '. $this->getErrorCode() .', '. $this->getError() .', '. $this->sql->queryString); - } - - return $this->res; - - } - - public function cleanBinders(&$binder, $sql) { - foreach ($binder as $name => $value) { - if (strpos($sql, $name) === false) - unset($binder[$name]); - } - } - - public function removeNulls(&$binder) { - // Make DB updates compatible with badly-designed code bases and DB schemas (where NULL is not valid) - foreach ($binder as $name => $value) { - if (is_null($value)) $binder[$name] = ''; - } - } - - public function convertBinderValues(&$binder) { - foreach ($binder as $name => $value) { - - // If value is a 2-element array with the first value - // having one of the following values, the second is assumed - // to need special attention. ("SQL" is handeled only in - // splitArray for insert/update) - if (is_array($value) && (sizeof($value) == 2)) { - switch (strtoupper($value[0])) { - case 'SERIALIZE': - $value = serialize($value[1]); - break; - case 'JSON': - $value = json_encode($value[1]); - break; - } - } - - if (is_object($value) && is_a($value, 'DateTime')) { - - $date = clone $value; - if (static::$defaultTz) - $date->setTimezone(static::$defaultTz); - - $value = $date->format('Y-m-d H:i:s'); - - } - - if (is_array($value) || is_object($value)) - $value = serialize($value); - - $binder[$name] = $value; - - } - } - - public function getRes() { - return $this->res; - } - - public function getAll() { - if (!$this->res) return false; - return $this->sql->fetchAll(\PDO::FETCH_ASSOC); - } - - public function getRow() { - if (!$this->res) return false; - return $this->sql->fetch(\PDO::FETCH_ASSOC); - } - - public function getObject($className) { - if (!$this->res) return false; - return $this->sql->fetchObject($className); - } - - public function getNewId() { - return self::$pdo->lastInsertId(); - } - - public function getAffected() { - return $this->sql->rowCount(); - } - - public function getNumRows() { - // May not always return the correct number of rows - // See note at http://php.net/manual/en/pdostatement.rowcount.php - return $this->sql->rowCount(); - } - - public function getError() { - $e = self::$pdo->errorInfo(); - if ($e[0] == '00000') $e = $this->sql->errorInfo(); - return $e[2]; // Driver specific error message. - } - - public function getErrorCode() { - $e = self::$pdo->errorInfo(); - if ($e[0] == '00000') $e = $this->sql->errorInfo(); - return $e[1]; // Driver specific error code. - } - - public function getQuery() { - return $this->sql->queryString; - } - - public function success() { - return $this->res; - } - - public function insert($table, $array) { - // Preform an insert on the table - // Enter an associative array for $array with column names as keys - - if (!self::$pdo) static::connect(); - - list($statementArray, $binderArray) = self::splitArray($array); - - $this->sql = self::$pdo->prepare("INSERT INTO `". $table ."` ". self::toSQL('INSERT', $statementArray)); - - return $this->query($this->sql, $binderArray); - - } - - public function replace($table, $array) { - // Preform an replace on the table - // Enter an associative array for $array with column names as keys - - if (!self::$pdo) static::connect(); - - list($statementArray, $binderArray) = self::splitArray($array); - - $this->sql = self::$pdo->prepare("REPLACE INTO `". $table ."` ". self::toSQL('REPLACE', $statementArray)); - - return $this->query($this->sql, $binderArray); - - } - - public function update($table, $array, $whereSql, $whereBinder = array()) { - // Preform an update on the table - // Enter an associative array for $array with column names as keys - - if (!self::$pdo) static::connect(); - - list($statementArray, $binderArray) = self::splitArray($array); - - // Look for binder conflicts - foreach ($whereBinder as $placeholder => $value) { - if (isset($binderArray[$placeholder])) { - // Binder conflict! - $newPlaceholder = $placeholder.'_'.mt_rand(100,10000); - $whereBinder[$newPlaceholder] = $value; - unset($whereBinder[$placeholder]); - - $whereSql = preg_replace('/'.preg_quote($placeholder).'\b/', $newPlaceholder, $whereSql); - } - - } - - $this->sql = self::$pdo->prepare("UPDATE `". $table ."` SET ". self::toSQL('UPDATE', $statementArray) ." WHERE ". preg_replace('/^\s?WHERE\s/', '', $whereSql)); - - return $this->query($this->sql, array_merge($binderArray, $whereBinder)); - - } - - public static function splitArray($arrayIn) { - - if (!is_array($arrayIn)) - throw new \Exception('Parameter is not an array.'); - - $statement = array(); - $binder = array(); - - foreach ($arrayIn as $key => $value) { - - // Check for anything that should be SQL and put in statement array - if (is_array($value) && (sizeof($value) == 2)) { - if (strtoupper($value[0]) == 'SQL') { - if (!is_string($value[1])) continue; - $statement[$key] = $value[1]; - continue; - } - } - - $crossKey = ':'.preg_replace('/[^A-Za-z0-9]+/', '_', $key); - - // Key is already used, add random characters to end - if (isset($binder[$crossKey])) $crossKey .= '_'. mt_rand(1000, 10000); - - $statement[$key] = $crossKey; - - $binder[$crossKey] = $value; - - } - - return array($statement, $binder); - - } - - public static function toSQL($verb, $assocArray) { - // $assocArray should be an array of 'raw' items (not yet escaped for database) - - $verb = strtoupper($verb); - - if (($verb == 'INSERT') || ($verb == 'REPLACE')) { - - $sql1 = ''; - $sql2 = ''; - - foreach ($assocArray as $key => $value) { - - $sql1 .= ', `'. str_replace('`', '', $key) .'`'; - $sql2 .= ", ". $value; - - } - - return '('. substr($sql1, 2) . ') VALUES ('. substr($sql2, 2) .')'; - - } elseif ($verb == 'UPDATE') { - - $sql = ''; - - foreach ($assocArray as $key => $value) { - - $sql .= ', `'. str_replace('`', '', $key) .'`='. $value; - - } - - return substr($sql, 2); - - } else throw new \Exception("Invalid verb for toSQL();"); - } - - public static function escapeLike($sql) { - return preg_replace('/([%_])/', '\\\\\\1', $sql); - } - - public static function SQL($sql) { - return array('SQL', $sql); - } -} diff --git a/lib/Firelit/QueryIterator.php b/lib/Firelit/QueryIterator.php deleted file mode 100644 index 2d5e3fb..0000000 --- a/lib/Firelit/QueryIterator.php +++ /dev/null @@ -1,81 +0,0 @@ -query = $query; - $this->className = $className; - - } - - /** - * Return the current row - * @return Array An associative array with the current row's value - */ - public function current() { - - return $this->value; - - } - - /** - * Return the index of the current row - * @return Int The index of the current row - */ - public function key() { - - return $this->index; - - } - - /** - * Move forward to next row - * @return Object $this For method chaining - */ - public function next() { - - $this->index++; - - if ($this->className) - $this->value = $this->query->getObject($this->className); - else - $this->value = $this->query->getRow(); - - return $this; - - } - - /** - * Rewind the Iterator to the first row (not possible with PDO query results!) - */ - public function rewind() { - - if ($this->index !== -1) - throw new \Exception('Cannot rewind PDOStatement results'); - - $this->next(); - - } - - /** - * Check if the current position is valid - * @return Boolean True if valid - */ - public function valid() { - - return is_array($this->value) || is_object($this->value); - - } - -} \ No newline at end of file diff --git a/lib/Firelit/Registry.php b/lib/Firelit/Registry.php deleted file mode 100644 index 0ef6725..0000000 --- a/lib/Firelit/Registry.php +++ /dev/null @@ -1,30 +0,0 @@ -ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : false; - $this->proxies = array(); - $this->method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : false; - $this->path = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : false; - $this->secure = isset($_SERVER['HTTPS']) ? ($_SERVER['HTTPS'] == 'on') : false; - $this->host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : false; - $this->referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : false; - $this->protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : false; - - $this->cli = (php_sapi_name() == 'cli'); - if ($this->cli) $this->method = 'CLI'; - - if (isset(static::$methodInput)) $this->method = static::$methodInput; - - $this->uri = ($this->cli ? false : ($this->secure ? 'https' : 'http') .'://'. $this->host . $this->path); - - if (is_callable('apache_request_headers')) { - - $this->headers = apache_request_headers(); - - if (self::$loadBalanced) { - - if (isset($this->headers['X-Forwarded-For'])) { - $ips = $this->headers['X-Forwarded-For']; - $ips = explode(', ', $ips); - $this->ip = array_shift($ips); - - $this->proxies = $ips; - } - - if (isset($this->headers['X-Forwarded-Proto'])) { - $this->secure = ($this->headers['X-Forwarded-Proto'] == 'HTTPS'); - } - - } elseif (isset($this->headers['X-Forwarded-For'])) { - - $ips = $this->headers['X-Forwarded-For']; - $ips = explode(', ', $ips); - $this->proxies = $ips; - - } - - } else { - $this->headers = array(); - } - - // Create our own global array for PUT data - global $_PUT; - $_PUT = array(); - - if (isset(self::$dataInput)) { - $this->body = self::$dataInput; - } else { - $this->body = file_get_contents("php://input"); - } - - if ($this->method == 'PUT') { - parse_str($this->body, $_PUT); - } - - if ($bodyFormat == 'json') { - - $this->put = array(); - $this->post = array(); - - $jsonErr = JSON_ERROR_NONE; - - if ($this->method == 'PUT') { - $this->put = json_decode($this->body, true); - $jsonErr = json_last_error(); - } elseif ($this->method == 'POST') { - $this->post = json_decode($this->body, true); - $jsonErr = json_last_error(); - } - - if ($jsonErr !== JSON_ERROR_NONE) { - switch ($jsonErr) { - case JSON_ERROR_DEPTH: - $jsonErrMsg = 'Maximum stack depth exceeded.'; - case JSON_ERROR_STATE_MISMATCH: - $jsonErrMsg = 'Underflow or the modes mismatch.'; - case JSON_ERROR_CTRL_CHAR: - $jsonErrMsg = 'Unexpected control character found.'; - case JSON_ERROR_SYNTAX: - $jsonErrMsg = 'Syntax error, malformed data.'; - case JSON_ERROR_UTF8: - $jsonErrMsg = 'Malformed UTF-8 characters, possibly incorrectly encoded.'; - default: - $jsonErrMsg = 'Generic error.'; - } - - throw new InvalidJsonException($jsonErrMsg); - } - - } else { - $this->post = $_POST; - $this->put = $_PUT; - } - - $this->get = $_GET; - $this->cookie = $_COOKIE; - - if ($filter) { - // Filter local copies of POST, GET & COOKIE data - // Unset global versions to prevent access to un-filtered - $this->filterInputs($filter); - - $_PUT = null; - $_POST = null; - $_GET = null; - $_COOKIE = null; - - } - - } - - public function filterInputs($filter = false) { - - if ($filter == false) return; - if (!is_callable($filter)) - throw new \Exception('Specified filter is not callable.'); - - $this->recurse($this->post, $filter); - $this->recurse($this->put, $filter); - $this->recurse($this->get, $filter); - $this->recurse($this->cookie, $filter); - - } - - protected function recurse(&$input, &$function) { - - if (is_array($input)) - foreach ($input as $name => &$value) - $this->recurse($value, $function); - else - $function($input); - - } - - public function __get($name) { - - if (property_exists($this, $name)) return $this->$name; - - throw new \Exception('The property "'. $name .'" is not valid.'); - - } - - public function __isset($name) { - - return isset($this->$name); - - } - -} \ No newline at end of file diff --git a/lib/Firelit/Response.php b/lib/Firelit/Response.php deleted file mode 100644 index abbdc11..0000000 --- a/lib/Firelit/Response.php +++ /dev/null @@ -1,312 +0,0 @@ -callback)) { - - if (self::$outputBuffering) { - - $out = ob_get_contents(); - - // Work-around: Can't call anonymous functions that are class properties - // PHP just looks for a method with that name - $callback = &$this->callback; - $callback($out); - - $this->clearBuffer(); - - echo $out; - - } else { - // Work-around: Can't call anonymous functions that are class properties - // PHP just looks for a method with that name - $callback = &$this->callback; - $callback(false); - - } - - } - - $this->endBuffer(); - - } - - /** - * Pass a closure to define a callback function - * Should take one parameter: the string to be sent as output - * Used passed variable referentially to save any changes to output - * If output buffering is off, false will be passed - * @param Function $function - */ - public function setCallback($function) { - - if (empty($function)) { - $this->callback = false; - return; - } - - if (!is_callable($function)) - throw new \Exception('Callback should be a function or false.'); - - $this->callback = $function; - - } - - /** - * Set the HTTP content type - * @param Mixed $type The response type (defaults to 'text/html') - */ - public function contentType($type = false) { - - if (headers_sent()) { - - if (self::$exceptOnHeaders) - throw new \Exception('Headers already sent. Content-type cannot be changed.'); - else return; - - } - - if (!$type) $type = "text/html"; - - self::$contentType = $type ."; charset=". strtolower(self::$charset); - header("Content-Type: ". self::$contentType); - - } - - /** - * Set the HTTP response code - * @param Mixed $code The response code to use or false to return current value - * @return Return the code used if $code is false - */ - public function code($code = false) { - - if (!$code) return http_response_code(); - - if (headers_sent()) { - - if (self::$exceptOnHeaders && (http_response_code() != $code)) - throw new \Exception('Headers already sent. HTTP response code cannot be changed.'); - else return; - - } - - self::$code = $code; - http_response_code(self::$code); - - } - - /** - * Redirect the client - * @param String $path - * @param Int $type 301 or 302 redirect - * @param Bool $end End response - */ - public function redirect($path, $type = 302, $end = true) { - // $type should be one of the following: - // 301 = Moved permanently - // 302 = Temporary redirect - // 303 = Perform GET at new location (instead of POST) - - if (headers_sent()) { - - if (self::$exceptOnHeaders) - throw new \Exception('Headers already sent. Redirect cannot be initiated.'); - else return; - - } - - if (self::$outputBuffering) - $this->cleanBuffer(); - - $this->code($type); - header('Location: '. $path); - - if ($end) exit; - - } - - /** - * Flush the output buffer (but leave enabled) - */ - public function flushBuffer() { - // Send buffer out - if (self::$outputBuffering) - ob_flush(); - - } - - /** - * Clean the output buffer - */ - public function cleanBuffer() { - // Empty buffer - if (self::$outputBuffering) - ob_clean(); - - } - - /** - * Clear the output buffer, alias to cleanBuffer() method - */ - public function clearBuffer() { - // Alias of cleanBuffer() - $this->cleanBuffer(); - - } - - /** - * Flush the buffer and end - */ - public function endBuffer() { - // Call cleanBuffer first if you don't want anything getting out - - if (self::$outputBuffering) - ob_end_flush(); - - self::$outputBuffering = false; - - } - - /** - * Alias to code() method, only does not return code - */ - public function setCode($code) { - // Set the HTTP response code - $this->code($code); - - } - - /** - * Alias to contentType() method - */ - public function setContentType($type) { - // Set the HTTP content type - $this->contentType($type); - - } - - -} - - -// Backwards compatabilty PHP<5.4 (TODO: REMOVE) -if (!function_exists('http_response_code')) { - function http_response_code($code = NULL) { - - if ($code !== NULL) { - - $GLOBALS['http_response_code'] = $code; - - if (headers_sent()) return; - - switch ($code) { - case 100: $text = 'Continue'; break; - case 101: $text = 'Switching Protocols'; break; - case 200: $text = 'OK'; break; - case 201: $text = 'Created'; break; - case 202: $text = 'Accepted'; break; - case 203: $text = 'Non-Authoritative Information'; break; - case 204: $text = 'No Content'; break; - case 205: $text = 'Reset Content'; break; - case 206: $text = 'Partial Content'; break; - case 300: $text = 'Multiple Choices'; break; - case 301: $text = 'Moved Permanently'; break; - case 302: $text = 'Moved Temporarily'; break; - case 303: $text = 'See Other'; break; - case 304: $text = 'Not Modified'; break; - case 305: $text = 'Use Proxy'; break; - case 400: $text = 'Bad Request'; break; - case 401: $text = 'Unauthorized'; break; - case 402: $text = 'Payment Required'; break; - case 403: $text = 'Forbidden'; break; - case 404: $text = 'Not Found'; break; - case 405: $text = 'Method Not Allowed'; break; - case 406: $text = 'Not Acceptable'; break; - case 407: $text = 'Proxy Authentication Required'; break; - case 408: $text = 'Request Time-out'; break; - case 409: $text = 'Conflict'; break; - case 410: $text = 'Gone'; break; - case 411: $text = 'Length Required'; break; - case 412: $text = 'Precondition Failed'; break; - case 413: $text = 'Request Entity Too Large'; break; - case 414: $text = 'Request-URI Too Large'; break; - case 415: $text = 'Unsupported Media Type'; break; - case 500: $text = 'Internal Server Error'; break; - case 501: $text = 'Not Implemented'; break; - case 502: $text = 'Bad Gateway'; break; - case 503: $text = 'Service Unavailable'; break; - case 504: $text = 'Gateway Time-out'; break; - case 505: $text = 'HTTP Version not supported'; break; - default: return; - break; - } - - $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'); - - header($protocol . ' ' . $code . ' ' . $text); - - } else { - - $code = (isset($GLOBALS['http_response_code']) ? $GLOBALS['http_response_code'] : 200); - - } - - return $code; - - } -} \ No newline at end of file diff --git a/lib/Firelit/RouteToError.php b/lib/Firelit/RouteToError.php deleted file mode 100644 index 5d5917c..0000000 --- a/lib/Firelit/RouteToError.php +++ /dev/null @@ -1,11 +0,0 @@ -request = $request; - - $this->method = $request->method; - - $rootPath = self::$rootPath; - if (preg_match('!/$!', $rootPath)) $rootPath = substr($rootPath, 0, -1); - - $this->uri = preg_replace('!^'. preg_quote($rootPath) .'!', '', $request->path); - if (strpos($this->uri, '?')) $this->uri = substr($this->uri, 0, strpos($this->uri, '?')); - - } - - public function __destruct() { - - if ($this->match || !is_callable($this->default)) return; // Response already sent or no default set - if (in_array(http_response_code(), array(301, 302, 303))) return; // No default route if redirect - - try { - - $this->default(); - - } catch (RouteToError $e) { - - $this->triggerError($e->getCode(), $e->getMessage()); - - } - - exit; - } - - /** - * Magic method used specifically for calling the default route and exception handler - * - * @param string $method - * @param array $args - * @return mixed - */ - public function __call($method, $args) { - if (isset($this->$method) && is_callable($this->$method)) { - return call_user_func_array($this->$method, $args); - } - } - - /** - * Check the method and uri and run the supplied function if match. - * The $execute function is passed an array of matches from $regExpUrlMatch - * - * @param int|array $filterMethod - * @param string|bool $regExpUrlMatch - * @param function $execute - * @return void - */ - public function add($filterMethod, $regExpUrlMatch, $execute) { - - if (!is_array($filterMethod)) $filterMethod = array($filterMethod); - - // (1) Does the request method match? - if (!in_array('*', $filterMethod) && !in_array($this->method, $filterMethod)) return; - - $params = array(); - - // (2) Does the URI match? (set $regExpUrlMatch to false to skip) - if ($regExpUrlMatch && ($this->method == 'CLI')) return; - if ($regExpUrlMatch && !preg_match($regExpUrlMatch, $this->uri, $params)) return; - - // Method and URI match! - $this->match = true; - - // Remove the full text match from the match array - array_shift($params); - - try { - - // Go! - $execute($params); - - } catch (RouteToError $e) { - - $this->triggerError($e->getCode(), $e->getMessage()); - - } - - // End execution - exit; - - } - - /** - * Set an error route for a specific error code (or 0 for default/catch-all) - * Function to execute will be passed two parameters: the error code & an optional error message - * - * @param int|array $errorCode - * @param function $execute - * @return Firelit\Router - */ - public function errorRoute($errorCode, $execute) { - if (!is_array($errorCode)) $errorCode = array($errorCode); - foreach ($errorCode as $thisCode) $this->error[$thisCode] = $execute; - - return $this; - } - - /** - * Set the default route if no other routes match - * - * @param function $execute - * @return Firelit\Router - */ - public function defaultRoute($execute) { - $this->default = $execute; - - return $this; - } - - /** - * A function to handle any exceptions that are caught - * The passed function should take one parameter, an exception object - * - * @param function $execute - * @return Firelit\Router - */ - public function exceptionHandler($execute) { - $this->exceptionHandler = $execute; - set_exception_handler($this->exceptionHandler); - - return $this; - } - - /** - * Trigger an error route based on the error code and exit script with that error code - * - * @param int $errorCode - * @param string $errorMessage - * @return void - */ - public function triggerError($errorCode, $errorMessage = '') { - $callError = $errorCode; - - if (!isset($this->error[$errorCode]) || !is_callable($this->error[$errorCode])) { - if (isset($this->error[0]) && is_callable($this->error[0])) $callError = 0; - else exit($errorCode); - } - - // Error response function exists - $this->match = true; - - //call_user_func_array($this->error[$errorCode], array($errorCode, $errorMessage)); - $this->error[$callError]($errorCode, $errorMessage); - // Or use call_user_func_array ? - - exit($errorCode); - } - -} diff --git a/lib/Firelit/Session.php b/lib/Firelit/Session.php deleted file mode 100644 index ea8fcaa..0000000 --- a/lib/Firelit/Session.php +++ /dev/null @@ -1,164 +0,0 @@ - array( - 'name' => 'session', - 'lifetime' => 0, // Expires when browser closes - 'path' => '/', - 'domain' => false, // Set to false to use the current domain - 'secureOnly' => false, - 'httpOnly' => true - ), - 'validatorSalt' => 'dJa832lwkdP1' // Recommend changing to slow session key brute-force spoofing - ); - - private $session_id; - - public function __construct(\SessionHandlerInterface $store = null, $sessionId = false, $validateSessionId = true) { - // Create a session using the given SessionHandlerInterface object - // If null, will use PHP's native SESSION engine - - if ($store) session_set_save_handler($store, true); - - // Check for Session ID in cookies - if (!$sessionId) - $sessionId = $this->getSessionId(); - - // Generate Session ID if none available - if (!$sessionId) - $sessionId = $this->generateSessionId(); - - // Set the Session ID and send cookie with value - $this->setSessionId($sessionId, $validateSessionId); - - @session_start(); - - } - - public function __set($name, $val) { - // Magic sesion value setter - - $_SESSION[$name] = $val; - - } - - public function __get($name) { - // Magic sesion value getter - - if (!isset($_SESSION[$name])) return null; - return $_SESSION[$name]; - - } - - public function __isset($name) { - // Magic session isset - - return isset($_SESSION[$name]); - - } - - public function __unset($name) { - // Magic session unsetter - - unset($_SESSION[$name]); - - } - - // Can only be excuted before session_start() - protected function setSessionId($sessionId, $validateSessionId = true) { - - if ($validateSessionId) { - if (!$this->sessionIdIsValid($sessionId)) - $sessionId = false; - } - - if (!$sessionId) - $sessionId = $this->generateSessionId(); - - $this->session_id = $sessionId; - - // Not relying on session to retrieve it's ID from the cookie - session_id($sessionId); - - // Will set cookie automatically anyway, let's try to control it - session_set_cookie_params( - self::$config['cookie']['lifetime'], - self::$config['cookie']['path'], - self::$config['cookie']['domain'], - self::$config['cookie']['secureOnly'], - self::$config['cookie']['httpOnly'] ); - - session_name(self::$config['cookie']['name']); - - } - - public function getSessionId() { - - if (!$this->session_id) { - - if (isset($_COOKIE[self::$config['cookie']['name']])) - $this->session_id = $_COOKIE[self::$config['cookie']['name']]; - - } - - return $this->session_id; - - } - - public static function sessionIdIsValid($sessionId) { - - $sessionId = preg_replace('/[^A-Za-z0-9\+\/=]+/', '', $sessionId); - - if (strlen($sessionId) != 50) return false; - - $sids = explode('=', $sessionId); - if (sizeof($sids) != 2) return false; - - // Verify mini-hmac; not critical, just a quick sanity check - $check = static::generateHmacSid($sids[0].'=', static::$config['validatorSalt']); - - if ($sessionId !== $check) return false; - - return true; - - } - - public static function generateSessionId() { - - if (isset($_SERVER['REMOTE_ADDR'])) $remAddr = $_SERVER['REMOTE_ADDR']; - else $remAddr = mt_rand(0, 1000000); - - if (isset($_SERVER['REMOTE_PORT'])) $remPort = $_SERVER['REMOTE_PORT']; - else $remPort = mt_rand(0, 1000000); - - // Looks like we need a new session ID - $sid = base64_encode( hash('sha256', microtime() .'|'. $remAddr .'|'. $remPort .'|'. mt_rand(0, 1000000), true) ); - - // Create mini-hmac; not critical, just a quick sanity check - return static::generateHmacSid($sid, static::$config['validatorSalt']); - - } - - public static function generateHmacSid($partSid, $salt) { - - return $partSid . substr( base64_encode( hash_hmac('sha256', $partSid, $salt, true) ), 0, 6 ); - - } - - public function destroy() { - // Remove all data from and traces of the current session - - session_destroy(); - - } - - public function __destruct() { - // Not required as it is handled automatically but could be convienent to close a session early or juggle multiple sessions - session_write_close(); - } - -} diff --git a/lib/Firelit/Singleton.php b/lib/Firelit/Singleton.php deleted file mode 100644 index c87eebe..0000000 --- a/lib/Firelit/Singleton.php +++ /dev/null @@ -1,33 +0,0 @@ -newInstanceArgs($args); - - } - - return self::$singletons[$class]; - - } - - static public function destruct() { - - $class = get_called_class(); - if (isset(self::$singletons[$class])) - unset(self::$singletons[$class]); - - } - -} \ No newline at end of file diff --git a/lib/Firelit/States.php b/lib/Firelit/States.php deleted file mode 100644 index 1c43382..0000000 --- a/lib/Firelit/States.php +++ /dev/null @@ -1,175 +0,0 @@ - array( - "AL" => "Alabama", - "AK" => "Alaska", - "AS" => "American Samoa", - "AZ" => "Arizona", - "AR" => "Arkansas", - "CA" => "California", - "CO" => "Colorado", - "CT" => "Connecticut", - "DE" => "Delaware", - "DC" => "Dist of Columbia", - "FM" => "Fed States Of Micronesia", - "FL" => "Florida", - "GA" => "Georgia", - "GU" => "Guam", - "HI" => "Hawaii", - "ID" => "Idaho", - "IL" => "Illinois", - "IN" => "Indiana", - "IA" => "Iowa", - "KS" => "Kansas", - "KY" => "Kentucky", - "LA" => "Louisiana", - "ME" => "Maine", - "MH" => "Marshall Islands", - "MD" => "Maryland", - "MA" => "Massachusetts", - "MI" => "Michigan", - "MN" => "Minnesota", - "MS" => "Mississippi", - "MO" => "Missouri", - "MT" => "Montana", - "NE" => "Nebraska", - "NV" => "Nevada", - "NH" => "New Hampshire", - "NJ" => "New Jersey", - "NM" => "New Mexico", - "NY" => "New York", - "NC" => "North Carolina", - "ND" => "North Dakota", - "MP" => "Northern Mariana Islands", - "OH" => "Ohio", - "OK" => "Oklahoma", - "OR" => "Oregon", - "PW" => "Palau", - "PA" => "Pennsylvania", - "PR" => "Puerto Rico", - "RI" => "Rhode Island", - "SC" => "South Carolina", - "SD" => "South Dakota", - "TN" => "Tennessee", - "TX" => "Texas", - "UT" => "Utah", - "VT" => "Vermont", - "VI" => "Virgin Islands", - "VA" => "Virginia", - "WA" => "Washington", - "WV" => "West Virginia", - "WI" => "Wisconsin", - "WY" => "Wyoming", - "AA" => "Armed Forces (AA)", - "AE" => "Armed Forces (AE)", - "AP" => "Armed Forces (AP)" - ), - "CA" => array( - "AB" => "Alberta", - "BC" => "British Columbia", - "MB" => "Manitoba", - "NB" => "New Brunswick", - "NL" => "Newfoundland and Labrador", - "NT" => "Northwest Territories", - "NS" => "Nova Scotia", - "NU" => "Nunavut", - "ON" => "Ontario", - "PE" => "Prince Edward Island", - "QC" => "Quebec", - "SK" => "Saskatchewan", - "YT" => "Yukon" - ), - "MX" => array( - "AG" => "Aguascalientes", - "BC" => "Baja California Norte", - "BS" => "Baja California Sur", - "CM" => "Campeche", - "CS" => "Chiapas", - "CH" => "Chihuahua", - "CO" => "Coahuila", - "CL" => "Colima", - "DF" => "Distrito Federal", - "DG" => "Durango", - "GT" => "Guanajuato", - "GR" => "Guerrero", - "HG" => "Hidalgo", - "JA" => "Jalisco", - "MX" => "Mexico", - "MI" => "Michoacan", // Michoacán - "MO" => "Morelos", - "NA" => "Nayarit", - "NL" => "Nuevo Leon", - "OA" => "Oaxaca", - "PU" => "Puebla", - "QT" => "Queretaro", - "QR" => "Quintana Roo", - "SL" => "San Luis Potosi", - "SI" => "Sinaloa", - "SO" => "Sonora", - "TB" => "Tabasco", - "TM" => "Tamaulipas", - "TL" => "Tlaxcala", - "VE" => "Veracruz", - "YU" => "Yucatan", - "ZA" => "Zacatecas" - ) - ); - - function __construct() { } - - /* - Useage Example: - - States::display( 'US', create_function('$abbrv,$name', 'return "";') ); - - */ - public static function display($country, $callback, $subset = false) { - // $country is the country to display states from - // $callback is the anonymous function for formating the data - // $subset should be an array of ok abbreviations, set to false for all states - // Returns [true] on success, [false] if no states to display - - if (!is_callable($callback)) throw new Exception('Callback function is not callable.'); - if ($subset && !is_array($subset)) throw new Exception('Subset must be false or an array of acceptable country abbreviations.'); - - if (class_exists('Countries') && !Countries::check($country)) return false; - if (!isset(self::$list[$country])) return false; - - foreach (self::$list[$country] as $abbrv => $name) { - - if ($subset && !in_array($abbrv, $subset)) continue; - - echo $callback($abbrv, $name); - - } - - return true; - } - - public static function check($country, $stateAbbrv) { - // Check if a state abbrevation is valid - // Returns [true] if valid, [false] if invalid and [null] if states are not set for this country - - if (class_exists('Countries') && !Countries::check($country)) return false; - if (!isset(self::$list[$country])) return null; - if (!isset(self::$list[$country][$stateAbbrv])) return false; - - return true; - - } - - public static function getName($country, $stateAbbrv, $html = true) { - // Get a states's name from its abbrevation - // Returns the state name [string] if available, [false] if not available - - if (self::check($country, $stateAbbrv)) return false; - return self::$list[$country][$stateAbbrv]; - - } - -} \ No newline at end of file diff --git a/lib/Firelit/Strings.php b/lib/Firelit/Strings.php deleted file mode 100644 index 0e69c46..0000000 --- a/lib/Firelit/Strings.php +++ /dev/null @@ -1,111 +0,0 @@ - $v) - self::cleanUTF8($input[$k], $lineBreaksOk); - - } else { - - $input = mb_convert_encoding($input, "UTF-8", "UTF-8"); - if ($lineBreaksOk) $input = preg_replace('![\x00-\x09\x0B\x0C\x0E-\x1F\x7F-\x9F]!', '', $input); - else $input = preg_replace('!\p{C}!u', '', $input); - - } - } - - public static function html($string) { - // HTML-escaping with UTF-8 support - return htmlentities($string, ENT_COMPAT, 'UTF-8'); - } - - public static function xml($string) { - // XML-escaping - $string = htmlentities($string); - $xml = array('"','&','&','<','>',' ','¡','¢','£','¤','¥','¦','§','¨','©','ª','«','¬','­','®','¯','°','±','²','³','´','µ','¶','·','¸','¹','º','»','¼','½','¾','¿','À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','×','Ø','Ù','Ú','Û','Ü','Ý','Þ','ß','à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ð','ñ','ò','ó','ô','õ','ö','÷','ø','ù','ú','û','ü','ý','þ','ÿ'); - $html = array('"','&','&','<','>',' ','¡','¢','£','¤','¥','¦','§','¨','©','ª','«','¬','­','®','¯','°','±','²','³','´','µ','¶','·','¸','¹','º','»','¼','½','¾','¿','À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','×','Ø','Ù','Ú','Û','Ü','Ý','Þ','ß','à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ð','ñ','ò','ó','ô','õ','ö','÷','ø','ù','ú','û','ü','ý','þ','ÿ'); - $string = str_replace($html, $xml, $string); - $string = str_ireplace($html, $xml, $string); - return $string; - } - - public static function csv($string) { - // CSV-escaping for a CSV cell - $string = str_replace('"', '""', $string); - return '"'.utf8_decode($string).'"'; - } - - public static function lower($string) { - // Multi-byte-safe lower-case - return mb_strtolower($string, 'UTF-8'); - } - - public static function upper($string) { - // Multi-byte-safe lower-case - return mb_strtoupper($string, 'UTF-8'); - } - - public static function ucword($string) { - // Multi-byte-safe lower-case - return mb_convert_case($string, MB_CASE_TITLE, 'UTF-8'); // This is also doing a lower() first, not like ucwords() - } - -} \ No newline at end of file diff --git a/lib/Firelit/TestModeException.php b/lib/Firelit/TestModeException.php deleted file mode 100644 index 3c408a6..0000000 --- a/lib/Firelit/TestModeException.php +++ /dev/null @@ -1,3 +0,0 @@ -store = $store; - - } - - public function __set($name, $val) { - - $this->store->set($name, $val); - - } - - public function __unset($name) { - - $this->store->set($name, null); - - } - - public function __get($name) { - - return $this->store->get($name); - - } - -} \ No newline at end of file diff --git a/lib/Firelit/VarsStore.php b/lib/Firelit/VarsStore.php deleted file mode 100644 index d088788..0000000 --- a/lib/Firelit/VarsStore.php +++ /dev/null @@ -1,11 +0,0 @@ - 'Vars', - 'maxNameLength' => 32 - ); - - public function __construct(Query $queryObject, $config = array()) { - - $this->db = $queryObject; - - // Merge config data with defaults - self::config($config); - - } - - public static function config($config) { - - self::$config = array_merge(self::$config, $config); - - } - - public function set($name, $value) { - - $this->db->replace(self::$config['tableName'], array( - 'name' => $name, - 'value' => serialize($value) - )); - - if (!$this->db->success()) - throw new \Exception('Error setting value in database.'); - - } - - public function get($name) { - - $this->db->select(self::$config['tableName'], array('value'), "`name`=:name", array( 'name' => $name ), 1); - - if (!$this->db->success()) - throw new \Exception('Error getting value from database.'); - - if ($row = $this->db->fetch()) return unserialize($row['value']); - else return null; - - } - - public static function install(Query $query) { - // One-time install - // Create the supporting tables in the db - - // Running MySql >= 5.5.3 ? Use utf8mb4 insetad of utf8. - $sql = "CREATE TABLE IF NOT EXISTS `". self::$config['tableName'] ."` ( - `name` varchar(". self::$config['maxNameLength'] .") character set utf8 collate utf8_bin NOT NULL, - `value` longtext NOT NULL, - UNIQUE KEY `name` (`name`) - ) ENGINE=MyISAM DEFAULT CHARSET=utf8;" - - $q = $query->query($sql); - - if (!$q->success()) - throw new \Exception('Install failed! ('. __FILE__ .':'. __LINE__ .')'); - - return $query->insertId(); - - } - -} \ No newline at end of file diff --git a/lib/Firelit/VarsStoreFile.php b/lib/Firelit/VarsStoreFile.php deleted file mode 100644 index bdd4522..0000000 --- a/lib/Firelit/VarsStoreFile.php +++ /dev/null @@ -1,40 +0,0 @@ -file = $fileName; - - } - - public function set($name, $value) { - - $current = $this->get($name); - - if (is_null($value)) unset($current[$name]); - else $current[$name] = $value; - - $res = file_put_contents($this->file, serialize($current)); - - if ($res === false) - throw new \Exception('Error saving var data to file.'); - - } - - public function get($name) { - - $data = file_get_contents($this->file); - - if ($data === false) - throw new \Exception('Error reading var data from file.'); - - return unserialize($data); - - } - -} \ No newline at end of file diff --git a/lib/Firelit/View.php b/lib/Firelit/View.php deleted file mode 100644 index 638e2fa..0000000 --- a/lib/Firelit/View.php +++ /dev/null @@ -1,110 +0,0 @@ -setLayout($layout); - $this->setTemplate($template); - } - - public function setLayout($layout) { - $this->layout = $layout; - return $this; - } - - public function setTemplate($template) { - $this->template = $template; - return $this; - } - - public function setData($data) { - if (is_array($data)) $this->data = $data; - return $this; - } - - protected function yieldNow() { - if (!$this->template) return; - - extract($this->data, EXTR_SKIP); - - $file = $this->fileName($this->template); - include($file); - } - - protected function html($html) { - return htmlentities($html); - } - - protected function addAsset($name, $attributes = array()) { - $nameArray = explode('.', $name); - $ext = array_pop($nameArray); - - switch ($ext) { - case 'js': - echo ''."\n"; - break; - case 'css': - if (!isset($attributes['rel'])) $attributes['rel'] = 'stylesheet'; - - echo ' $value) echo ' '. $name .'="'. $this->html($value) .'"'; - echo ">\n"; - - break; - case 'ico': - - echo ' $value) echo ' '. $name .'="'. $this->html($value) .'"'; - echo ">\n"; - - break; - } - - } - - protected function includePart($name, $moreData = array()) { - extract($this->data, EXTR_SKIP); - extract($moreData, EXTR_SKIP); - - $file = $this->fileName($name); - include($file); - } - - public function render($data = false) { - $this->setData($data); - extract($this->data, EXTR_SKIP); - - if ($this->layout) { - - $file = $this->fileName($this->layout); - include($file); - - } else $this->yieldNow(); - - } - - protected function fileName($name) { - if (preg_match('/\.php$/', $name)) $ext = ''; else $ext = '.php'; - $file = static::$viewFolder . $name . $ext; - - if (!file_exists($file)) throw new \Exception('View file does not exist: '. $file); - if (!is_readable($file)) throw new \Exception('View file not readable: '. $file); - - return $file; - } - - static public function quickRender($template, $layout = false, $data = false) { - $class = get_called_class(); - $view = new $class($template, $layout); - $view->render($data); - return $view; - } -} \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml index de0a8c8..3fb3fcb 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,17 +8,23 @@ ./tests/ApiResponseTest.php ./tests/CacheTest.php + ./tests/CountriesTest.php + ./tests/CryptoKeyTest.php + ./tests/CryptoPackageTest.php ./tests/CryptoTest.php ./tests/DatabaseMigrationManagerTest.php ./tests/DatabaseMigrationTest.php ./tests/DatabaseObjectTest.php ./tests/InputValidatorTest.php - + ./tests/QueryIteratorTest.php + ./tests/QueryTest.php ./tests/RegistryTest.php ./tests/RequestTest.php ./tests/ResponseTest.php ./tests/RouterTest.php ./tests/SessionTest.php + ./tests/StringsTest.php + ./tests/VarsTest.php ./tests/ViewTest.php diff --git a/src/ApiResponse.php b/src/ApiResponse.php new file mode 100644 index 0000000..6c39e1f --- /dev/null +++ b/src/ApiResponse.php @@ -0,0 +1,175 @@ +responseFormat = strtoupper($responseFormat); + + // Set appropriate output formats + if ($this->responseFormat == 'JSON') { + $this->setContentType('application/json'); + } + } + + /** + * Set the template or defaults for response + * @param Array $template Template response to send + */ + public function setTemplate($template) + { + // Set an output template to be sure minimum required fields are returned (eg, there's always a 'success' property) + $this->response = array_merge($this->response, $template); + } + + /** + * Set response data + * @param Array $response Response to send + * @param Bool $replaceResponse Indicates if response should be completely replaced with previous param (instead of merged) + */ + public function set($response, $replaceResponse = false) + { + if ($replaceResponse) { + $this->response = $response; + } else { + $this->response = array_merge($this->response, $response); + } + } + + /** + * Sending the response, if it hasn't already been sent + * @param Array $response Response to send + * @param Bool $replaceResponse Indicates if response should be completely replaced with previous param (instead of merged) + */ + public function respond($response = array(), $replaceResponse = false) + { + // Set any final data + $this->set($response, $replaceResponse); + + // Make sure it only responds once + if ($this->hasResponded()) { + return; + } + + // Clear anything that may have snuck in + $this->cleanBuffer(); + + if (self::$code == 204) { + // No-body response + $this->responseSent = true; + return; + } + + if (!empty($this->apiResponseCallback) && is_callable($this->apiResponseCallback)) { + $callback = &$this->apiResponseCallback; + $callback($this->response); + } + + // Format for specific output type + if ($this->responseFormat == 'JSON') { + if ($this->jsonCallbackWrap) { + echo $this->jsonCallbackWrap . '('; + } + + echo json_encode($this->response); + + if ($this->jsonCallbackWrap) { + echo ');'; + } + } else { + throw new \Exception('Invalid response format: '. $this->responseFormat); + } + + // Indicate the response as already sent + $this->responseSent = true; + } + + /** + * Already sent response? + * @return Bool + */ + public function hasResponded() + { + return $this->responseSent; + } + + /** + * Cancel the ApiResponse (allows switch to output of non-api data if needed) + */ + public function cancel() + { + // No longer need a response + $this->responseSent = true; + } + + /** + * Set a function to be wrapped around JSON response (for JSONP) + * @param String $callback The function name + */ + public function setJsonCallbackWrap($callback) + { + + if (empty($callback)) { + $this->jsonCallbackWrap = false; + return; + } + + if (!is_string($callback)) { + throw new \Exception('JSON callback wrap should be a string or false.'); + } + + $this->jsonCallbackWrap = $callback; + } + + /** + * Set a function to be called upon response (or false to cancel) + * @param Funcation $callback The function to call with 1 parameter being the data to return (pass by reference to modify) + */ + public function setApiCallback($callback) + { + + if (empty($callback)) { + $this->apiResponseCallback = false; + return; + } + + if (!is_callable($callback)) { + throw new \Exception('Callback should be a function or false.'); + } + + $this->apiResponseCallback = $callback; + } + + /** + * Most likely called at end of execution, outputs data as needed + */ + public function __destruct() + { + + // Respond, if it hasn't yet + if (!$this->hasResponded()) { + $this->respond(array(), false); + } + + // Parent's destructer returns all data in output buffer + parent::__destruct(); + } +} diff --git a/src/Cache.php b/src/Cache.php new file mode 100644 index 0000000..4ee94bf --- /dev/null +++ b/src/Cache.php @@ -0,0 +1,160 @@ + array( + 'enabled' => false, + 'servers' => array() + ) + ); + + protected static $memcached = false; + protected static $cache = array(); + + // Default settings for each memcached server + public static $memcachedServerDefaults = array( + 'host' => 'localhost', + 'port' => 11211, + 'persistent' => true, + 'weight' => 1, + 'timeout' => 1 + ); + + // Boolean indicating if a get() resulted in a cache hit + public static $cacheHit = false; + + /** + * __construct() + */ + public function __construct() + { + } + + /** + * config() + * @param array $config Updated configuration array + */ + public static function config($config) + { + + self::$config = array_merge(self::$config, $config); + } + + /** + * get() + * @param string $name Name of variable as stored in cache + * @param string $closure Optional closure to get value if cache miss + * @return mixed The cached value (or closure-returned value, if cache miss), null if cache-miss and no closure + */ + public static function get($name, $closure = false) + { + + if (!is_string($name)) { + throw new \Exception('Cache key must be a string.'); + } + + // First check php-memory cache + if (isset(self::$cache[$name])) { + // Cache hit! + self::$cacheHit = true; + return self::$cache[$name]; + } + + if (self::$config['memcached']['enabled']) { + // Connect if not connected + self::memcachedConnect(); + + // Check if in memcached + $val = self::$memcached->get($name); + + if (self::$memcached->getResultCode() == \Memcached::RES_SUCCESS) { + // Cache hit! + self::$cacheHit = true; + + // Set php-memory cache + self::$cache[$name] = $val; + return $val; + } + } + + self::$cacheHit = false; + + // If no other way to get value, return + if (!is_callable($closure)) { + return null; + } + + // Call given closure to get value + $val = $closure(); + + // Nothing returned, no need to store it + if (is_null($val)) { + return null; + } + + // Store closure-returned value in cache + self::set($name, $val); + + return $val; + } + + /** + * set() + * @param string $name Name of variable to be stored in cache + * @param string $val Value of variable to be stored, null to delete value from cache + */ + public static function set($name, $val) + { + + if (!is_string($name)) { + throw new \Exception('Cache key must be a string.'); + } + + // Connect if not connected + if (self::$config['memcached']['enabled']) { + self::memcachedConnect(); + } + + if (is_null($val)) { + // If $val is null, remove from cache + unset(self::$cache[$name]); + + // Remove from memcached + if (self::$config['memcached']['enabled']) { + self::$memcached->delete($name); + } + } else { + // Store in php-memory cache + self::$cache[$name] = $val; + + // Store in memcached + if (self::$config['memcached']['enabled']) { + self::$memcached->set($name, $val); + } + } + } + + /** + * memcachedConnect() + */ + public static function memcachedConnect() + { + + if (self::$memcached) { + return; + } + + self::$memcached = new \Memcached(); + + foreach (self::$config['memcached']['servers'] as $server) { + extract(array_merge(self::$memcachedServerDefaults, $server)); + + self::$memcached->addServers($host, $port, $persistent, $weight, $timeout); + } + } +} diff --git a/src/Controller.php b/src/Controller.php new file mode 100644 index 0000000..e92b526 --- /dev/null +++ b/src/Controller.php @@ -0,0 +1,42 @@ +newInstanceArgs($method ? array() : $args); + + if ($method) { + // Execute a method + $reflectMethod = new \ReflectionMethod($controller, $method); + return $reflectMethod->invokeArgs($newClass, $args); + } + + // If method specified, return the result of that method call (above) + // Else return the object itself + return $newClass; + } +} diff --git a/src/Countries.php b/src/Countries.php new file mode 100644 index 0000000..19be457 --- /dev/null +++ b/src/Countries.php @@ -0,0 +1,326 @@ + "Afghanistan", + "AX" => "Ã…land Islands", + "AL" => "Albania", + "DZ" => "Algeria", + "AS" => "American Samoa", + "AD" => "Andorra", + "AO" => "Angola", + "AI" => "Anguilla", + "AQ" => "Antarctica", + "AG" => "Antigua and Barbuda", + "AR" => "Argentina", + "AM" => "Armenia", + "AW" => "Aruba", + "AU" => "Australia", + "AT" => "Austria", + "AZ" => "Azerbaijan", + "BS" => "Bahamas", + "BH" => "Bahrain", + "BD" => "Bangladesh", + "BB" => "Barbados", + "BY" => "Belarus", + "BE" => "Belgium", + "BZ" => "Belize", + "BJ" => "Benin", + "BM" => "Bermuda", + "BT" => "Bhutan", + "BO" => "Bolivia", + "BA" => "Bosnia and Herzegovina", + "BW" => "Botswana", + "BV" => "Bouvet Island", + "BR" => "Brazil", + "IO" => "British Indian Ocean Territory", + "BN" => "Brunei Darussalam", + "BG" => "Bulgaria", + "BF" => "Burkina Faso", + "BI" => "Burundi", + "KH" => "Cambodia", + "CM" => "Cameroon", + "CA" => "Canada", + "CV" => "Cape Verde", + "KY" => "Cayman Islands", + "CF" => "Central African Republic", + "TD" => "Chad", + "CL" => "Chile", + "CN" => "China", + "CX" => "Christmas Island", + "CC" => "Cocos (Keeling) Islands", + "CO" => "Colombia", + "KM" => "Comoros", + "CG" => "Congo", + "CD" => "Congo, The Democratic Republic of", + "CK" => "Cook Islands", + "CR" => "Costa Rica", + "CI" => "Côte D'Ivoire", + "HR" => "Croatia", + "CU" => "Cuba", + "CY" => "Cyprus", + "CZ" => "Czech Republic", + "DK" => "Denmark", + "DJ" => "Djibouti", + "DM" => "Dominica", + "DO" => "Dominican Republic", + "EC" => "Ecuador", + "EG" => "Egypt", + "SV" => "El Salvador", + "GQ" => "Equatorial Guinea", + "ER" => "Eritrea", + "EE" => "Estonia", + "ET" => "Ethiopia", + "FK" => "Falkland Islands (Malvinas)", + "FO" => "Faroe Islands", + "FJ" => "Fiji", + "FI" => "Finland", + "FR" => "France", + "GF" => "French Guiana", + "PF" => "French Polynesia", + "TF" => "French Southern Territories", + "GA" => "Gabon", + "GM" => "Gambia", + "GE" => "Georgia", + "DE" => "Germany", + "GH" => "Ghana", + "GI" => "Gibraltar", + "GR" => "Greece", + "GL" => "Greenland", + "GD" => "Grenada", + "GP" => "Guadeloupe", + "GU" => "Guam", + "GT" => "Guatemala", + "GG" => "Guernsey", + "GN" => "Guinea", + "GW" => "Guinea-Bissau", + "GY" => "Guyana", + "HT" => "Haiti", + "HM" => "Heard Island and Mcdonald Islands", + "VA" => "Holy See", + "HN" => "Honduras", + "HK" => "Hong Kong", + "HU" => "Hungary", + "IS" => "Iceland", + "IN" => "India", + "ID" => "Indonesia", + "IR" => "Iran", + "IQ" => "Iraq", + "IE" => "Ireland", + "IM" => "Isle of Man", + "IL" => "Israel", + "IT" => "Italy", + "JM" => "Jamaica", + "JP" => "Japan", + "JE" => "Jersey", + "JO" => "Jordan", + "KZ" => "Kazakhstan", + "KE" => "Kenya", + "KI" => "Kiribati", + "KP" => "Korea, Democratic People's Rep of", + "KR" => "Korea, Republic of", + "KW" => "Kuwait", + "KG" => "Kyrgyzstan", + "LA" => "Lao People's Democratic Republic", + "LV" => "Latvia", + "LB" => "Lebanon", + "LS" => "Lesotho", + "LR" => "Liberia", + "LY" => "Libyan Arab Jamahiriya", + "LI" => "Liechtenstein", + "LT" => "Lithuania", + "LU" => "Luxembourg", + "MO" => "Macao", + "MK" => "Macedonia", + "MG" => "Madagascar", + "MW" => "Malawi", + "MY" => "Malaysia", + "MV" => "Maldives", + "ML" => "Mali", + "MT" => "Malta", + "MH" => "Marshall Islands", + "MQ" => "Martinique", + "MR" => "Mauritania", + "MU" => "Mauritius", + "YT" => "Mayotte", + "MX" => "Mexico", + "FM" => "Micronesia, Federated States of", + "MD" => "Moldova, Republic of", + "MC" => "Monaco", + "MN" => "Mongolia", + "ME" => "Montenegro", + "MS" => "Montserrat", + "MA" => "Morocco", + "MZ" => "Mozambique", + "MM" => "Myanmar", + "NA" => "Namibia", + "NR" => "Nauru", + "NP" => "Nepal", + "NL" => "Netherlands", + "AN" => "Netherlands Antilles", + "NC" => "New Caledonia", + "NZ" => "New Zealand", + "NI" => "Nicaragua", + "NE" => "Niger", + "NG" => "Nigeria", + "NU" => "Niue", + "NF" => "Norfolk Island", + "MP" => "Northern Mariana Islands", + "NO" => "Norway", + "OM" => "Oman", + "PK" => "Pakistan", + "PW" => "Palau", + "PS" => "Palestinian Territory, Occupied", + "PA" => "Panama", + "PG" => "Papua New Guinea", + "PY" => "Paraguay", + "PE" => "Peru", + "PH" => "Philippines", + "PN" => "Pitcairn", + "PL" => "Poland", + "PT" => "Portugal", + "PR" => "Puerto Rico", + "QA" => "Qatar", + "RE" => "Réunion", + "RO" => "Romania", + "RU" => "Russian Federation", + "RW" => "Rwanda", + "BL" => "Saint Barthélemy", + "SH" => "Saint Helena", + "KN" => "Saint Kitts and Nevis", + "LC" => "Saint Lucia", + "MF" => "Saint Martin", + "PM" => "Saint Pierre and Miquelon", + "VC" => "Saint Vincent and The Grenadines", + "WS" => "Samoa", + "SM" => "San Marino", + "ST" => "Sao Tome and Principe", + "SA" => "Saudi Arabia", + "SN" => "Senegal", + "RS" => "Serbia", + "SC" => "Seychelles", + "SL" => "Sierra Leone", + "SG" => "Singapore", + "SK" => "Slovakia", + "SI" => "Slovenia", + "SB" => "Solomon Islands", + "SO" => "Somalia", + "ZA" => "South Africa", + "GS" => "South Georgia (SGSSI)", + "ES" => "Spain", + "LK" => "Sri Lanka", + "SD" => "Sudan", + "SR" => "Suriname", + "SJ" => "Svalbard and Jan Mayen", + "SZ" => "Swaziland", + "SE" => "Sweden", + "CH" => "Switzerland", + "SY" => "Syrian Arab Republic", + "TW" => "Taiwan", + "TJ" => "Tajikistan", + "TZ" => "Tanzania", + "TH" => "Thailand", + "TL" => "Timor-Leste", + "TG" => "Togo", + "TK" => "Tokelau", + "TO" => "Tonga", + "TT" => "Trinidad and Tobago", + "TN" => "Tunisia", + "TR" => "Turkey", + "TM" => "Turkmenistan", + "TC" => "Turks and Caicos Islands", + "TV" => "Tuvalu", + "UG" => "Uganda", + "UA" => "Ukraine", + "AE" => "United Arab Emirates", + "GB" => "United Kingdom", + "US" => "United States", + "UM" => "U.S. Minor Outlying Islands", + "UY" => "Uruguay", + "UZ" => "Uzbekistan", + "VU" => "Vanuatu", + "VA" => "Vatican City State", + "VE" => "Venezuela", + "VN" => "Viet Nam", + "VG" => "Virgin Islands, British", + "VI" => "Virgin Islands, U.S.", + "WF" => "Wallis and Futuna", + "EH" => "Western Sahara", + "YE" => "Yemen", + "ZM" => "Zambia", + "ZW" => "Zimbabwe" + ); + + /* + Useage Example: + + Countries::display( create_function('$abbrv,$name', 'return "";') ); + + */ + public static function display($callback, $subset = false) + { + // $callback is the anonymous function for formating the data + // $subset should be an array of ok abbreviations, set to false for all countries + // returns true on success + + if (!is_callable($callback)) { + throw new Exception('Callback function is not callable.'); + } + if ($subset && !is_array($subset)) { + throw new Exception('Subset must be false or an array of acceptable country abbreviations.'); + } + + foreach (self::$list as $abbrv => $name) { + if ($subset && !in_array($abbrv, $subset)) { + continue; + } + + echo $callback($abbrv, $name); + } + + return true; + } + + public static function check($countryAbbrv) + { + // Check if a country abbrevation is valid + + if (isset(self::$list[$countryAbbrv])) { + return true; + } + return false; + } + + public static function getName($countryAbbrv, $html = true) + { + // Get a country's name from its abbrevation + + if (!self::check($countryAbbrv)) { + return false; + } + + $ret = self::$list[$countryAbbrv]; + + if ($html) { + $find = array( + 'Ã…', + 'ô', + 'é' + ); + + $replace = array( + 'Å', + 'ô', + 'é' + ); + + $ret = str_replace($find, $replace, $ret); + } + + return $ret; + } +} diff --git a/src/Crypto.php b/src/Crypto.php new file mode 100644 index 0000000..a3bae63 --- /dev/null +++ b/src/Crypto.php @@ -0,0 +1,128 @@ +80 characters, related to key size) + */ +class Crypto +{ + + const PUBLIC_KEY = 'PUB', + PRIVATE_KEY = 'PRIV'; + + const ACTION_ENCRYPT = 'ENC', + ACTION_DECRYPT = 'DEC'; + + const DEFAULT_AES_MODE = 'CFB'; + + protected $key; + protected $subject; + protected $action; + protected $aesMode; + + /** + * Constructor + * @param CryptoKey $key Key to be used for encryption/decryption + */ + public function __construct(CryptoKey $key, $aesMode = self::DEFAULT_AES_MODE) + { + + $this->key = $key; + $this->aesMode = $aesMode; + } + + /** + * Returns encrypted data if using symmetric key, otherwise returns $this for chaining to with() method + * @param $plainText Data/string to encrypt + * @return $this (with private key) or raw encrypted data (with symmetric key) + */ + public function encrypt($plainText) + { + + if ($this->key->getType() == CryptoKey::TYPE_SYMMETRIC) { + $bitLen = $this->key->getBitLength(); + $method = 'AES-'. $bitLen .'-'. $this->aesMode; + $encryptionKey = $this->key->getKey(CryptoKey::FORMAT_RAW); + $ivLen = openssl_cipher_iv_length($method); + $iv = openssl_random_pseudo_bytes($ivLen); + + return $iv . openssl_encrypt($plainText, $method, $encryptionKey, OPENSSL_RAW_DATA, $iv); + } + + $this->action = self::ACTION_ENCRYPT; + $this->subject = $plainText; + + return $this; + } + + /** + * Returns decrypted data if using symmetric key, otherwise returns $this for chaining to with() method + * @param $subject Data/string to encrypt + * @return $this (with private key) or decrypted data (with symmetric key) + */ + public function decrypt($subject) + { + + if ($this->key->getType() == CryptoKey::TYPE_SYMMETRIC) { + $bitLen = $this->key->getBitLength(); + $method = 'AES-'. $bitLen .'-'. $this->aesMode; + $encryptionKey = $this->key->getKey(CryptoKey::FORMAT_RAW); + $ivLen = openssl_cipher_iv_length($method); + $iv = substr($subject, 0, $ivLen); + + return openssl_decrypt(substr($subject, $ivLen), $method, $encryptionKey, OPENSSL_RAW_DATA, $iv); + } + + $this->action = self::ACTION_DECRYPT; + $this->subject = $subject; + + return $this; + } + + /** + * Specifies what type of key to use with the action specified (only for private/public key cryptography) + * @param $keyType Specify the key type to use (see constants) + */ + public function with($keyType) + { + + if (empty($this->action) || empty($this->subject)) { + throw new \Exception('Must set an action with encrypt() or decrypt()'); + } + + if ($this->key->getType() != CryptoKey::TYPE_PRIVATE) { + throw new \Exception('Can only specify key to use when using asymmetric cryptogrpahy'); + } + + if ($keyType == self::PUBLIC_KEY) { + $publicKey = $this->key->getPublicKey(CryptoKey::FORMAT_RAW); + } elseif ($keyType == self::PRIVATE_KEY) { + $privateKey = $this->key->getKey(CryptoKey::FORMAT_RAW); + $privateKey = openssl_pkey_get_private($privateKey); + } + + $out = ''; + $success = false; + + if ($this->action == self::ACTION_ENCRYPT) { + if ($keyType == self::PUBLIC_KEY) { + $success = openssl_public_encrypt($this->subject, $out, $publicKey, OPENSSL_PKCS1_PADDING); + } elseif ($keyType == self::PRIVATE_KEY) { + $success = openssl_private_encrypt($this->subject, $out, $privateKey, OPENSSL_PKCS1_PADDING); + } + } elseif ($this->action == self::ACTION_DECRYPT) { + if ($keyType == self::PUBLIC_KEY) { + $success = openssl_public_decrypt($this->subject, $out, $publicKey, OPENSSL_PKCS1_PADDING); + } elseif ($keyType == self::PRIVATE_KEY) { + $success = openssl_private_decrypt($this->subject, $out, $privateKey, OPENSSL_PKCS1_PADDING); + } + } + + if (!$success) { + throw new \Exception('Failure to '. $this->action .' with '. $keyType .' key ('. openssl_error_string() .')'); + } + + return $out; + } +} diff --git a/src/CryptoKey.php b/src/CryptoKey.php new file mode 100644 index 0000000..71c4bb6 --- /dev/null +++ b/src/CryptoKey.php @@ -0,0 +1,190 @@ +setKey($key, $type); + } + } + + public function setKey($key, $type) + { + + if ($type == static::TYPE_SYMMETRIC) { + $len = strlen($key); + $this->bits = $len * 8; + $this->key = $key; + } elseif ($type == static::TYPE_PRIVATE) { + if (gettype($key) == 'resource') { + // Store the key resource + $this->key = $key; + } elseif (gettype($key) == 'string') { + // Import the given key + $this->key = openssl_pkey_get_private($key); + } else { + throw new \Exception('Private key given in unsupported format (must be PEM encoded string or a key resource)'); + } + + $details = openssl_pkey_get_details($this->key); + $this->bits = $details['bits']; + } else { + throw new \Exception('Invalid key type'); + } + + $this->type = $type; + } + + public function getType() + { + + return $this->type; + } + + public function getBitLength() + { + + return $this->bits; + } + + public function getKey($format = false) + { + + if (!$format) { + // Default formats for given type: + if ($this->type == static::TYPE_SYMMETRIC) { + $format = self::FORMAT_RAW; + } + + if ($this->type == static::TYPE_PRIVATE) { + $format = self::FORMAT_PEM; + } + } + + switch ($format) { + case self::FORMAT_RAW: + return $this->key; + + case self::FORMAT_BASE64: + if ($this->type != static::TYPE_SYMMETRIC) { + throw new \Exception('Invalid formatting for symmetric key'); + } + + return base64_encode($this->key); + + case self::FORMAT_HEX: + if ($this->type != static::TYPE_SYMMETRIC) { + throw new \Exception('Invalid formatting for symmetric key'); + } + + return unpack('H*', $this->key); + + case self::FORMAT_PEM: + if ($this->type != static::TYPE_PRIVATE) { + throw new \Exception('Invalid formatting for private key'); + } + + $out = ''; + openssl_pkey_export($this->key, $out, null, array( + 'config' => dirname(__DIR__) .'/config/openssl.cnf' + )); + return $out; + + default: + throw new \Exception('Invalid format'); + } + } + + public function getPublicKey($format = self::FORMAT_RAW) + { + if ($this->type != static::TYPE_PRIVATE) { + throw new \Exception('Public keys can only be generated from private key'); + } + + $pubKey = openssl_pkey_get_details($this->key); + $pubKey = $pubKey['key']; + + switch ($format) { + case self::FORMAT_RAW: + return openssl_pkey_get_public($pubKey); + + case self::FORMAT_PEM: + return $pubKey; + + default: + throw new \Exception('Invalid format'); + } + } + + public static function newSymmetricKey($bits = 256) + { + + if (!in_array($bits, array(128, 192, 256))) { + throw new \Exception('Invalid key length: '. $bits .' bits'); + } + + $keyclass = get_called_class(); + $ckey = new $keyclass; + + $success = false; + + $rand = openssl_random_pseudo_bytes($bits / 8, $success); + + if (!$success || empty($rand)) { + throw new \Exception('Could not generate a secure key'); + } + + $ckey->setKey($rand, static::TYPE_SYMMETRIC); + + return $ckey; + } + + public static function newPrivateKey($bits = 2048) + { + + if (!in_array($bits, array(1024, 2048, 3072))) { + throw new \Exception('Invalid key length: '. $bits .' bits'); + } + + $keyclass = get_called_class(); + $ckey = new $keyclass; + + $success = false; + + $key = openssl_pkey_new(array( + 'config' => dirname(__DIR__) .'/config/openssl.cnf', + 'private_key_bits' => $bits, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + 'encrypt_key' => false + )); + + if ($key === false) { + throw new \Exception('Could not generate a key ('. openssl_error_string() .')'); + } + + $ckey->setKey($key, static::TYPE_PRIVATE); + + return $ckey; + } +} diff --git a/src/CryptoPackage.php b/src/CryptoPackage.php new file mode 100644 index 0000000..fdfcb3f --- /dev/null +++ b/src/CryptoPackage.php @@ -0,0 +1,87 @@ +key->getType() == CryptoKey::TYPE_SYMMETRIC) { + // Symmetric? This is easy... + return parent::encrypt($subject); + } + + // If we're not encrypting with symmetric cryptography, we need to encrypt + // the data with symmetric and then encrypt the key with the chosen method + $secondKey = CryptoKey::newSymmetricKey(); + $secondCrypto = new Crypto($secondKey); + + $this->secondSubject = $secondCrypto->encrypt($subject); + $this->subject = $secondKey->getKey(CryptoKey::FORMAT_RAW); + $this->action = self::ACTION_ENCRYPT; + + return $this; + } + + /** + * Returns decrypted data if using symmetric key, otherwise returns $this for chaining to with() method + * @param $subject Data/string to encrypt + * @return $this (with private key) or decrypted data (with symmetric key) + */ + public function decrypt($subject) + { + + if ($this->key->getType() == CryptoKey::TYPE_SYMMETRIC) { + // Symmetric? This is easy... + return parent::decrypt($subject); + } + + $splitSubject = explode('|', $subject); + if (sizeof($splitSubject) != 2) { + throw new Exception('Not a valid ciphertext for CryptoPackage, try the Crypto class'); + } + + $this->action = self::ACTION_DECRYPT; + $this->subject = base64_decode($splitSubject[0]); + $this->secondSubject = base64_decode($splitSubject[1]); + + return $this; + } + + /** + * Specifies what type of key to use with the action specified (only for private/public key cryptography) + * @param $keyType Specify the key type to use (see constants) + */ + public function with($keyType) + { + + if ($this->action == self::ACTION_ENCRYPT) { + $encryptedKey = parent::with($keyType); + + return base64_encode($encryptedKey) .'|'. base64_encode($this->secondSubject); + } elseif ($this->action == self::ACTION_DECRYPT) { + $decryptedKey = parent::with($keyType); + + $secondKey = new CryptoKey($decryptedKey, CryptoKey::TYPE_SYMMETRIC); + $secondCrypto = new Crypto($secondKey); + + $plaintext = $secondCrypto->decrypt($this->secondSubject); + + return unserialize($plaintext); + } + } +} diff --git a/src/DatabaseMigration.php b/src/DatabaseMigration.php new file mode 100644 index 0000000..96934c2 --- /dev/null +++ b/src/DatabaseMigration.php @@ -0,0 +1,40 @@ += 0) { + return false; + } + $comp = version_compare($targetVersion, static::$version); + return ($comp >= 0); + } + + public static function checkVersionDown($currentVersion, $targetVersion) + { + $comp = version_compare($currentVersion, static::$version); + if ($comp < 0) { + return false; + } + $comp = version_compare($targetVersion, static::$version); + return ($comp == -1); + } +} diff --git a/src/DatabaseMigrationManager.php b/src/DatabaseMigrationManager.php new file mode 100644 index 0000000..2d7db51 --- /dev/null +++ b/src/DatabaseMigrationManager.php @@ -0,0 +1,139 @@ +current = $currentVersion; + $this->target = $targetVersion; + $this->direction = $direction; + } + + /** + * submitMigration() + * @param string $className The name of the migration class to be checked for inclusion and execution + */ + public function submitMigration($className) + { + + if ($this->direction == 'up') { + if ($className::checkVersionUp($this->current, $this->target)) { + $this->addMigration(new $className); + } + } elseif ($this->direction == 'down') { + if ($className::checkVersionDown($this->current, $this->target)) { + $this->addMigration(new $className); + } + } + } + + /** + * count() + * @return int Number of migrations included for execution + */ + public function count() + { + return sizeof($this->migrations); + } + + /** + * addMigration() + * @param DatabaseMigration $mig Add a migration for execution + */ + public function addMigration(DatabaseMigration $mig) + { + + $this->migrations[] = $mig; + } + + /** + * sortMigrations() + */ + public function sortMigrations() + { + + $dmm = $this; + + usort($this->migrations, function ($a, $b) use ($dmm) { + + $av = $a->getVersion(); + $bv = $b->getVersion(); + + if ($dmm->direction == 'up') { + return version_compare($av, $bv); + } elseif ($dmm->direction == 'down') { + return version_compare($bv, $av); + } + }); + } + + /** + * setPreExecCallback() + * @param function $function Set a callback function to run before each individual migration + */ + public function setPreExecCallback($function) + { + $this->preCallback = $function; + } + + /** + * setPostExecCallback() + * @param function $function Set a callback function to run after each individual migration + */ + public function setPostExecCallback($function) + { + $this->postCallback = $function; + } + + /** + * executeMigrations() + */ + public function executeMigrations() + { + + $count = $this->count(); + + foreach ($this->migrations as $i => $mig) { + if ($this->preCallback) { + $callback = $this->preCallback; + $callback($mig->getVersion(), $i, $count); + } + + if ($this->direction == 'up') { + $mig->up(); + } elseif ($this->direction == 'down') { + $mig->down(); + } + + if ($this->postCallback) { + $callback = $this->postCallback; + $callback($mig->getVersion(), $i, $count); + } + } + } +} diff --git a/src/DatabaseObject.php b/src/DatabaseObject.php new file mode 100644 index 0000000..e5ed20c --- /dev/null +++ b/src/DatabaseObject.php @@ -0,0 +1,358 @@ +constructed = true; + + // If it doesn't come pre-loaded, it's new + // (Pre-loading made possible by PDOStatement::fetchObject) + if (!sizeof($this->_data)) { + $this->_new = true; + } + + if (is_array(static::$colsDateTime)) { + if (!static::$defaultTz) { + static::$defaultTz = new \DateTimeZone('UTC'); + } + + foreach (static::$colsDateTime as $aCol) { + // Change all pre-loaded date/time data into DateTime object + if (!empty($this->_data[$aCol]) && !is_object($this->_data[$aCol])) { + $this->_data[$aCol] = new \DateTime($this->_data[$aCol], static::$defaultTz); + } + } + } + + $this->_dirty = array(); // Reset in case pre-loaded + + if (is_array($data)) { + foreach ($data as $name => $value) { + $this->__set($name, $value); + } + } + } + + public static function setQueryObject(Query $query) + { + // Used for testing + static::$query = $query; + } + + public function save() + { + + if (!$this->_new && !sizeof($this->_dirty)) { + return; + } + if ($this->_readOnly) { + throw new \Exception('Cannot save a read-only object.'); + } + + $saveData = $this->_data; + + foreach ($saveData as $var => $val) { + // JIT encoding: Serialize/JSON-encode just before saving + if (!is_null($val) && in_array($var, static::$colsSerialize)) { + $saveData[$var] = serialize($val); + } elseif (!is_null($val) && in_array($var, static::$colsJson)) { + $saveData[$var] = json_encode($val); + } + } + + if (static::$query) { + $q = static::$query; + } else { + $q = new Query(); + } + + if ($this->_new) { + $q->insert(static::$tableName, $saveData); + + // If new and single primary key, set data id from autoincrement id + if (static::$primaryKey && !is_array(static::$primaryKey)) { + $this->_data[static::$primaryKey] = $q->getNewId(); + } + } else { + if (!static::$primaryKey) { + throw new \Exception('Cannot perform update without a primary key.'); + } + + if (is_array(static::$primaryKey)) { + foreach (static::$primaryKey as $aKey) { + if (!isset($saveData[$aKey])) { + throw new \Exception('Cannot perform update without all primary keys set.'); + } + } + } elseif (!isset($saveData[static::$primaryKey])) { + throw new \Exception('Cannot perform update without primary key set.'); + } + + $updateData = array(); + + foreach ($this->_dirty as $key) { + $updateData[$key] = $saveData[$key]; + } + + $wheres = array(); + + if (is_array(static::$primaryKey)) { + foreach (static::$primaryKey as $aKey) { + if (isset($updateData[$aKey])) { + throw new \Exception('Cannot perform update on primary key (it was marked dirty).'); + } + + $wheres[] = $saveData[$aKey]; + } + } else { + if (isset($updateData[static::$primaryKey])) { + throw new \Exception('Cannot perform update on primary key (it was marked dirty).'); + } + + $wheres = $saveData[static::$primaryKey]; + } + + list($whereSql, $whereBinder) = $this->getWhere($wheres); + + $q->update(static::$tableName, $updateData, $whereSql, $whereBinder); + } + + $this->_new = false; + $this->_dirty = array(); + } + + protected static function getWhere($valueArray) + { + + $whereBinder = array(); + + if (is_array($valueArray)) { + $whereSql = "WHERE"; + + // If not associative array, set the primary keys as the column names + $hasStringKeys = count(array_filter(array_keys($valueArray), 'is_string')) > 0; + if (!$hasStringKeys) { + $valueArray = array_merge(static::$primaryKey, $valueArray); + } + + foreach ($valueArray as $aKey => $aVal) { + $binderName = ':binder_'.mt_rand(0, 1000000); + $whereSql .= " `". $aKey ."`=". $binderName ." AND"; + $whereBinder[$binderName] = $aVal; + } + + $whereSql = substr($whereSql, 0, -3). "LIMIT 1"; + } else { + if (is_array(static::$primaryKey)) { + throw new \Exception('The valueArray must be an array if the primary key is an array.'); + } + + $binderName = ':binder_'.mt_rand(0, 1000000); + $whereSql = "WHERE `". static::$primaryKey ."`=". $binderName ." LIMIT 1"; + $whereBinder[$binderName] = $valueArray; + } + + return array($whereSql, $whereBinder); + } + + public function setNew() + { + $this->_new = true; + $this->_dirty = array(); + } + + public function setNotNew() + { + $this->_new = false; + } + + public function setReadOnly() + { + $this->_new = false; + $this->_dirty = array(); + $this->_readOnly = true; + } + + public function isNew() + { + return $this->_new; + } + + public function getDirty() + { + return $this->_dirty; + } + + public function __get($var) + { + + if (isset($this->_data[$var])) { + return $this->_data[$var]; + } + + return null; + } + + public function __isset($var) + { + + return isset($this->_data[$var]); + } + + public function __set($var, $val) + { + + // If pre-construct loading + if (!$this->constructed) { + // Take out of deep-freeze (as stored in DB) + if (!is_null($val) && in_array($var, static::$colsSerialize)) { + $val = unserialize($val); + } elseif (!is_null($val) && in_array($var, static::$colsJson)) { + $val = json_decode($val, true); + } + + $this->_data[$var] = $val; + return; + } + + if (isset($this->_data[$var]) && ($this->_data[$var] === $val)) { + return; + } + + $this->_data[$var] = $val; + + if (!$this->_new && !in_array($var, $this->_dirty)) { + $this->_dirty[] = $var; + } + } + + public function __clone() + { + + if (static::$primaryKey) { + unset($this->_data[static::$primaryKey]); + } + + $this->_new = true; + $this->_dirty = array(); + } + + public function delete() + { + + if ($this->_readOnly) { + throw new \Exception('Cannot delete a read-only object.'); + } + + if (!static::$primaryKey) { + throw new \Exception('Cannot perform delete without a primary key.'); + } + + $this->_dirty = array(); + + if ($this->_new) { + return; + } + + list($whereSql, $whereBinder) = $this->getWhere($this->_data); + + $sql = "DELETE FROM `". static::$tableName ."` ". $whereSql; + + if (static::$query) { + $q = static::$query; + } else { + $q = new Query(); + } + + $q->query($sql, $whereBinder); + + $this->_data = array(); + } + + public static function create($data) + { + + $class = get_called_class(); + + $do = new $class($data); + $do->save(); + + return $do; + } + + public static function find($searchValue) + { + + if (!static::$primaryKey) { + throw new \Exception('Cannot perform find without a primary key.'); + } + + return static::findBy(static::$primaryKey, $searchValue, 1); + } + + public static function findBy($column, $searchValue, $limit = false) + { + + if (is_array($searchValue) != is_array($column)) { + throw new \Exception('If column is an array, must search by array and vice versa.'); + } + + if (is_array($searchValue) && (sizeof($searchValue) != sizeof($column))) { + throw new \Exception('Number of elements in search array must match column array.'); + } + + if (!is_array($searchValue)) { + $searchValue = array($column => $searchValue); + } else { + $searchValue = array_combine($column, $searchValue); + } + + list($whereSql, $whereBinder) = static::getWhere($searchValue); + + $sql = "SELECT * FROM `". static::$tableName ."` ". $whereSql; + + if (!empty($limit)) { + $sql .= ' LIMIT '. $limit; + } + + if (static::$query) { + $q = static::$query; + } else { + $q = new Query(); + } + + $q->query($sql, $whereBinder); + + if ($limit === 1) { + return $q->getObject(get_called_class()); + } else { + return new QueryIterator($q, get_called_class()); + } + } +} diff --git a/src/DatabaseSessionHandler.php b/src/DatabaseSessionHandler.php new file mode 100644 index 0000000..f30f4ee --- /dev/null +++ b/src/DatabaseSessionHandler.php @@ -0,0 +1,79 @@ + 'Sessions', // Table where it is all stored + 'colKey' => 'key', // The key column for the unique session ID + 'colData' => 'data', // The data column for storing session data + 'colExp' => 'expires', // The datetime column for expiration date + 'expSeconds' => 14400 // 4 hours + ); + + public function open($savePath, $sessionName) + { + return true; + } + + public function close() + { + return true; + } + + public function read($id) + { + + $sql = "SELECT `". self::$config['colData'] ."` FROM `". self::$config['tableName'] ."` WHERE `". self::$config['colKey'] ."`=:session_id AND `". self::$config['colExp'] ."` > NOW() LIMIT 1"; + + $q = new Query($sql, array( + ':session_id' => $id + )); + + if (!$data = $q->getRow()) { + return false; + } + return $data[ self::$config['colData'] ]; + } + + public function write($id, $data) + { + + $q = new Query(); + + $q->replace(self::$config['tableName'], array( + self::$config['colKey'] => $id, + self::$config['colData'] => $data, + self::$config['colExp'] => Query::SQL('DATE_ADD(NOW(), INTERVAL '. self::$config['expSeconds'] .' SECOND)') + )); + + return true; + } + + public function destroy($id) + { + + $sql = "DELETE FROM `". self::$config['tableName'] ."` WHERE `". self::$config['colKey'] ."`=:session_id LIMIT 1"; + + new Query($sql, array( + ':session_id' => $id + )); + + return true; + } + + public function gc($maxlifetime) + { + // $maxlifetime not implemented. + + $sql = "DELETE FROM `". self::$config['tableName'] ."` WHERE `". self::$config['colExp'] ."` <= NOW()"; + + new Query($sql, array( + ':session_id' => $id + )); + + return true; + } +} diff --git a/src/HttpRequest.php b/src/HttpRequest.php new file mode 100644 index 0000000..5ad64f6 --- /dev/null +++ b/src/HttpRequest.php @@ -0,0 +1,209 @@ + array( + 'connect' => 3, + 'response' => 10 + ), + 'userAgent' => false, + 'caInfo' => false, + 'followRedirect' => false + ); + + public function __construct() + { + + if (function_exists('curl_init')) { + $this->handle = curl_init(); + } else { + throw new \Exception('cURL required.'); + } + + if (!$this->handle) { + throw new \Exception('Could not initiate cURL.'); + } + } + + public static function config($config) + { + + self::$config = array_merge(self::$config, $config); + } + + public function enableCookies($file = false, $delOnDestruct = true) + { + + $this->cookies = true; + + if ($file && strlen($file)) { + $this->cookieFile = $file; + } else { + $this->cookieFile = tempnam(".", "CURL-COOKIE-"); + } + + $this->delCookieFile = $delOnDestruct; + } + + public function close() + { + + if (!$this->handle) { + return; + } + + curl_close($this->handle); + + $this->handle = false; + } + + public function clearCookies() + { + + if ($this->cookies && $this->delCookieFile && file_exists($this->cookieFile)) { + unlink($this->cookieFile); + } + } + + public function customHeaders($headerArray) + { + + curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headerArray); + } + + public function setBasicAuth($user, $pass = null) + { + + curl_setopt($this->handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + curl_setopt($this->handle, CURLOPT_USERPWD, (!empty($pass) ? $user .':'. $pass : $user)); // Should be 'username:password' + } + + // Three executing methods: + + public function get($url) + { + + curl_setopt($this->handle, CURLOPT_POST, 0); // Added to clear past values + curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, 'GET'); // Added to clear past values + + return $this->execute($url); + } + + public function post($url, $postData) + { + + if (is_array($postData)) { + $postData = http_build_query($postData); + } + + curl_setopt($this->handle, CURLOPT_POST, 1); // Perform a POST + curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, 'POST'); // Added to clear past values + curl_setopt($this->handle, CURLOPT_POSTFIELDS, $postData); + + return $this->execute($url); + } + + public function put($url, $putData) + { + + if (is_array($putData)) { + $putData = http_build_query($putData); + } + + curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, 'PUT'); + curl_setopt($this->handle, CURLOPT_POSTFIELDS, $putData); + + return $this->execute($url); + } + + public function delete($url) + { + + curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, 'DELETE'); + + return $this->execute($url); + } + + public function other($url, $type) + { + // For example, HEAD request + curl_setopt($this->handle, CURLOPT_POST, 0); // Added to clear past values + curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $type); + + return $this->execute($url); + } + + private function execute($url) + { + + curl_setopt($this->handle, CURLOPT_URL, $url); // Set the URL + + if ($this->userAgent) { + curl_setopt($this->handle, CURLOPT_USERAGENT, $this->userAgent); + } elseif (self::$config['userAgent']) { + curl_setopt($this->handle, CURLOPT_USERAGENT, self::$config['userAgent']); + } + + if (self::$config['caInfo']) { + curl_setopt($this->handle, CURLOPT_CAINFO, self::$config['caInfo']); // Name of the file to verify the server's cert against + } + if (self::$config['caInfo']) { + curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 1); // Verify the SSL certificate + } + if (self::$config['followRedirect']) { + curl_setopt($this->handle, CURLOPT_FOLLOWLOCATION, 1); // Follow redirects + } + + curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1); // If not set, curl prints output to the browser + curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, self::$config['timeout']['connect']); // How long to wait for a connection + curl_setopt($this->handle, CURLOPT_TIMEOUT, self::$config['timeout']['response']); // How long to wait for a response + + if ($this->cookies) { + curl_setopt($this->handle, CURLOPT_COOKIEJAR, $this->cookieFile); + curl_setopt($this->handle, CURLOPT_COOKIEFILE, $this->cookieFile); + } + + $dataBack = curl_exec($this->handle); + + $this->respInfo = curl_getinfo($this->handle); + $this->respCode = curl_getinfo($this->handle, CURLINFO_HTTP_CODE); + + return $dataBack; + } + + public function respCode() + { + + return $this->respCode; + } + + public function respInfo() + { + + return $this->respInfo; + } + + public function __destruct() + { + + $this->close(); + $this->clearCookies(); + } +} diff --git a/src/InitExtendable.php b/src/InitExtendable.php new file mode 100644 index 0000000..337fa7d --- /dev/null +++ b/src/InitExtendable.php @@ -0,0 +1,18 @@ +newInstanceArgs($args); + } +} diff --git a/src/InputValidator.php b/src/InputValidator.php new file mode 100644 index 0000000..45e21b4 --- /dev/null +++ b/src/InputValidator.php @@ -0,0 +1,389 @@ +type = $type; + $this->value = filter_var(trim($value), FILTER_UNSAFE_RAW, array( + 'flags' => FILTER_FLAG_STRIP_LOW + )); + $this->region = $region; + $this->required = true; + } + + public function setRequired($required = true) + { + $this->required = $required; + } + + public function isValid() + { + return self::validate($this->type, $this->value, $this->region, $this->required); + } + + public function getNormalized($returnType = self::TYPE_DEFAULT) + { + + switch ($this->type) { + case self::NAME: + case self::ORG_NAME: + case self::CITY: + return Strings::nameFix($this->value); + + case self::ADDRESS: + return Strings::addressFix($this->value); + + case self::STATE: + $state = $this->value; + + if (in_array($this->region, array('US', 'CA', 'MX'))) { + return substr(mb_strtoupper(trim($state)), 0, 2); + } + + if (strlen($state) <= 3) { + return mb_strtoupper($state); + } + + return Strings::ucwords($state); + + case self::ZIP: + $zip = mb_strtoupper($this->value); + if ($this->region == 'CA') { + $zip = substr($zip, 0, 3) .' '. substr($zip, -3); + } + + return $zip; + + case self::COUNTRY: + return substr(mb_strtoupper($this->value), 0, 2); + + case self::PHONE: + $phone = preg_replace('/\s{2,}/', ' ', trim($this->value)); + + if (strlen($phone) < 5) { + return $phone; + } + + if (in_array($this->region, array('US', 'CA'))) { + $phone = preg_replace('/[^0-9]+/', '', $phone); + + if ((strlen($phone) > 10) && (substr($phone, 0, 1) == '1')) { + $phone = substr($phone, 1); + } + + $phone = "(". substr($phone, 0, 3) .") ". substr($phone, 3, 3) ."-". substr($phone, 6, 4) . trim(' '. substr($phone, 10)); + } else { + $phone = preg_replace('/[^0-9\-\+\(\) ]+/', '', $phone); + } + + return $phone; + + case self::EMAIL: + return mb_strtolower($this->value); + + case self::CREDIT_ACCT: + $num = preg_replace('/\D+/', '', $this->value); + + if ($returnType == self::TYPE_GATEWAY) { + return $num; + } + + return self::lastfour($num, 'x'); + + case self::CREDIT_EXP: + $exp = $this->value; + + if (preg_match('/^\d{2}[\/-]?\d{2}$/', $exp)) { + $mo = substr($exp, 0, 2); + $yr = substr($exp, -2); + $exp = strtotime($mo.'/01/20'.$yr); + } elseif (preg_match('/^\d{1}[\/-]?\d{2}$/', $exp)) { + $mo = '0'.substr($exp, 0, 1); + $yr = substr($exp, -2); + $exp = strtotime($mo.'/01/20'.$yr); + } else { + // Shot in the dark + $exp = strtotime($exp); + } + + if ($returnType == self::TYPE_GATEWAY) { + return date('my', $exp); + } + + if ($returnType == self::TYPE_DB) { + return '20'. $yr .'-'. $mo .'-'. date('t', $exp); + } + + return date('m/y', $exp); + + case self::CREDIT_CVV: + $num = preg_replace('/\D+/', '', $this->value); + + if ($returnType == self::TYPE_GATEWAY) { + return $num; + } + + return str_pad('', strlen($num), 'x'); + + case self::ACH_ROUT: + $num = preg_replace('/\D+/', '', $this->value); + + return $num; + + case self::ACH_ACCT: + $num = preg_replace('/\D+/', '', $this->value); + + if ($returnType == self::TYPE_GATEWAY) { + return $num; + } + + return self::lastfour($num, 'x'); + + case self::ACH_TYPE: // @codingStandardsIgnoreLine (Code sniffer bug) + $type = mb_strtoupper(substr($this->value, 0, 1)); + + if (in_array($returnType, array(self::TYPE_DB, self::TYPE_GATEWAY))) { + return $type; + } + + if ($type == 'C') { + return 'Checking'; + } elseif ($type == 'S') { + return 'Savings'; + } else { + return ''; + } + + case self::URL: + $url = parse_url($this->value); + if (empty($url['scheme'])) { + $url = parse_url('http://'. $this->value); // Add a scheme for proper parsing + } + + if (empty($url['path'])) { + $url['path'] = '/'; + } + + return $url['scheme'] .'://'. strtolower($url['host']) . (!empty($url['port']) ? ':'. $url['port'] : '') . $url['path'] . (!empty($url['query']) ? '?'. $url['query'] : ''); + + default: + return false; + } + } + + public static function validate($type, $value, $region = false, $required = true) + { + if (empty($value)) { + return !$required; + } + + switch ($type) { + case self::NAME: + case self::CITY: + if (preg_match('/^\d/', $value)) { + return false; + } + + return preg_match('/^\p{L}[\p{L} \-\'\+&\.]*[\p{L}\.]$/u', $value); + + case self::ORG_NAME: + return preg_match('/^[\p{L}\d].*[\p{L}\d\.\)\]\!\?]$/u', $value); + + case self::ADDRESS: + // Must contain at least one digit and 2 letters + return preg_match('/\d+/', $value) && preg_match('/[A-Za-z]{2,}/', $value); + + case self::STATE: // @codingStandardsIgnoreLine (Code sniffer bug) + if (in_array($region, array('US', 'CA', 'MX'))) { + return ( States::check($region, mb_strtoupper($value)) !== false ); + } else { + return preg_match('/^(.+)$/', $value); + } + + case self::ZIP: // @codingStandardsIgnoreLine (Code sniffer bug) + if ($region == 'US') { + return preg_match('/^\d{5}(\-\d{4})?$/', $value); + } elseif ($region == 'CA') { + return preg_match('/^\D\d\D(\s)?\d\D\d$/', $value); + } elseif ($region == 'MX') { + return preg_match('/^\d{5}$/', $value); + } else { + return preg_match('/^(.+)$/', $value); + } + + case self::COUNTRY: + return Countries::check($value); + + case self::PHONE: // @codingStandardsIgnoreLine (Code sniffer bug) + $temp = preg_replace('/\D+/', '', $value); + + if (in_array($region, array('US', 'CA'))) { + return preg_match('/^(1)?[2-9]\d{9}$/', $temp); + } else { + return preg_match('/^\d{4,}$/', $temp); + } + + case self::EMAIL: + return (filter_var($value, FILTER_VALIDATE_EMAIL) !== false); + + case self::CREDIT_ACCT: + $value = preg_replace('/\s+/', '', $value); + + if (!preg_match('/^\d{15,16}$/', $value)) { + return false; + } + if (!preg_match('/^[3-6]/', $value)) { + return false; + } + + return self::checkLuhn($value); + + case self::CREDIT_EXP: + $value = preg_replace_callback('/^(\d{1,2})[\/\-]?(\d{2})$/', function ($matches) { + if (strlen($matches[1]) == 1) { + $matches[1] = '0'.$matches[1]; + } + return $matches[1] . $matches[2]; + }, $value); + + $mo = intval(substr($value, 0, 2)); + $yr = intval(substr($value, -2)); + + if (($mo < 1) || ($mo > 12)) { + return false; + } + if (($yr < intval(date('y'))) || ($yr > (intval(date('y')) + 15))) { + return false; + } + + return preg_match('/^\d{4}$/', $value); + + case self::CREDIT_CVV: + return preg_match('/^\d{3,4}$/', $value); + + case self::ACH_ROUT: + if (!preg_match('/^\d{9}$/', $value)) { + return false; + } + + return self::checkChecksum($value); + + case self::ACH_ACCT: + $value = preg_replace('/[\s\-]+/', '', $value); + + return preg_match('/^\d{4,25}$/', $value); + + case self::ACH_TYPE: + return preg_match('/^(C|S)$/i', substr($value, 0, 1)); + + case self::URL: + $url = parse_url($value); + + if (!$url) { + return false; + } + + if (empty($url['scheme'])) { + $url = parse_url('http://'. $value); // Add a scheme for proper parsing + } + + if (strlen($url['scheme']) && !in_array($url['scheme'], array('http', 'https'))) { + return false; + } + if (!isset($url['host']) && isset($url['path'])) { + $url['host'] = $url['path']; + $url['path'] = ''; + } + if (!preg_match('/^([a-z0-9\-]+\.)+([a-z]{2,})$/i', $url['host'])) { + return false; + } + + return true; + + default: + return false; + } + } + + public static function lastfour($number, $padChar = 'x') + { + + $len = strlen($number); + if ($len == 0) { + return ''; + } + + $lastLen = intval(floor($len / 2)); + if ($lastLen > 4) { + $lastLen = 4; + } + $lastFour = substr($number, -$lastLen, $lastLen); + $lastFour = str_pad($lastFour, $len, $padChar, STR_PAD_LEFT); + + return $lastFour; + } + + public static function checkChecksum($number) + { + + settype($number, 'string'); + + $sum = 3 * ( intval(substr($number, 0, 1)) + intval(substr($number, 3, 1)) + intval(substr($number, 6, 1)) ); + $sum += 7 * ( intval(substr($number, 1, 1)) + intval(substr($number, 4, 1)) + intval(substr($number, 7, 1)) ); + $sum += intval(substr($number, 2, 1)) + intval(substr($number, 5, 1)) + intval(substr($number, 8, 1)); + + return (($sum % 10) === 0); + } + + public static function checkLuhn($number) + { + + settype($number, 'string'); + + $sumTable = array( + array(0,1,2,3,4,5,6,7,8,9), + array(0,2,4,6,8,1,3,5,7,9) + ); + + $sum = 0; + $flip = 0; + + for ($i = strlen($number) - 1; $i >= 0; $i--) { + $sum += $sumTable[ $flip++ & 0x1 ][ $number[$i] ]; + } + + return (($sum % 10) === 0); + } +} diff --git a/src/InvalidJsonException.php b/src/InvalidJsonException.php new file mode 100644 index 0000000..eb250aa --- /dev/null +++ b/src/InvalidJsonException.php @@ -0,0 +1,8 @@ +query($sql, $binders); + } + + public static function connect() + { + + $reg = Registry::get('database'); + + try { + if ($reg) { + if ($reg['type'] == 'mysql') { + if (!isset($reg['port'])) { + $reg['port'] = 3306; + } + + self::$pdo = new \PDO('mysql:host='. $reg['host'] .';port='. $reg['port'] .';dbname='. $reg['name'], $reg['user'], $reg['pass']); + } else { + self::$pdo = new \PDO($reg['dsn']); + } + } else { + if (!isset($_SERVER['DB_PORT'])) { + $_SERVER['DB_PORT'] = 3306; + } + + self::$pdo = new \PDO('mysql:host='. $_SERVER['DB_HOST'] .';port='. $_SERVER['DB_PORT'] .';dbname='. $_SERVER['DB_NAME'], $_SERVER['DB_USER'], $_SERVER['DB_PASS']); + } + } catch (\Exception $e) { + self::$errorCount++; + throw $e; + } + + if (!self::$pdo) { + self::$errorCount++; + throw new \Exception('Could not connect to database.'); + } + + return self::$pdo; + } + + public function query($sql, $binders = array()) + { + + if (self::$errorCount > 10) { + trigger_error('QUERY ERROR LIMIT REACHED', E_USER_ERROR); + exit(1); + } + + if (is_string($sql)) { + $this->cleanBinders($binders, $sql); + } + + $this->convertBinderValues($binders); + + if (!self::$pdo) { + static::connect(); + } + + // $sql can be a PDOStatement or a SQL string + if (is_string($sql)) { + $this->sql = self::$pdo->prepare($sql); + } elseif ($sql instanceof \PDOStatement) { + $this->sql = $sql; + } else { + self::$errorCount++; + throw new \Exception('Invalid parameter supplied to query method.'); + } + + foreach ($binders as $name => $value) { + // Fixes issue with innodb not interpreting false correctly (converts to empty string) + if (gettype($value) == 'boolean') { + $binders[$name] = intval($value); + } + } + + if (!$this->sql instanceof \PDOStatement) { + $errorInfo = self::$pdo->errorInfo(); + throw new \Exception('Could not instantiate PDOStatement object ('. $errorInfo[2] .')'); + } + + $this->res = $this->sql->execute($binders); + + if (!$this->res) { + self::$errorCount++; + throw new \Exception('Database error: '. $this->getErrorCode() .', '. $this->getError() .', '. $this->sql->queryString); + } + + return $this->res; + } + + public function cleanBinders(&$binder, $sql) + { + foreach ($binder as $name => $value) { + if (strpos($sql, $name) === false) { + unset($binder[$name]); + } + } + } + + public function removeNulls(&$binder) + { + // Make DB updates compatible with badly-designed code bases and DB schemas (where NULL is not valid) + foreach ($binder as $name => $value) { + if (is_null($value)) { + $binder[$name] = ''; + } + } + } + + public function convertBinderValues(&$binder) + { + foreach ($binder as $name => $value) { + // If value is a 2-element array with the first value + // having one of the following values, the second is assumed + // to need special attention. ("SQL" is handeled only in + // splitArray for insert/update) + if (is_array($value) && (sizeof($value) == 2)) { + switch (strtoupper($value[0])) { + case 'SERIALIZE': + $value = serialize($value[1]); + break; + case 'JSON': + $value = json_encode($value[1]); + break; + } + } + + if (is_object($value) && is_a($value, 'DateTime')) { + $date = clone $value; + if (static::$defaultTz) { + $date->setTimezone(static::$defaultTz); + } + + $value = $date->format('Y-m-d H:i:s'); + } + + if (is_array($value) || is_object($value)) { + $value = serialize($value); + } + + $binder[$name] = $value; + } + } + + public function getRes() + { + return $this->res; + } + + public function getAll() + { + if (!$this->res) { + return false; + } + return $this->sql->fetchAll(\PDO::FETCH_ASSOC); + } + + public function getRow() + { + if (!$this->res) { + return false; + } + return $this->sql->fetch(\PDO::FETCH_ASSOC); + } + + public function getObject($className) + { + if (!$this->res) { + return false; + } + return $this->sql->fetchObject($className); + } + + public function getNewId() + { + return self::$pdo->lastInsertId(); + } + + public function getAffected() + { + return $this->sql->rowCount(); + } + + public function getNumRows() + { + // May not always return the correct number of rows + // See note at http://php.net/manual/en/pdostatement.rowcount.php + return $this->sql->rowCount(); + } + + public function getError() + { + $e = self::$pdo->errorInfo(); + if ($e[0] == '00000') { + $e = $this->sql->errorInfo(); + } + return $e[2]; // Driver specific error message. + } + + public function getErrorCode() + { + $e = self::$pdo->errorInfo(); + if ($e[0] == '00000') { + $e = $this->sql->errorInfo(); + } + return $e[1]; // Driver specific error code. + } + + public function getQuery() + { + return $this->sql->queryString; + } + + public function success() + { + return $this->res; + } + + public function insert($table, $array) + { + // Preform an insert on the table + // Enter an associative array for $array with column names as keys + + if (!self::$pdo) { + static::connect(); + } + + list($statementArray, $binderArray) = self::splitArray($array); + + $this->sql = self::$pdo->prepare("INSERT INTO `". $table ."` ". self::toSQL('INSERT', $statementArray)); + + return $this->query($this->sql, $binderArray); + } + + public function replace($table, $array) + { + // Preform an replace on the table + // Enter an associative array for $array with column names as keys + + if (!self::$pdo) { + static::connect(); + } + + list($statementArray, $binderArray) = self::splitArray($array); + + $this->sql = self::$pdo->prepare("REPLACE INTO `". $table ."` ". self::toSQL('REPLACE', $statementArray)); + + return $this->query($this->sql, $binderArray); + } + + public function update($table, $array, $whereSql, $whereBinder = array()) + { + // Preform an update on the table + // Enter an associative array for $array with column names as keys + + if (!self::$pdo) { + static::connect(); + } + + list($statementArray, $binderArray) = self::splitArray($array); + + // Look for binder conflicts + foreach ($whereBinder as $placeholder => $value) { + if (isset($binderArray[$placeholder])) { + // Binder conflict! + $newPlaceholder = $placeholder.'_'.mt_rand(100, 10000); + $whereBinder[$newPlaceholder] = $value; + unset($whereBinder[$placeholder]); + + $whereSql = preg_replace('/'.preg_quote($placeholder).'\b/', $newPlaceholder, $whereSql); + } + } + + $this->sql = self::$pdo->prepare("UPDATE `". $table ."` SET ". self::toSQL('UPDATE', $statementArray) ." WHERE ". preg_replace('/^\s?WHERE\s/', '', $whereSql)); + + return $this->query($this->sql, array_merge($binderArray, $whereBinder)); + } + + public static function splitArray($arrayIn) + { + + if (!is_array($arrayIn)) { + throw new \Exception('Parameter is not an array.'); + } + + $statement = array(); + $binder = array(); + + foreach ($arrayIn as $key => $value) { + // Check for anything that should be SQL and put in statement array + if (is_array($value) && (sizeof($value) == 2)) { + if (strtoupper($value[0]) == 'SQL') { + if (!is_string($value[1])) { + continue; + } + $statement[$key] = $value[1]; + continue; + } + } + + $crossKey = ':'.preg_replace('/[^A-Za-z0-9]+/', '_', $key); + + // Key is already used, add random characters to end + if (isset($binder[$crossKey])) { + $crossKey .= '_'. mt_rand(1000, 10000); + } + + $statement[$key] = $crossKey; + + $binder[$crossKey] = $value; + } + + return array($statement, $binder); + } + + public static function toSQL($verb, $assocArray) + { + // $assocArray should be an array of 'raw' items (not yet escaped for database) + + $verb = strtoupper($verb); + + if (($verb == 'INSERT') || ($verb == 'REPLACE')) { + $sql1 = ''; + $sql2 = ''; + + foreach ($assocArray as $key => $value) { + $sql1 .= ', `'. str_replace('`', '', $key) .'`'; + $sql2 .= ", ". $value; + } + + return '('. substr($sql1, 2) . ') VALUES ('. substr($sql2, 2) .')'; + } elseif ($verb == 'UPDATE') { + $sql = ''; + + foreach ($assocArray as $key => $value) { + $sql .= ', `'. str_replace('`', '', $key) .'`='. $value; + } + + return substr($sql, 2); + } else { + throw new \Exception("Invalid verb for toSQL();"); + } + } + + public static function escapeLike($sql) + { + return preg_replace('/([%_])/', '\\\\\\1', $sql); + } + + public static function SQL($sql) + { + return array('SQL', $sql); + } +} diff --git a/src/QueryIterator.php b/src/QueryIterator.php new file mode 100644 index 0000000..9728a5e --- /dev/null +++ b/src/QueryIterator.php @@ -0,0 +1,86 @@ +query = $query; + $this->className = $className; + } + + /** + * Return the current row + * @return Array An associative array with the current row's value + */ + public function current() + { + + return $this->value; + } + + /** + * Return the index of the current row + * @return Int The index of the current row + */ + public function key() + { + + return $this->index; + } + + /** + * Move forward to next row + * @return Object $this For method chaining + */ + public function next() + { + + $this->index++; + + if ($this->className) { + $this->value = $this->query->getObject($this->className); + } else { + $this->value = $this->query->getRow(); + } + + return $this; + } + + /** + * Rewind the Iterator to the first row (not possible with PDO query results!) + */ + public function rewind() + { + + if ($this->index !== -1) { + throw new \Exception('Cannot rewind PDOStatement results'); + } + + $this->next(); + } + + /** + * Check if the current position is valid + * @return Boolean True if valid + */ + public function valid() + { + + return is_array($this->value) || is_object($this->value); + } +} diff --git a/src/Registry.php b/src/Registry.php new file mode 100644 index 0000000..24d7f93 --- /dev/null +++ b/src/Registry.php @@ -0,0 +1,37 @@ +ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : false; + $this->proxies = array(); + $this->method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : false; + $this->path = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : false; + $this->secure = isset($_SERVER['HTTPS']) ? ($_SERVER['HTTPS'] == 'on') : false; + $this->host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : false; + $this->referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : false; + $this->protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : false; + + $this->cli = (php_sapi_name() == 'cli'); + if ($this->cli) { + $this->method = 'CLI'; + } + + if (isset(static::$methodInput)) { + $this->method = static::$methodInput; + } + + $this->uri = ($this->cli ? false : ($this->secure ? 'https' : 'http') .'://'. $this->host . $this->path); + + if (is_callable('apache_request_headers')) { + $this->headers = apache_request_headers(); + + if (self::$loadBalanced) { + if (isset($this->headers['X-Forwarded-For'])) { + $ips = $this->headers['X-Forwarded-For']; + $ips = explode(', ', $ips); + $this->ip = array_shift($ips); + + $this->proxies = $ips; + } + + if (isset($this->headers['X-Forwarded-Proto'])) { + $this->secure = ($this->headers['X-Forwarded-Proto'] == 'HTTPS'); + } + } elseif (isset($this->headers['X-Forwarded-For'])) { + $ips = $this->headers['X-Forwarded-For']; + $ips = explode(', ', $ips); + $this->proxies = $ips; + } + } else { + $this->headers = array(); + } + + // Create our own global array for PUT data + global $_PUT; + $_PUT = array(); + + if (isset(self::$dataInput)) { + $this->body = self::$dataInput; + } else { + $this->body = file_get_contents("php://input"); + } + + if ($this->method == 'PUT') { + parse_str($this->body, $_PUT); + } + + if ($bodyFormat == 'json') { + $this->put = array(); + $this->post = array(); + + $jsonErr = JSON_ERROR_NONE; + + if ($this->method == 'PUT') { + $this->put = json_decode($this->body, true); + $jsonErr = json_last_error(); + } elseif ($this->method == 'POST') { + $this->post = json_decode($this->body, true); + $jsonErr = json_last_error(); + } + + if ($jsonErr !== JSON_ERROR_NONE) { + switch ($jsonErr) { + case JSON_ERROR_DEPTH: + $jsonErrMsg = 'Maximum stack depth exceeded.'; + break; + case JSON_ERROR_STATE_MISMATCH: + $jsonErrMsg = 'Underflow or the modes mismatch.'; + break; + case JSON_ERROR_CTRL_CHAR: + $jsonErrMsg = 'Unexpected control character found.'; + break; + case JSON_ERROR_SYNTAX: + $jsonErrMsg = 'Syntax error, malformed data.'; + break; + case JSON_ERROR_UTF8: + $jsonErrMsg = 'Malformed UTF-8 characters, possibly incorrectly encoded.'; + break; + default: + $jsonErrMsg = 'Generic error.'; + } + + throw new InvalidJsonException($jsonErrMsg); + } + } else { + $this->post = $_POST; + $this->put = $_PUT; + } + + $this->get = $_GET; + $this->cookie = $_COOKIE; + + if ($filter) { + // Filter local copies of POST, GET & COOKIE data + // Unset global versions to prevent access to un-filtered + $this->filterInputs($filter); + + $_PUT = null; + $_POST = null; + $_GET = null; + $_COOKIE = null; + } + } + + public function filterInputs($filter = false) + { + + if ($filter == false) { + return; + } + if (!is_callable($filter)) { + throw new \Exception('Specified filter is not callable.'); + } + + $this->recurse($this->post, $filter); + $this->recurse($this->put, $filter); + $this->recurse($this->get, $filter); + $this->recurse($this->cookie, $filter); + } + + protected function recurse(&$input, &$function) + { + + if (is_array($input)) { + foreach ($input as $name => &$value) { + $this->recurse($value, $function); + } + } else { + $function($input); + } + } + + public function __get($name) + { + + if (property_exists($this, $name)) { + return $this->$name; + } + + throw new \Exception('The property "'. $name .'" is not valid.'); + } + + public function __isset($name) + { + + return isset($this->$name); + } +} diff --git a/src/Response.php b/src/Response.php new file mode 100644 index 0000000..93efb4c --- /dev/null +++ b/src/Response.php @@ -0,0 +1,394 @@ +callback)) { + if (self::$outputBuffering) { + $out = ob_get_contents(); + + // Work-around: Can't call anonymous functions that are class properties + // PHP just looks for a method with that name + $callback = &$this->callback; + $callback($out); + + $this->clearBuffer(); + + echo $out; + } else { + // Work-around: Can't call anonymous functions that are class properties + // PHP just looks for a method with that name + $callback = &$this->callback; + $callback(false); + } + } + + $this->endBuffer(); + } + + /** + * Pass a closure to define a callback function + * Should take one parameter: the string to be sent as output + * Used passed variable referentially to save any changes to output + * If output buffering is off, false will be passed + * @param Function $function + */ + public function setCallback($function) + { + + if (empty($function)) { + $this->callback = false; + return; + } + + if (!is_callable($function)) { + throw new \Exception('Callback should be a function or false.'); + } + + $this->callback = $function; + } + + /** + * Set the HTTP content type + * @param Mixed $type The response type (defaults to 'text/html') + */ + public function contentType($type = false) + { + + if (headers_sent()) { + if (self::$exceptOnHeaders) { + throw new \Exception('Headers already sent. Content-type cannot be changed.'); + } else { + return; + } + } + + if (!$type) { + $type = "text/html"; + } + + self::$contentType = $type ."; charset=". strtolower(self::$charset); + header("Content-Type: ". self::$contentType); + } + + /** + * Set the HTTP response code + * @param Mixed $code The response code to use or false to return current value + * @return Return the code used if $code is false + */ + public function code($code = false) + { + + if (!$code) { + return http_response_code(); + } + + if (headers_sent()) { + if (self::$exceptOnHeaders && (http_response_code() != $code)) { + throw new \Exception('Headers already sent. HTTP response code cannot be changed.'); + } else { + return; + } + } + + self::$code = $code; + http_response_code(self::$code); + } + + /** + * Redirect the client + * @param String $path + * @param Int $type 301 or 302 redirect + * @param Bool $end End response + */ + public function redirect($path, $type = 302, $end = true) + { + // $type should be one of the following: + // 301 = Moved permanently + // 302 = Temporary redirect + // 303 = Perform GET at new location (instead of POST) + + if (headers_sent()) { + if (self::$exceptOnHeaders) { + throw new \Exception('Headers already sent. Redirect cannot be initiated.'); + } else { + return; + } + } + + if (self::$outputBuffering) { + $this->cleanBuffer(); + } + + $this->code($type); + header('Location: '. $path); + + if ($end) { + exit; + } + } + + /** + * Flush the output buffer (but leave enabled) + */ + public function flushBuffer() + { + // Send buffer out + if (self::$outputBuffering) { + ob_flush(); + } + } + + /** + * Clean the output buffer + */ + public function cleanBuffer() + { + // Empty buffer + if (self::$outputBuffering) { + if (headers_sent()) { + // Mute warnings if headers sent + @ob_clean(); + } else { + ob_clean(); + } + } + } + + /** + * Clear the output buffer, alias to cleanBuffer() method + */ + public function clearBuffer() + { + // Alias of cleanBuffer() + $this->cleanBuffer(); + } + + /** + * Flush the buffer and end + */ + public function endBuffer() + { + // Call cleanBuffer first if you don't want anything getting out + + if (self::$outputBuffering) { + ob_end_flush(); + } + + self::$outputBuffering = false; + } + + /** + * Alias to code() method, only does not return code + */ + public function setCode($code) + { + // Set the HTTP response code + $this->code($code); + } + + /** + * Alias to contentType() method + */ + public function setContentType($type) + { + // Set the HTTP content type + $this->contentType($type); + } +} + + +// Backwards compatabilty PHP<5.4 (TODO: REMOVE) +if (!function_exists('http_response_code')) { + function http_response_code($code = null) + { + + if ($code !== null) { + $GLOBALS['http_response_code'] = $code; + + if (headers_sent()) { + return; + } + + switch ($code) { + case 100: + $text = 'Continue'; + break; + case 101: + $text = 'Switching Protocols'; + break; + case 200: + $text = 'OK'; + break; + case 201: + $text = 'Created'; + break; + case 202: + $text = 'Accepted'; + break; + case 203: + $text = 'Non-Authoritative Information'; + break; + case 204: + $text = 'No Content'; + break; + case 205: + $text = 'Reset Content'; + break; + case 206: + $text = 'Partial Content'; + break; + case 300: + $text = 'Multiple Choices'; + break; + case 301: + $text = 'Moved Permanently'; + break; + case 302: + $text = 'Moved Temporarily'; + break; + case 303: + $text = 'See Other'; + break; + case 304: + $text = 'Not Modified'; + break; + case 305: + $text = 'Use Proxy'; + break; + case 400: + $text = 'Bad Request'; + break; + case 401: + $text = 'Unauthorized'; + break; + case 402: + $text = 'Payment Required'; + break; + case 403: + $text = 'Forbidden'; + break; + case 404: + $text = 'Not Found'; + break; + case 405: + $text = 'Method Not Allowed'; + break; + case 406: + $text = 'Not Acceptable'; + break; + case 407: + $text = 'Proxy Authentication Required'; + break; + case 408: + $text = 'Request Time-out'; + break; + case 409: + $text = 'Conflict'; + break; + case 410: + $text = 'Gone'; + break; + case 411: + $text = 'Length Required'; + break; + case 412: + $text = 'Precondition Failed'; + break; + case 413: + $text = 'Request Entity Too Large'; + break; + case 414: + $text = 'Request-URI Too Large'; + break; + case 415: + $text = 'Unsupported Media Type'; + break; + case 500: + $text = 'Internal Server Error'; + break; + case 501: + $text = 'Not Implemented'; + break; + case 502: + $text = 'Bad Gateway'; + break; + case 503: + $text = 'Service Unavailable'; + break; + case 504: + $text = 'Gateway Time-out'; + break; + case 505: + $text = 'HTTP Version not supported'; + break; + default: + return; + break; + } + + $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'); + + header($protocol . ' ' . $code . ' ' . $text); + } else { + $code = (isset($GLOBALS['http_response_code']) ? $GLOBALS['http_response_code'] : 200); + } + + return $code; + } +} diff --git a/src/RouteToError.php b/src/RouteToError.php new file mode 100644 index 0000000..f9883ea --- /dev/null +++ b/src/RouteToError.php @@ -0,0 +1,12 @@ +request = $request; + + $this->method = $request->method; + + $rootPath = self::$rootPath; + if (preg_match('!/$!', $rootPath)) { + $rootPath = substr($rootPath, 0, -1); + } + + $this->uri = preg_replace('!^'. preg_quote($rootPath) .'!', '', $request->path); + if (strpos($this->uri, '?')) { + $this->uri = substr($this->uri, 0, strpos($this->uri, '?')); + } + } + + public function __destruct() + { + + if ($this->match || !is_callable($this->default)) { + return; // Response already sent or no default set + } + if (in_array(http_response_code(), array(301, 302, 303))) { + return; // No default route if redirect + } + + try { + $this->default(); + } catch (RouteToError $e) { + $this->triggerError($e->getCode(), $e->getMessage()); + } + + exit; + } + + /** + * Magic method used specifically for calling the default route and exception handler + * + * @param string $method + * @param array $args + * @return mixed + */ + public function __call($method, $args) + { + if (isset($this->$method) && is_callable($this->$method)) { + return call_user_func_array($this->$method, $args); + } + } + + /** + * Check the method and uri and run the supplied function if match. + * The $execute function is passed an array of matches from $regExpUrlMatch + * + * @param int|array $filterMethod + * @param string|bool $regExpUrlMatch + * @param function $execute + * @return void + */ + public function add($filterMethod, $regExpUrlMatch, $execute) + { + + if (!is_array($filterMethod)) { + $filterMethod = array($filterMethod); + } + + if (!is_callable($execute) && !$execute instanceof Router) { + throw new \Exception('The last parameter to Router::add must be a callable function or an instance of the Firelit\Router class'); + } + + $this->routes[] = array( + 'method' => $filterMethod, + 'matcher' => $regExpUrlMatch, + 'function' => $execute + ); + } + + public function go($uri = false) + { + + if (empty($uri)) { + $uri = $this->uri; + } + + foreach ($this->routes as $route) { + $filterMethod = $route['method']; + $regExpUrlMatch = $route['matcher']; + $execute = $route['function']; + + // (1) Does the request method match? + if (!in_array('*', $filterMethod) && !in_array($this->method, $filterMethod)) { + continue; + } + + $params = array(); + + // (2) Does the URI match? (set $regExpUrlMatch to false to skip) + if ($regExpUrlMatch && ($this->method == 'CLI')) { + continue; + } + if ($regExpUrlMatch && !preg_match($regExpUrlMatch, $uri, $params)) { + continue; + } + + // Method and URI match! + $this->match = true; + + // Remove the full text match from the match array + array_shift($params); + + try { + // Go! + if ($execute instanceof Router) { + $subUri = preg_replace($regExpUrlMatch, '', $uri); + $execute->go($subUri); + } else { + $execute($params); + } + } catch (RouteToError $e) { + $this->triggerError($e->getCode(), $e->getMessage()); + // If not exited, throw it back up (could be caught by another nested route) + throw $e; + } + + // We had a match: No more checking routes! + return; + } + } + + /** + * Set an error route for a specific error code (or 0 for default/catch-all) + * Function to execute will be passed two parameters: the error code & an optional error message + * + * @param int|array $errorCode + * @param function $execute + * @return Firelit\Router + */ + public function errorRoute($errorCode, $execute) + { + if (!is_array($errorCode)) { + $errorCode = array($errorCode); + } + + foreach ($errorCode as $thisCode) { + $this->error[$thisCode] = $execute; + } + + return $this; + } + + /** + * Set the default route if no other routes match + * + * @param function $execute + * @return Firelit\Router + */ + public function defaultRoute($execute) + { + $this->default = $execute; + + return $this; + } + + /** + * A function to handle any exceptions that are caught + * The passed function should take one parameter, an exception object + * + * @param function $execute + * @return Firelit\Router + */ + public function exceptionHandler($execute) + { + $this->exceptionHandler = $execute; + set_exception_handler($this->exceptionHandler); + + return $this; + } + + /** + * Trigger an error route based on the error code and exit script with that error code + * + * @param int $errorCode + * @param string $errorMessage + * @return void + */ + public function triggerError($errorCode, $errorMessage = '') + { + $callError = $errorCode; + + if (!isset($this->error[$errorCode]) || !is_callable($this->error[$errorCode])) { + if (isset($this->error[0]) && is_callable($this->error[0])) { + $callError = 0; + } else { + // Nothing set to handle this error! + return; + } + } + + // Error response function exists + $this->match = true; + + //call_user_func_array($this->error[$errorCode], array($errorCode, $errorMessage)); + $this->error[$callError]($errorCode, $errorMessage); + // Or use call_user_func_array ? + + exit($errorCode); + } +} diff --git a/src/Session.php b/src/Session.php new file mode 100644 index 0000000..c80d1f3 --- /dev/null +++ b/src/Session.php @@ -0,0 +1,185 @@ + array( + 'name' => 'session', + 'lifetime' => 0, // Expires when browser closes + 'path' => '/', + 'domain' => false, // Set to false to use the current domain + 'secureOnly' => false, + 'httpOnly' => true + ), + 'validatorSalt' => 'dJa832lwkdP1' // Recommend changing to slow session key brute-force spoofing + ); + + private $session_id; + + public function __construct(\SessionHandlerInterface $store = null, $sessionId = false, $validateSessionId = true) + { + // Create a session using the given SessionHandlerInterface object + // If null, will use PHP's native SESSION engine + + if ($store) { + session_set_save_handler($store, true); + } + + // Check for Session ID in cookies + if (!$sessionId) { + $sessionId = $this->getSessionId(); + } + + // Generate Session ID if none available + if (!$sessionId) { + $sessionId = $this->generateSessionId(); + } + + // Set the Session ID and send cookie with value + $this->setSessionId($sessionId, $validateSessionId); + + @session_start(); + } + + public function __set($name, $val) + { + // Magic sesion value setter + + $_SESSION[$name] = $val; + } + + public function __get($name) + { + // Magic sesion value getter + + if (!isset($_SESSION[$name])) { + return null; + } + return $_SESSION[$name]; + } + + public function __isset($name) + { + // Magic session isset + + return isset($_SESSION[$name]); + } + + public function __unset($name) + { + // Magic session unsetter + + unset($_SESSION[$name]); + } + + // Can only be excuted before session_start() + protected function setSessionId($sessionId, $validateSessionId = true) + { + + if ($validateSessionId) { + if (!$this->sessionIdIsValid($sessionId)) { + $sessionId = false; + } + } + + if (!$sessionId) { + $sessionId = $this->generateSessionId(); + } + + $this->session_id = $sessionId; + + // Not relying on session to retrieve it's ID from the cookie + session_id($sessionId); + + // Will set cookie automatically anyway, let's try to control it + session_set_cookie_params( + self::$config['cookie']['lifetime'], + self::$config['cookie']['path'], + self::$config['cookie']['domain'], + self::$config['cookie']['secureOnly'], + self::$config['cookie']['httpOnly'] + ); + + session_name(self::$config['cookie']['name']); + } + + public function getSessionId() + { + + if (!$this->session_id) { + if (isset($_COOKIE[self::$config['cookie']['name']])) { + $this->session_id = $_COOKIE[self::$config['cookie']['name']]; + } + } + + return $this->session_id; + } + + public static function sessionIdIsValid($sessionId) + { + + $sessionId = preg_replace('/[^A-Za-z0-9\+\/=]+/', '', $sessionId); + + if (strlen($sessionId) != 50) { + return false; + } + + $sids = explode('=', $sessionId); + if (sizeof($sids) != 2) { + return false; + } + + // Verify mini-hmac; not critical, just a quick sanity check + $check = static::generateHmacSid($sids[0].'=', static::$config['validatorSalt']); + + if ($sessionId !== $check) { + return false; + } + + return true; + } + + public static function generateSessionId() + { + + if (isset($_SERVER['REMOTE_ADDR'])) { + $remAddr = $_SERVER['REMOTE_ADDR']; + } else { + $remAddr = mt_rand(0, 1000000); + } + + if (isset($_SERVER['REMOTE_PORT'])) { + $remPort = $_SERVER['REMOTE_PORT']; + } else { + $remPort = mt_rand(0, 1000000); + } + + // Looks like we need a new session ID + $sid = base64_encode(hash('sha256', microtime() .'|'. $remAddr .'|'. $remPort .'|'. mt_rand(0, 1000000), true)); + + // Create mini-hmac; not critical, just a quick sanity check + return static::generateHmacSid($sid, static::$config['validatorSalt']); + } + + public static function generateHmacSid($partSid, $salt) + { + + return $partSid . substr(base64_encode(hash_hmac('sha256', $partSid, $salt, true)), 0, 6); + } + + public function destroy() + { + // Remove all data from and traces of the current session + + session_destroy(); + } + + public function __destruct() + { + // Not required as it is handled automatically but could be convienent to close a session early or juggle multiple sessions + session_write_close(); + } +} diff --git a/src/Singleton.php b/src/Singleton.php new file mode 100644 index 0000000..939de10 --- /dev/null +++ b/src/Singleton.php @@ -0,0 +1,32 @@ +newInstanceArgs($args); + } + + return self::$singletons[$class]; + } + + public static function destruct() + { + + $class = get_called_class(); + if (isset(self::$singletons[$class])) { + unset(self::$singletons[$class]); + } + } +} diff --git a/src/States.php b/src/States.php new file mode 100644 index 0000000..2afed3c --- /dev/null +++ b/src/States.php @@ -0,0 +1,190 @@ + array( + "AL" => "Alabama", + "AK" => "Alaska", + "AS" => "American Samoa", + "AZ" => "Arizona", + "AR" => "Arkansas", + "CA" => "California", + "CO" => "Colorado", + "CT" => "Connecticut", + "DE" => "Delaware", + "DC" => "Dist of Columbia", + "FM" => "Fed States Of Micronesia", + "FL" => "Florida", + "GA" => "Georgia", + "GU" => "Guam", + "HI" => "Hawaii", + "ID" => "Idaho", + "IL" => "Illinois", + "IN" => "Indiana", + "IA" => "Iowa", + "KS" => "Kansas", + "KY" => "Kentucky", + "LA" => "Louisiana", + "ME" => "Maine", + "MH" => "Marshall Islands", + "MD" => "Maryland", + "MA" => "Massachusetts", + "MI" => "Michigan", + "MN" => "Minnesota", + "MS" => "Mississippi", + "MO" => "Missouri", + "MT" => "Montana", + "NE" => "Nebraska", + "NV" => "Nevada", + "NH" => "New Hampshire", + "NJ" => "New Jersey", + "NM" => "New Mexico", + "NY" => "New York", + "NC" => "North Carolina", + "ND" => "North Dakota", + "MP" => "Northern Mariana Islands", + "OH" => "Ohio", + "OK" => "Oklahoma", + "OR" => "Oregon", + "PW" => "Palau", + "PA" => "Pennsylvania", + "PR" => "Puerto Rico", + "RI" => "Rhode Island", + "SC" => "South Carolina", + "SD" => "South Dakota", + "TN" => "Tennessee", + "TX" => "Texas", + "UT" => "Utah", + "VT" => "Vermont", + "VI" => "Virgin Islands", + "VA" => "Virginia", + "WA" => "Washington", + "WV" => "West Virginia", + "WI" => "Wisconsin", + "WY" => "Wyoming", + "AA" => "Armed Forces (AA)", + "AE" => "Armed Forces (AE)", + "AP" => "Armed Forces (AP)" + ), + "CA" => array( + "AB" => "Alberta", + "BC" => "British Columbia", + "MB" => "Manitoba", + "NB" => "New Brunswick", + "NL" => "Newfoundland and Labrador", + "NT" => "Northwest Territories", + "NS" => "Nova Scotia", + "NU" => "Nunavut", + "ON" => "Ontario", + "PE" => "Prince Edward Island", + "QC" => "Quebec", + "SK" => "Saskatchewan", + "YT" => "Yukon" + ), + "MX" => array( + "AG" => "Aguascalientes", + "BC" => "Baja California Norte", + "BS" => "Baja California Sur", + "CM" => "Campeche", + "CS" => "Chiapas", + "CH" => "Chihuahua", + "CO" => "Coahuila", + "CL" => "Colima", + "DF" => "Distrito Federal", + "DG" => "Durango", + "GT" => "Guanajuato", + "GR" => "Guerrero", + "HG" => "Hidalgo", + "JA" => "Jalisco", + "MX" => "Mexico", + "MI" => "Michoacan", // Michoacán + "MO" => "Morelos", + "NA" => "Nayarit", + "NL" => "Nuevo Leon", + "OA" => "Oaxaca", + "PU" => "Puebla", + "QT" => "Queretaro", + "QR" => "Quintana Roo", + "SL" => "San Luis Potosi", + "SI" => "Sinaloa", + "SO" => "Sonora", + "TB" => "Tabasco", + "TM" => "Tamaulipas", + "TL" => "Tlaxcala", + "VE" => "Veracruz", + "YU" => "Yucatan", + "ZA" => "Zacatecas" + ) + ); + + /* + Useage Example: + + States::display( 'US', create_function('$abbrv,$name', 'return "";') ); + + */ + public static function display($country, $callback, $subset = false) + { + // $country is the country to display states from + // $callback is the anonymous function for formating the data + // $subset should be an array of ok abbreviations, set to false for all states + // Returns [true] on success, [false] if no states to display + + if (!is_callable($callback)) { + throw new Exception('Callback function is not callable.'); + } + if ($subset && !is_array($subset)) { + throw new Exception('Subset must be false or an array of acceptable country abbreviations.'); + } + + if (class_exists('Countries') && !Countries::check($country)) { + return false; + } + if (!isset(self::$list[$country])) { + return false; + } + + foreach (self::$list[$country] as $abbrv => $name) { + if ($subset && !in_array($abbrv, $subset)) { + continue; + } + + echo $callback($abbrv, $name); + } + + return true; + } + + public static function check($country, $stateAbbrv) + { + // Check if a state abbrevation is valid + // Returns [true] if valid, [false] if invalid and [null] if states are not set for this country + + if (class_exists('Countries') && !Countries::check($country)) { + return false; + } + if (!isset(self::$list[$country])) { + return null; + } + if (!isset(self::$list[$country][$stateAbbrv])) { + return false; + } + + return true; + } + + public static function getName($country, $stateAbbrv, $html = true) + { + // Get a states's name from its abbrevation + // Returns the state name [string] if available, [false] if not available + + if (self::check($country, $stateAbbrv)) { + return false; + } + return self::$list[$country][$stateAbbrv]; + } +} diff --git a/src/Strings.php b/src/Strings.php new file mode 100644 index 0000000..aef0b5f --- /dev/null +++ b/src/Strings.php @@ -0,0 +1,168 @@ + $v) { + self::cleanUTF8($input[$k], $lineBreaksOk); + } + } else { + $input = mb_convert_encoding($input, "UTF-8", "UTF-8"); + if ($lineBreaksOk) { + $input = preg_replace('![\x00-\x09\x0B\x0C\x0E-\x1F\x7F-\x9F]!', '', $input); + } else { + $input = preg_replace('!\p{C}!u', '', $input); + } + } + } + + public static function html($string) + { + // HTML-escaping with UTF-8 support + return htmlentities($string, ENT_COMPAT, 'UTF-8'); + } + + public static function xml($string) + { + // XML-escaping + $string = htmlentities($string); + $xml = array('"','&','&','<','>',' ','¡','¢','£','¤','¥','¦','§','¨','©','ª','«','¬','­','®','¯','°','±','²','³','´','µ','¶','·','¸','¹','º','»','¼','½','¾','¿','À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','×','Ø','Ù','Ú','Û','Ü','Ý','Þ','ß','à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ð','ñ','ò','ó','ô','õ','ö','÷','ø','ù','ú','û','ü','ý','þ','ÿ'); + $html = array('"','&','&','<','>',' ','¡','¢','£','¤','¥','¦','§','¨','©','ª','«','¬','­','®','¯','°','±','²','³','´','µ','¶','·','¸','¹','º','»','¼','½','¾','¿','À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','×','Ø','Ù','Ú','Û','Ü','Ý','Þ','ß','à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ð','ñ','ò','ó','ô','õ','ö','÷','ø','ù','ú','û','ü','ý','þ','ÿ'); + + $string = str_replace($html, $xml, $string); + $string = str_ireplace($html, $xml, $string); + + return $string; + } + + public static function csv($string) + { + // CSV-escaping for a CSV cell + $string = str_replace('"', '""', $string); + return '"'.utf8_decode($string).'"'; + } + + public static function lower($string) + { + // Multi-byte-safe lower-case + return mb_strtolower($string, 'UTF-8'); + } + + public static function upper($string) + { + // Multi-byte-safe lower-case + return mb_strtoupper($string, 'UTF-8'); + } + + public static function title($string) + { + // Multi-byte-safe title-case + // This is also doing a lower() first, not like ucwords() + return mb_convert_case($string, MB_CASE_TITLE, 'UTF-8'); + } + + public static function ucwords($string) + { + // mb_convert_case($str, MB_CASE_TITLE, 'UTF-8') is doing a lower() first, not like ucwords() + return preg_replace_callback('/\b(\s?)(.)(\S*)\b/u', function ($matches) { + return $matches[1] . static::upper($matches[2]) . $matches[3]; + }, $string); + } +} diff --git a/src/Vars.php b/src/Vars.php new file mode 100644 index 0000000..da8d6d3 --- /dev/null +++ b/src/Vars.php @@ -0,0 +1,91 @@ + static::TYPE_DB, + 'table' => 'Vars', + 'col_name' => 'name', + 'col_value' => 'value' + ); + + static::$config = array_merge($config, static::$config, $default); + + if (static::$config['type'] == static::TYPE_DB) { + static::$setter = function ($name, $value) { + + $q = new Query(); + $q->replace(static::$config['table'], array( + static::$config['col_name'] => $name, + static::$config['col_value'] => $value + )); + }; + + static::$getter = function ($name) { + + $q = new Query(); + $q->query("SELECT :col_value AS `value` FROM :table WHERE :col_name = :name LIMIT 1", array( + ':col_value' => static::$config['col_value'], + ':table' => static::$config['table'], + ':col_name' => static::$config['col_name'], + ':name' => $name + )); + + $result = $q->getRow(); + return (!empty($result) ? $result['value'] : null); + }; + } + } + + $name = get_called_class(); + return new $name(); + } + + public function __construct() + { + if (empty(static::$config)) { + throw new \Exception('Not yet configured; Call Firelit\Vars::init() first'); + } + } + + public function __set($name, $val) + { + + $setter = static::$setter; + return $setter($name, $val); + } + + public function __unset($name) + { + + $setter = static::$setter; + return $setter($name, null); + } + + public function __isset($name) + { + + $getter = static::$getter; + return ($getter($name) !== null); + } + + public function __get($name) + { + + $getter = static::$getter; + return $getter($name); + } +} diff --git a/src/View.php b/src/View.php new file mode 100644 index 0000000..4c46346 --- /dev/null +++ b/src/View.php @@ -0,0 +1,138 @@ +setLayout($layout); + $this->setTemplate($template); + } + + public function setLayout($layout) + { + $this->layout = $layout; + return $this; + } + + public function setTemplate($template) + { + $this->template = $template; + return $this; + } + + public function setData($data) + { + if (is_array($data)) { + $this->data = $data; + } + return $this; + } + + protected function yieldNow() + { + if (!$this->template) { + return; + } + + extract($this->data, EXTR_SKIP); + + $file = $this->fileName($this->template); + include($file); + } + + protected function html($html) + { + return htmlentities($html); + } + + protected function addAsset($name, $attributes = array()) + { + $nameArray = explode('.', $name); + $ext = array_pop($nameArray); + + switch ($ext) { + case 'js': + echo ''."\n"; + break; + case 'css': + if (!isset($attributes['rel'])) { + $attributes['rel'] = 'stylesheet'; + } + + echo ' $value) { + echo ' '. $name .'="'. $this->html($value) .'"'; + } + echo ">\n"; + + break; + case 'ico': + echo ' $value) { + echo ' '. $name .'="'. $this->html($value) .'"'; + } + echo ">\n"; + + break; + } + } + + protected function includePart($name, $moreData = array()) + { + extract($this->data, EXTR_SKIP); + extract($moreData, EXTR_SKIP); + + $file = $this->fileName($name); + include($file); + } + + public function render($data = false) + { + $this->setData($data); + extract($this->data, EXTR_SKIP); + + if ($this->layout) { + $file = $this->fileName($this->layout); + include($file); + } else { + $this->yieldNow(); + } + } + + protected function fileName($name) + { + if (preg_match('/\.php$/', $name)) { + $ext = ''; + } else { + $ext = '.php'; + } + $file = static::$viewFolder . $name . $ext; + + if (!file_exists($file)) { + throw new \Exception('View file does not exist: '. $file); + } + if (!is_readable($file)) { + throw new \Exception('View file not readable: '. $file); + } + + return $file; + } + + public static function quickRender($template, $layout = false, $data = false) + { + $class = get_called_class(); + $view = new $class($template, $layout); + $view->render($data); + return $view; + } +} diff --git a/tests/ApiResponseTest.php b/tests/ApiResponseTest.php index a72b4c2..9bb4126 100644 --- a/tests/ApiResponseTest.php +++ b/tests/ApiResponseTest.php @@ -1,94 +1,95 @@ setTemplate(array('res' => true, 'message' => 'Peter picked a pack of pickels')); - $resp->respond(array(), false); - - unset($resp); - Firelit\ApiResponse::destruct(); - - $res = ob_get_contents(); - ob_end_clean(); - - $this->assertEquals('{"res":true,"message":"Peter picked a pack of pickels"}', $res); - - } - - public function testCancel() { - - ob_start(); - - $resp = Firelit\ApiResponse::init('JSON', false); - - $resp->setTemplate(array('res' => true, 'message' => 'Peter picked a pack of pickels')); - - $resp->cancel(); - - echo ''; - - unset($resp); - Firelit\ApiResponse::destruct(); - - $res = ob_get_contents(); - ob_end_clean(); - - $this->assertEquals('', $res); - - } - - public function testJsonCallbackWrap() { - - ob_start(); - - $resp = Firelit\ApiResponse::init('JSON', false); - - $resp->setJsonCallbackWrap('test_function'); - - $resp->setTemplate(array('res' => false, 'message' => 'Peter picked a pack of pickels')); - $resp->respond(array(), false); - - unset($resp); - Firelit\ApiResponse::destruct(); - - $res = ob_get_contents(); - ob_end_clean(); - - $this->assertEquals('test_function({"res":false,"message":"Peter picked a pack of pickels"});', $res); - - } - - - public function testApiCallback() { - - ob_start(); - - $resp = Firelit\ApiResponse::init('JSON', false); - - $resp->setApiCallback(function(&$response) { - - unset($response['data']); - $response['new'] = true; - - }); - - $resp->setTemplate(array('res' => true, 'message' => 'Peter picked a pack of pickels', 'data' => 'erase me')); - $resp->respond(array(), false); - - unset($resp); - Firelit\ApiResponse::destruct(); - - $res = ob_get_contents(); - ob_end_clean(); - - $this->assertEquals('{"res":true,"message":"Peter picked a pack of pickels","new":true}', $res); - - } - -} \ No newline at end of file +namespace Firelit; + +class ApiResponseTest extends \PHPUnit_Framework_TestCase +{ + + public function testTemplate() + { + + ob_start(); + + $resp = ApiResponse::init('JSON', false); + + $resp->setTemplate(array('res' => true, 'message' => 'Peter picked a pack of pickels')); + $resp->respond(array(), false); + + unset($resp); + ApiResponse::destruct(); + + $res = ob_get_contents(); + ob_end_clean(); + + $this->assertEquals('{"res":true,"message":"Peter picked a pack of pickels"}', $res); + } + + public function testCancel() + { + + ob_start(); + + $resp = ApiResponse::init('JSON', false); + + $resp->setTemplate(array('res' => true, 'message' => 'Peter picked a pack of pickels')); + + $resp->cancel(); + + echo ''; + + unset($resp); + ApiResponse::destruct(); + + $res = ob_get_contents(); + ob_end_clean(); + + $this->assertEquals('', $res); + } + + public function testJsonCallbackWrap() + { + + ob_start(); + + $resp = ApiResponse::init('JSON', false); + + $resp->setJsonCallbackWrap('test_function'); + + $resp->setTemplate(array('res' => false, 'message' => 'Peter picked a pack of pickels')); + $resp->respond(array(), false); + + unset($resp); + ApiResponse::destruct(); + + $res = ob_get_contents(); + ob_end_clean(); + + $this->assertEquals('test_function({"res":false,"message":"Peter picked a pack of pickels"});', $res); + } + + + public function testApiCallback() + { + + ob_start(); + + $resp = ApiResponse::init('JSON', false); + + $resp->setApiCallback(function (&$response) { + + unset($response['data']); + $response['new'] = true; + }); + + $resp->setTemplate(array('res' => true, 'message' => 'Peter picked a pack of pickels', 'data' => 'erase me')); + $resp->respond(array(), false); + + unset($resp); + ApiResponse::destruct(); + + $res = ob_get_contents(); + ob_end_clean(); + + $this->assertEquals('{"res":true,"message":"Peter picked a pack of pickels","new":true}', $res); + } +} diff --git a/tests/CacheTest.php b/tests/CacheTest.php index 3f8c42d..47358fe 100644 --- a/tests/CacheTest.php +++ b/tests/CacheTest.php @@ -1,95 +1,97 @@ array( - 'enabled' => false - ) - )); - - } - - public function testGetNoClosure() { - - $val = Firelit\Cache::get('index'); - - $this->assertNull($val); - - } - - /** - * @depends testGetNoClosure - */ - public function testGetCacheMiss() { - - $val = Firelit\Cache::get('index', function() { - - return 12345; - - }); - - $this->assertEquals( 12345, $val ); - $this->assertFalse( Firelit\Cache::$cacheHit ); - - } - - /** - * @depends testGetCacheMiss - */ - public function testVariableSeperation() { - - $val = Firelit\Cache::get('index2', function() { - - return 100; - - }); - - $this->assertEquals( 100, $val ); - $this->assertFalse( Firelit\Cache::$cacheHit ); - - } - - /** - * @depends testGetCacheMiss - */ - public function testGetCacheHit() { - - $val = Firelit\Cache::get('index', function() { - - return -1; - - }); - - $this->assertEquals( 12345, $val ); - $this->assertTrue( Firelit\Cache::$cacheHit ); - - } - - /** - * @depends testGetCacheHit - */ - public function testGetCacheSet() { - - $val = Firelit\Cache::get('index'); - - $this->assertEquals( 12345, $val ); - - Firelit\Cache::set('index', 123456); - - $val = Firelit\Cache::get('index'); - - $this->assertEquals( 123456, $val ); - - Firelit\Cache::set('index', null); - - $val = Firelit\Cache::get('index'); - - $this->assertNull( $val ); - - } -} \ No newline at end of file +namespace Firelit; + +class CacheTest extends \PHPUnit_Framework_TestCase +{ + + private $session; + private $store; + private $testVal; + + protected function setUp() + { + + Cache::config(array( + 'memecached' => array( + 'enabled' => false + ) + )); + } + + public function testGetNoClosure() + { + + $val = Cache::get('index'); + + $this->assertNull($val); + } + + /** + * @depends testGetNoClosure + */ + public function testGetCacheMiss() + { + + $val = Cache::get('index', function () { + + return 12345; + }); + + $this->assertEquals(12345, $val); + $this->assertFalse(Cache::$cacheHit); + } + + /** + * @depends testGetCacheMiss + */ + public function testVariableSeperation() + { + + $val = Cache::get('index2', function () { + + return 100; + }); + + $this->assertEquals(100, $val); + $this->assertFalse(Cache::$cacheHit); + } + + /** + * @depends testGetCacheMiss + */ + public function testGetCacheHit() + { + + $val = Cache::get('index', function () { + + return -1; + }); + + $this->assertEquals(12345, $val); + $this->assertTrue(Cache::$cacheHit); + } + + /** + * @depends testGetCacheHit + */ + public function testGetCacheSet() + { + + $val = Cache::get('index'); + + $this->assertEquals(12345, $val); + + Cache::set('index', 123456); + + $val = Cache::get('index'); + + $this->assertEquals(123456, $val); + + Cache::set('index', null); + + $val = Cache::get('index'); + + $this->assertNull($val); + } +} diff --git a/tests/CountriesTest.php b/tests/CountriesTest.php index a3b3a90..b2dfa81 100644 --- a/tests/CountriesTest.php +++ b/tests/CountriesTest.php @@ -1,15 +1,16 @@ assertEquals( 'United States', Countries::getName('US') ); - $this->assertEquals( 'Canada', Countries::getName('CA') ); - $this->assertEquals( 'Ã…land Islands', Countries::getName('AX', false) ); - $this->assertEquals( 'Åland Islands', Countries::getName('AX', true) ); - - } -} \ No newline at end of file + $this->assertEquals('United States', Countries::getName('US')); + $this->assertEquals('Canada', Countries::getName('CA')); + $this->assertEquals('Ã…land Islands', Countries::getName('AX', false)); + $this->assertEquals('Åland Islands', Countries::getName('AX', true)); + } +} diff --git a/tests/CryptoKeyTest.php b/tests/CryptoKeyTest.php new file mode 100644 index 0000000..ae0b006 --- /dev/null +++ b/tests/CryptoKeyTest.php @@ -0,0 +1,38 @@ +assertEquals(CryptoKey::TYPE_SYMMETRIC, $key->getType()); + $this->assertEquals(256, $key->getBitLength()); + + // It takes 24 base64 characters to represent 256-bits of data + $textKey = $key->getKey(CryptoKey::FORMAT_BASE64); + $this->assertRegExp('/^[A-Za-z0-9\+\=\/]{44}$/', $textKey); + + $key = CryptoKey::newPrivateKey(1024); + + // Make sure the key type is set + $this->assertEquals(CryptoKey::TYPE_PRIVATE, $key->getType()); + $this->assertEquals(1024, $key->getBitLength()); + + // It takes 24 base64 characters to represent 256-bits of data + $textKey = $key->getKey(CryptoKey::FORMAT_PEM); + $this->assertRegExp('/^-{5}.+-{5}$/m', $textKey); + $this->assertRegExp('/PRIVATE/', $textKey); + $this->assertRegExp('/[A-Za-z0-9\+\=\/\n]{800,}/m', $textKey); + + $textKey = $key->getPublicKey(CryptoKey::FORMAT_PEM); + $this->assertRegExp('/^-{5}.+-{5}$/m', $textKey); + $this->assertRegExp('/PUBLIC/', $textKey); + $this->assertRegExp('/[A-Za-z0-9\+\=\/\n]{219,}/m', $textKey); + } +} diff --git a/tests/CryptoPackageTest.php b/tests/CryptoPackageTest.php new file mode 100644 index 0000000..6063566 --- /dev/null +++ b/tests/CryptoPackageTest.php @@ -0,0 +1,52 @@ +secret = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; + + $this->object = (object) array('key' => 'value123', 'text' => $this->secret); + } + + public function testString() + { + + $key = CryptoKey::newPrivateKey(2048); + + $packager = new CryptoPackage($key); + $ciphertext = $packager->encrypt($this->secret)->with($packager::PUBLIC_KEY); + + $this->assertTrue(strlen($ciphertext) > 400); + $this->assertEquals(0, preg_match('/Lorem ipsum/', $ciphertext)); + $this->assertNotEquals($ciphertext, $this->secret); + + $packager = new CryptoPackage($key); + $back = $packager->decrypt($ciphertext)->with($packager::PRIVATE_KEY); + + $this->assertEquals($this->secret, $back); + } + + public function testObject() + { + + $key = CryptoKey::newPrivateKey(2048); + + $packager = new CryptoPackage($key); + $ciphertext = $packager->encrypt($this->object)->with($packager::PRIVATE_KEY); + + $this->assertTrue(strlen($ciphertext) > 500); + $this->assertEquals(0, preg_match('/Lorem ipsum/', $ciphertext)); + + $packager = new CryptoPackage($key); + $back = $packager->decrypt($ciphertext)->with($packager::PUBLIC_KEY); + + $this->assertEquals($this->object, $back); + } +} diff --git a/tests/CryptoTest.php b/tests/CryptoTest.php index b7073b9..8523996 100644 --- a/tests/CryptoTest.php +++ b/tests/CryptoTest.php @@ -1,87 +1,64 @@ password = base64_encode('My super secret password!'); - $this->unencrypted = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut urna tellus, faucibus nec porttitor ac, laoreet tristique libero.'; - - } - - public function testIv() { - - $this->iv = Firelit\Crypto::getIv(); - $this->assertRegExp('/^[\w=\/\+]{5,}$/', $this->iv); - - return $this->iv; - - } - - public function testKeyGen() { - - $key = Firelit\Crypto::generateKey(); - $this->assertRegExp('/^[0-9A-Za-z\\=\+\/]{44}$/', $key); - - } - - /** - * @depends testIv - */ - public function testEncrypt($iv) { - - $this->iv = $iv; - - $this->encrypted = Firelit\Crypto::encrypt($this->unencrypted, $this->password, $this->iv); - $this->assertRegExp('/^[\w=\/\+]{5,}$/', $this->encrypted); - $this->assertNotEquals($this->encrypted, $this->unencrypted); - - return array($this->iv, $this->encrypted); - - } - - /** - * @depends testEncrypt - */ - public function testDecrypt($passed) { - - $this->iv = $passed[0]; - $this->encrypted = $passed[1]; - - $testVal = Firelit\Crypto::decrypt($this->encrypted, $this->password, $this->iv); - $this->assertEquals($testVal, $this->unencrypted); - - } - - public function testPackages() { - - $subject = 'This is a test cipher. Encrypt me!'; - $password = Firelit\Crypto::generateKey(32); - - $package = Firelit\Crypto::package($subject, $password); - - $this->assertRegExp('/^[0-9A-Za-z\\=\+\/\|]{20,}$/', $package); - - $failedUnPackage = Firelit\Crypto::unpackage($package, $password.'1'); - - $this->assertNull($failedUnPackage, 'Unpackaging should fail due to invalid HMAC'); - - $successUnPackage = Firelit\Crypto::unpackage($package, $password); - - $this->assertTrue($subject === $successUnPackage, 'Unpackaging should be successfull'); - - } - - public function testNoKeyException() { - - $this->setExpectedException('Exception'); - - $subject = 'This is a test cipher. Encrypt me!'; - $password = ''; - - $package = Firelit\Crypto::package($subject, $password); - - } -} \ No newline at end of file +namespace Firelit; + +class CryptoTest extends \PHPUnit_Framework_TestCase +{ + + private $secret; + + protected function setUp() + { + + $this->secret = 'This is a secret phrase!'; + } + + public function testSymmetric() + { + + $key = CryptoKey::newSymmetricKey(256); + + $crypto = new Crypto($key); + $ciphertext = $crypto->encrypt($this->secret); + + $this->assertTrue(strlen($ciphertext) > 20); + $this->assertNotEquals($ciphertext, $this->secret); + + $crypto = new Crypto($key); + $back = $crypto->decrypt($ciphertext); + + $this->assertEquals($this->secret, $back); + } + + public function testPublicKey() + { + + $key = CryptoKey::newPrivateKey(1024); + + $crypto = new Crypto($key); + $ciphertext = $crypto->encrypt($this->secret)->with($crypto::PUBLIC_KEY); + + $this->assertTrue(strlen($ciphertext) > 20); + $this->assertNotEquals($ciphertext, $this->secret); + + $crypto = new Crypto($key); + $back = $crypto->decrypt($ciphertext)->with($crypto::PRIVATE_KEY); + + $this->assertEquals($this->secret, $back); + + // Now let's try it the other way around + + $key = CryptoKey::newPrivateKey(1024); + + $crypto = new Crypto($key); + $ciphertext = $crypto->encrypt($this->secret)->with($crypto::PRIVATE_KEY); + + $this->assertTrue(strlen($ciphertext) > 20); + $this->assertNotEquals($ciphertext, $this->secret); + + $crypto = new Crypto($key); + $back = $crypto->decrypt($ciphertext)->with($crypto::PUBLIC_KEY); + + $this->assertEquals($this->secret, $back); + } +} diff --git a/tests/DatabaseMigrationManagerTest.php b/tests/DatabaseMigrationManagerTest.php index 71fbeda..ab81a5c 100644 --- a/tests/DatabaseMigrationManagerTest.php +++ b/tests/DatabaseMigrationManagerTest.php @@ -1,109 +1,125 @@ submitMigration($mock); + $manager->submitMigration($mock); - $this->assertEquals(1, $manager->count()); + $this->assertEquals(1, $manager->count()); + } - } + public function testUpMigration() + { - public function testUpMigration() { + $mock1 = new DatabaseMigrationMock_1(); + $mock2 = new DatabaseMigrationMock_2(); + $mock3 = new DatabaseMigrationMock_3(); - $mock1 = new DatabaseMigrationMock_1(); - $mock2 = new DatabaseMigrationMock_2(); - $mock3 = new DatabaseMigrationMock_3(); + $manager = new DatabaseMigrationManager('4.4.12', 'up', '5.0'); - $manager = new Firelit\DatabaseMigrationManager('4.4.12', 'up', '5.0'); + $manager->submitMigration($mock1); + $this->assertEquals(1, $manager->count()); - $manager->submitMigration($mock1); - $this->assertEquals(1, $manager->count()); + $manager->submitMigration($mock2); + $this->assertEquals(2, $manager->count()); - $manager->submitMigration($mock2); - $this->assertEquals(2, $manager->count()); + $manager->submitMigration($mock3); + $this->assertEquals(2, $manager->count()); + } - $manager->submitMigration($mock3); - $this->assertEquals(2, $manager->count()); + public function testDownMigration() + { - } + $mock1 = new DatabaseMigrationMock_1(); + $mock2 = new DatabaseMigrationMock_2(); - public function testDownMigration() { + $manager = new DatabaseMigrationManager('5.0', 'down', '4.5.1'); - $mock1 = new DatabaseMigrationMock_1(); - $mock2 = new DatabaseMigrationMock_2(); + $manager->submitMigration($mock1); + $manager->submitMigration($mock2); - $manager = new Firelit\DatabaseMigrationManager('5.0', 'down', '4.5.1'); + $this->assertEquals(1, $manager->count()); + } - $manager->submitMigration($mock1); - $manager->submitMigration($mock2); + public function testSortAndCallback() + { - $this->assertEquals(1, $manager->count()); - - } + $manager = new DatabaseMigrationManager('4.4'); - public function testSortAndCallback() { + $manager->submitMigration('Firelit\DatabaseMigrationMock_2'); + $manager->submitMigration('Firelit\DatabaseMigrationMock_3'); + $manager->submitMigration('Firelit\DatabaseMigrationMock_1'); - $manager = new Firelit\DatabaseMigrationManager('4.4'); + $this->assertEquals(3, $manager->count()); - $manager->submitMigration('DatabaseMigrationMock_2'); - $manager->submitMigration('DatabaseMigrationMock_3'); - $manager->submitMigration('DatabaseMigrationMock_1'); + $manager->sortMigrations(); - $this->assertEquals(3, $manager->count()); + $test = $this; - $manager->sortMigrations(); + $manager->setPostExecCallback(function ($ver, $on, $of) use ($test) { - $test = $this; - - $manager->setPostExecCallback(function($ver, $on, $of) use ($test) { - - if ($on == 0) { - $test->assertEquals(1, DatabaseMigrationMock_1::$upCount); - $test->assertEquals(0, DatabaseMigrationMock_2::$upCount); - $test->assertEquals(0, DatabaseMigrationMock_3::$upCount); - } elseif ($on == 1) { - $test->assertEquals(1, DatabaseMigrationMock_1::$upCount); - $test->assertEquals(1, DatabaseMigrationMock_2::$upCount); - $test->assertEquals(0, DatabaseMigrationMock_3::$upCount); - } elseif ($on == 2) { - $test->assertEquals(1, DatabaseMigrationMock_1::$upCount); - $test->assertEquals(1, DatabaseMigrationMock_2::$upCount); - $test->assertEquals(1, DatabaseMigrationMock_3::$upCount); - } else { - throw new Exception('Invalid loop index: '. $on); - } - - }); - - $manager->executeMigrations(); - - } + if ($on == 0) { + $test->assertEquals(1, DatabaseMigrationMock_1::$upCount); + $test->assertEquals(0, DatabaseMigrationMock_2::$upCount); + $test->assertEquals(0, DatabaseMigrationMock_3::$upCount); + } elseif ($on == 1) { + $test->assertEquals(1, DatabaseMigrationMock_1::$upCount); + $test->assertEquals(1, DatabaseMigrationMock_2::$upCount); + $test->assertEquals(0, DatabaseMigrationMock_3::$upCount); + } elseif ($on == 2) { + $test->assertEquals(1, DatabaseMigrationMock_1::$upCount); + $test->assertEquals(1, DatabaseMigrationMock_2::$upCount); + $test->assertEquals(1, DatabaseMigrationMock_3::$upCount); + } else { + throw new Exception('Invalid loop index: '. $on); + } + }); + $manager->executeMigrations(); + } } diff --git a/tests/DatabaseMigrationTest.php b/tests/DatabaseMigrationTest.php index 8d80643..c5769e5 100644 --- a/tests/DatabaseMigrationTest.php +++ b/tests/DatabaseMigrationTest.php @@ -1,73 +1,83 @@ assertEquals('5.0.5', $mock->getVersion()); - - } + public function down() + { + } +} +// @codingStandardsIgnoreEnd - public function testCheckVersionUp() { +// @codingStandardsIgnoreLine (ignoring multiple classes in a file) +class DatabaseMigrationTest extends \PHPUnit_Framework_TestCase +{ - $mock = new DatabaseMigrationMock(); + public function testGetVersion() + { - $this->assertTrue($mock->checkVersionUp('4.15.15')); - $this->assertTrue($mock->checkVersionUp('5.0')); - $this->assertTrue($mock->checkVersionUp('5.0.4')); + $mock = new DatabaseMigrationMock(); - $this->assertFalse($mock->checkVersionUp('5.0.5')); + $this->assertEquals('5.0.5', $mock->getVersion()); + } - $this->assertFalse($mock->checkVersionUp('5.0.6')); - $this->assertFalse($mock->checkVersionUp('5.1')); - $this->assertFalse($mock->checkVersionUp('6.0')); + public function testCheckVersionUp() + { - } + $mock = new DatabaseMigrationMock(); - public function testCheckVersionUpLimit() { + $this->assertTrue($mock->checkVersionUp('4.15.15')); + $this->assertTrue($mock->checkVersionUp('5.0')); + $this->assertTrue($mock->checkVersionUp('5.0.4')); - $mock = new DatabaseMigrationMock(); + $this->assertFalse($mock->checkVersionUp('5.0.5')); - $this->assertTrue($mock->checkVersionUp('5.0.4', '5.0.5')); - $this->assertTrue($mock->checkVersionUp('5.0.3', '5.0.6')); - $this->assertFalse($mock->checkVersionUp('5.0.4', '5.0.4')); - $this->assertFalse($mock->checkVersionUp('5.0.3', '5.0.4')); + $this->assertFalse($mock->checkVersionUp('5.0.6')); + $this->assertFalse($mock->checkVersionUp('5.1')); + $this->assertFalse($mock->checkVersionUp('6.0')); + } - $this->assertFalse($mock->checkVersionUp('5.0.5', '5.0.2')); - $this->assertFalse($mock->checkVersionUp('5.0.5', '5.0.6')); + public function testCheckVersionUpLimit() + { - $this->assertFalse($mock->checkVersionUp('5.0.6', '5.0.2')); - $this->assertFalse($mock->checkVersionUp('5.0.6', '5.0.6')); + $mock = new DatabaseMigrationMock(); - } + $this->assertTrue($mock->checkVersionUp('5.0.4', '5.0.5')); + $this->assertTrue($mock->checkVersionUp('5.0.3', '5.0.6')); + $this->assertFalse($mock->checkVersionUp('5.0.4', '5.0.4')); + $this->assertFalse($mock->checkVersionUp('5.0.3', '5.0.4')); - public function testCheckVersionDown() { + $this->assertFalse($mock->checkVersionUp('5.0.5', '5.0.2')); + $this->assertFalse($mock->checkVersionUp('5.0.5', '5.0.6')); - $mock = new DatabaseMigrationMock(); + $this->assertFalse($mock->checkVersionUp('5.0.6', '5.0.2')); + $this->assertFalse($mock->checkVersionUp('5.0.6', '5.0.6')); + } - $this->assertFalse($mock->checkVersionDown('5.0', '4.15.1'), 'Current version is below mock migration version'); - $this->assertFalse($mock->checkVersionDown('5.0', '5.0.5'), 'Current version is below mock migration version'); - $this->assertFalse($mock->checkVersionDown('5.0', '5.2'), 'Current version is below mock migration version'); + public function testCheckVersionDown() + { - $this->assertTrue($mock->checkVersionDown('5.0.5', '4.15.1'), 'Current version matches, target is below'); - $this->assertFalse($mock->checkVersionDown('5.0.5', '5.0.5'), 'Current version matches, but target is same'); - $this->assertFalse($mock->checkVersionDown('5.0.5', '5.1'), 'Current version matches, but target is above'); + $mock = new DatabaseMigrationMock(); - $this->assertTrue($mock->checkVersionDown('5.1', '4.16'), 'Current version ahead of mock and target is below'); - $this->assertFalse($mock->checkVersionDown('5.1', '5.0.5'), 'Current version ahead of mock but target is same'); - $this->assertFalse($mock->checkVersionDown('5.1', '5.1.1'), 'Current version ahead of mock but target is above'); + $this->assertFalse($mock->checkVersionDown('5.0', '4.15.1'), 'Current version is below mock migration version'); + $this->assertFalse($mock->checkVersionDown('5.0', '5.0.5'), 'Current version is below mock migration version'); + $this->assertFalse($mock->checkVersionDown('5.0', '5.2'), 'Current version is below mock migration version'); - } + $this->assertTrue($mock->checkVersionDown('5.0.5', '4.15.1'), 'Current version matches, target is below'); + $this->assertFalse($mock->checkVersionDown('5.0.5', '5.0.5'), 'Current version matches, but target is same'); + $this->assertFalse($mock->checkVersionDown('5.0.5', '5.1'), 'Current version matches, but target is above'); + $this->assertTrue($mock->checkVersionDown('5.1', '4.16'), 'Current version ahead of mock and target is below'); + $this->assertFalse($mock->checkVersionDown('5.1', '5.0.5'), 'Current version ahead of mock but target is same'); + $this->assertFalse($mock->checkVersionDown('5.1', '5.1.1'), 'Current version ahead of mock but target is above'); + } } diff --git a/tests/DatabaseObjectTest.php b/tests/DatabaseObjectTest.php index 2a28aa2..0a165cd 100644 --- a/tests/DatabaseObjectTest.php +++ b/tests/DatabaseObjectTest.php @@ -1,113 +1,267 @@ getMock('Firelit\Query', array('insert', 'getNewId')); + $queryStub = $this->createMock('Firelit\Query', array('insert', 'getNewId')); - $queryStub->expects($this->once()) - ->method('insert'); + $queryStub->expects($this->once()) + ->method('insert'); - $queryStub->expects($this->once()) - ->method('getNewId') - ->will( $this->returnValue( $newId = mt_rand(100,1000) ) ); + $queryStub->expects($this->once()) + ->method('getNewId') + ->will($this->returnValue($newId = mt_rand(100, 1000))); - $do = new Firelit\DatabaseObject(array( - 'test' => '123' - )); + $do = new DatabaseObject(array( + 'test' => '123' + )); - $do->setQueryObject($queryStub); + $do->setQueryObject($queryStub); - $this->assertTrue($do->isNew()); + $this->assertTrue($do->isNew()); - $do->save(); + $do->save(); - $this->assertEquals($newId, $do->id); + $this->assertEquals($newId, $do->id); + } - } + public function testNewFlag() + { - public function testNewFlag() { + $queryStub = $this->createMock('Firelit\Query', array('update')); - $queryStub = $this->getMock('Firelit\Query', array('update')); + $queryStub->expects($this->once()) + ->method('update'); - $queryStub->expects($this->once()) - ->method('update'); + $do = new DatabaseObject(array( + 'id' => 3, + 'test' => '123' + )); - $do = new Firelit\DatabaseObject(array( - 'id' => 3, - 'test' => '123' - )); + $do->setQueryObject($queryStub); - $do->setQueryObject($queryStub); + $do->setNotNew(); - $do->setNotNew(); + $this->assertFalse($do->isNew()); - $this->assertFalse($do->isNew()); + $do->test = '321'; - $do->test = '321'; + $do->save(); + } - $do->save(); + public function testDirty() + { - } + $do = new DatabaseObject(array( + 'name' => 'John Doe', + 'age' => 28, + 'sex' => 'M' + )); - public function testDirty() { + $do->setNotNew(); - $do = new Firelit\DatabaseObject(array( - 'name' => 'John Doe', - 'age' => 28, - 'sex' => 'M' - )); + $this->assertEquals($do->age, 28); - $do->setNotNew(); + // Change 1st parameter + $do->age = 32; - $this->assertEquals($do->age, 28); + $this->assertEquals($do->age, 32); - // Change 1st parameter - $do->age = 32; + $dirty = $do->getDirty(); - $this->assertEquals($do->age, 32); + $this->assertEquals(sizeof($dirty), 1); + $this->assertEquals($dirty[0], 'age'); - $dirty = $do->getDirty(); + // Change a second parameter + $do->sex = 'F'; - $this->assertEquals(sizeof($dirty), 1); - $this->assertEquals($dirty[0], 'age'); + $dirty = $do->getDirty(); - // Change a second parameter - $do->sex = 'F'; + $this->assertEquals(sizeof($dirty), 2); + $this->assertEquals($dirty[1], 'sex'); + } - $dirty = $do->getDirty(); + public function testSerialization() + { - $this->assertEquals(sizeof($dirty), 2); - $this->assertEquals($dirty[1], 'sex'); + $to = new TestObject(array( + 'test' => '123', + 'serialize' => $class = new \stdClass(), + 'jsonize' => array(4, 5, 6) + )); - } + $this->assertEquals($to->test, '123'); + $this->assertSame($to->serialize, $class); + $this->assertEquals($to->jsonize, array(4, 5, 6)); + $this->assertEquals($to->jsonize[2], 6); + } - public function testSerialization() { + public function testFindBy() + { - $to = new TestObject(array( - 'test' => '123', - 'serialize' => $class = new stdClass(), - 'jsonize' => array(4, 5, 6) - )); + $queryStub = $this->createMock('Firelit\Query', array('query', 'getObject')); - $this->assertEquals($to->test, '123'); - $this->assertSame($to->serialize, $class); - $this->assertEquals($to->jsonize, array(4, 5, 6)); - $this->assertEquals($to->jsonize[2], 6); + $queryStub->expects($this->once()) + ->method('query') + ->with( + $this->stringContains('SELECT * FROM `TableName` WHERE `email`'), + $this->callback(function ($subject) { + // Make sure the 2nd param is an array + if (!is_array($subject)) { + return false; + } - } + if (sizeof($subject) != 1) { + return false; + } + // Make sure the primary key value is in the 2nd param + $key = key($subject); -} \ No newline at end of file + if ($subject[$key] != 'test@test.com') { + return false; + } + + // All good + return true; + }) + ); + + $queryStub->expects($this->once()) + ->method('getObject') + ->with($this->equalTo('Firelit\TestObject')) + ->will($this->returnValue(new TestObject())); + + TestObject::setQueryObject($queryStub); + + $to = TestObject::findBy('email', 'test@test.com'); + + $this->assertTrue($to instanceof QueryIterator); + + foreach ($to as $oneTo) { + $this->assertTrue($oneTo instanceof TestObject); + break; + } + + // Test with arrays: + + $queryStub = $this->createMock('Firelit\Query', array('query', 'getObject')); + + $queryStub->expects($this->once()) + ->method('query') + ->with( + $this->stringContains('SELECT * FROM `TableName` WHERE'), + $this->callback(function ($subject) { + // Make sure the 2nd param is an array + if (!is_array($subject)) { + return false; + } + if (sizeof($subject) != 2) { + return false; + } + // Make sure the primary key value is in the 2nd param + $key = key($subject); + if ($subject[$key] != 'john') { + return false; + } + // Make sure the primary key value is in the 2nd param + next($subject); + $key = key($subject); + if ($subject[$key] != 'test@test.com') { + return false; + } + // All good + return true; + }) + ); + + $queryStub->expects($this->once()) + ->method('getObject') + ->with($this->equalTo('Firelit\TestObject')) + ->will($this->returnValue(new TestObject())); + + TestObject::setQueryObject($queryStub); + + $to = TestObject::findBy(array('name', 'email'), array('john', 'test@test.com')); + + $this->assertTrue($to instanceof QueryIterator); + + foreach ($to as $oneTo) { + $this->assertTrue($oneTo instanceof TestObject); + break; + } + + try { + $to = TestObject::findBy(array('test', 'test2'), 'nope'); + $exception = false; + } catch (\Exception $e) { + $exception = true; + } + + $this->assertTrue($exception, 'Exception expected due to searching multiple columns with singular value'); + } + + public function testFind() + { + + $queryStub = $this->createMock('Firelit\Query', array('query', 'getObject')); + + $queryStub->expects($this->once()) + ->method('query') + ->with( + $this->stringContains('SELECT * FROM `TableName` WHERE `id`'), + $this->callback(function ($subject) { + // Make sure the 2nd param is an array + if (!is_array($subject)) { + return false; + } + // Make sure the primary key value is in the 2nd param + $key = key($subject); + if ($subject[$key] != 100) { + return false; + } + // All good + return true; + }) + ); + + $queryStub->expects($this->once()) + ->method('getObject') + ->with($this->equalTo('Firelit\TestObject')) + ->will($this->returnValue(new TestObject())); + + TestObject::setQueryObject($queryStub); + + $to = TestObject::find(100); + + $this->assertTrue($to instanceof TestObject); + + try { + $to = TestObject::find(array(1, 2)); + $exception = false; + } catch (\Exception $e) { + $exception = true; + } + + $this->assertTrue($exception, 'Exception expected due to searching array with singular PK'); + } +} diff --git a/tests/InputValidatorTest.php b/tests/InputValidatorTest.php index b400052..07fc7dc 100644 --- a/tests/InputValidatorTest.php +++ b/tests/InputValidatorTest.php @@ -1,505 +1,475 @@ assertEquals(false, $res); + // Static versions of called functions + $res = InputValidator::validate(InputValidator::EMAIL, 'test@test'); + $this->assertEquals(false, $res); - $res = InputValidator::validate(InputValidator::EMAIL, 'test@test.com'); - $this->assertEquals(true, $res); + $res = InputValidator::validate(InputValidator::EMAIL, 'test@test.com'); + $this->assertEquals(true, $res); + } - } + public function testNotRequired() + { - public function testNotRequired() { + // Fails, blank but required + $iv = new InputValidator(InputValidator::STATE, '', 'US'); + $this->assertEquals(false, $iv->isValid()); - // Fails, blank but required - $iv = new InputValidator(InputValidator::STATE, '', 'US'); - $this->assertEquals(false, $iv->isValid()); + // Blank ok, not required + $iv = new InputValidator(InputValidator::STATE, '', 'US'); + $iv->setRequired(false); + $this->assertEquals(true, $iv->isValid()); + } - // Blank ok, not required - $iv = new InputValidator(InputValidator::STATE, '', 'US'); - $iv->setRequired(false); - $this->assertEquals(true, $iv->isValid()); + public function testName() + { - } + // Valid name + $iv = new InputValidator(InputValidator::NAME, 'JOHN doe'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('John Doe', $iv->getNormalized()); - public function testName() { + // Valid name with accent + $iv = new InputValidator(InputValidator::NAME, 'Juán Doe'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('Juán Doe', $iv->getNormalized()); - // Valid name - $iv = new InputValidator(InputValidator::NAME, 'JOHN doe'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('John Doe', $iv->getNormalized()); + // Valid name with accent and abbreviation + $iv = new InputValidator(InputValidator::NAME, 'juan s.'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('Juan S.', $iv->getNormalized()); - // Valid name with accent - $iv = new InputValidator(InputValidator::NAME, 'Juán Doe'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('Juán Doe', $iv->getNormalized()); + // Invalid name + $iv = new InputValidator(InputValidator::NAME, '123Sam'); + $this->assertEquals(false, $iv->isValid()); - // Valid name with accent and abbreviation - $iv = new InputValidator(InputValidator::NAME, 'juan s.'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('Juan S.', $iv->getNormalized()); + // Name normalization + $iv = new InputValidator(InputValidator::NAME, 'JOHN doe-dream ii'); + $this->assertEquals('John Doe-Dream II', $iv->getNormalized()); - // Invalid name - $iv = new InputValidator(InputValidator::NAME, '123Sam'); - $this->assertEquals(false, $iv->isValid()); + $iv = new InputValidator(InputValidator::NAME, 'Sam and John Smith'); + $this->assertEquals('Sam and John Smith', $iv->getNormalized()); - // Name normalization - $iv = new InputValidator(InputValidator::NAME, 'j. d. doe i'); - $this->assertEquals('J. D. Doe I', $iv->getNormalized()); + $iv = new InputValidator(InputValidator::NAME, 'Sam+John Smith'); + $this->assertEquals('Sam & John Smith', $iv->getNormalized()); - $iv = new InputValidator(InputValidator::NAME, 'JOHN doe-dream ii'); - $this->assertEquals('John Doe-Dream II', $iv->getNormalized()); + $iv = new InputValidator(InputValidator::NAME, 'Sam&John Smith'); + $this->assertEquals('Sam & John Smith', $iv->getNormalized()); - $iv = new InputValidator(InputValidator::NAME, 'John mcdonald iii'); - $this->assertEquals('John McDonald III', $iv->getNormalized()); + // See Firelit\Strings for more name normaliztion tests + } - $iv = new InputValidator(InputValidator::NAME, 'John VanPlaat iV'); - $this->assertEquals('John VanPlaat IV', $iv->getNormalized()); + public function testOrgName() + { - $iv = new InputValidator(InputValidator::NAME, 'JOHN doe SR'); - $this->assertEquals('John Doe Sr', $iv->getNormalized()); + // Valid org name + $iv = new InputValidator(InputValidator::ORG_NAME, 'First Church'); + $this->assertEquals(true, $iv->isValid()); - $iv = new InputValidator(InputValidator::NAME, 'JOHN DeBoer j.r.'); - $this->assertEquals('John DeBoer Jr', $iv->getNormalized()); + $iv = new InputValidator(InputValidator::ORG_NAME, 'GO'); + $this->assertEquals(true, $iv->isValid()); - $iv = new InputValidator(InputValidator::NAME, 'john di\'vinici'); - $this->assertEquals('John Di\'Vinici', $iv->getNormalized()); + $iv = new InputValidator(InputValidator::ORG_NAME, 'GO!'); + $this->assertEquals(true, $iv->isValid()); - $iv = new InputValidator(InputValidator::NAME, 'Sam + John Smith'); - $this->assertEquals('Sam & John Smith', $iv->getNormalized()); + $iv = new InputValidator(InputValidator::ORG_NAME, 'GO (now)'); + $this->assertEquals(true, $iv->isValid()); - $iv = new InputValidator(InputValidator::NAME, 'Sam and John Smith'); - $this->assertEquals('Sam and John Smith', $iv->getNormalized()); + $iv = new InputValidator(InputValidator::ORG_NAME, '127 Foundation, Inc.'); + $this->assertEquals(true, $iv->isValid()); - $iv = new InputValidator(InputValidator::NAME, 'Sam+John Smith'); - $this->assertEquals('Sam & John Smith', $iv->getNormalized()); + $iv = new InputValidator(InputValidator::ORG_NAME, 'Strait to 123'); + $this->assertEquals(true, $iv->isValid()); - $iv = new InputValidator(InputValidator::NAME, 'Sam&John Smith'); - $this->assertEquals('Sam & John Smith', $iv->getNormalized()); + // Name normalization + $iv = new InputValidator(InputValidator::ORG_NAME, 'firelit design llc'); + $this->assertEquals('Firelit Design LLC', $iv->getNormalized()); - } + $iv = new InputValidator(InputValidator::ORG_NAME, 'halopays inc.'); + $this->assertEquals('Halopays Inc.', $iv->getNormalized()); - public function testOrgName() { + // Invalid org name + $iv = new InputValidator(InputValidator::ORG_NAME, '1'); + $this->assertEquals(false, $iv->isValid()); - // Valid org name - $iv = new InputValidator(InputValidator::ORG_NAME, 'First Church'); - $this->assertEquals(true, $iv->isValid()); + $iv = new InputValidator(InputValidator::ORG_NAME, ','); + $this->assertEquals(false, $iv->isValid()); + } - $iv = new InputValidator(InputValidator::ORG_NAME, 'GO'); - $this->assertEquals(true, $iv->isValid()); + public function testCity() + { - $iv = new InputValidator(InputValidator::ORG_NAME, 'GO!'); - $this->assertEquals(true, $iv->isValid()); + // Valid city + $iv = new InputValidator(InputValidator::CITY, 'maryville'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('Maryville', $iv->getNormalized()); + + // Invalid name + $iv = new InputValidator(InputValidator::CITY, '123Sam'); + $this->assertEquals(false, $iv->isValid()); + } - $iv = new InputValidator(InputValidator::ORG_NAME, 'GO (now)'); - $this->assertEquals(true, $iv->isValid()); - $iv = new InputValidator(InputValidator::ORG_NAME, '127 Foundation, Inc.'); - $this->assertEquals(true, $iv->isValid()); + public function testEmail() + { + + // Valid email address + $iv = new InputValidator(InputValidator::EMAIL, 'test-test.test@test-email.com'); + $this->assertEquals(true, $iv->isValid()); + + // Valid email address + $iv = new InputValidator(InputValidator::EMAIL, 'test_test55@test.what.NET'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('test_test55@test.what.net', $iv->getNormalized()); - $iv = new InputValidator(InputValidator::ORG_NAME, 'Strait to 123'); - $this->assertEquals(true, $iv->isValid()); + // Invalid email address + $iv = new InputValidator(InputValidator::EMAIL, 'test55@test'); + $this->assertEquals(false, $iv->isValid()); - // Name normalization - $iv = new InputValidator(InputValidator::ORG_NAME, 'firelit design llc'); - $this->assertEquals('Firelit Design LLC', $iv->getNormalized()); + // Invalid email address + $iv = new InputValidator(InputValidator::EMAIL, 'test(55)@test'); + $this->assertEquals(false, $iv->isValid()); + } + + public function testCreditCardNumber() + { - $iv = new InputValidator(InputValidator::ORG_NAME, 'halopays inc.'); - $this->assertEquals('Halopays Inc.', $iv->getNormalized()); + // Valid VISA credit card number + $iv = new InputValidator(InputValidator::CREDIT_ACCT, '4111111111111111'); + $this->assertTrue($iv->isValid()); - // Invalid org name - $iv = new InputValidator(InputValidator::ORG_NAME, '1'); - $this->assertEquals(false, $iv->isValid()); + // Valid AMEX credit card number + $iv = new InputValidator(InputValidator::CREDIT_ACCT, '370000000000002'); + $this->assertTrue($iv->isValid()); + $this->assertEquals('xxxxxxxxxxx0002', $iv->getNormalized()); - $iv = new InputValidator(InputValidator::ORG_NAME, ','); - $this->assertEquals(false, $iv->isValid()); - - } - - public function testCity() { - - // Valid city - $iv = new InputValidator(InputValidator::CITY, 'maryville'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('Maryville', $iv->getNormalized()); - - // Invalid name - $iv = new InputValidator(InputValidator::CITY, '123Sam'); - $this->assertEquals(false, $iv->isValid()); - - } - - - public function testEmail() { - - // Valid email address - $iv = new InputValidator(InputValidator::EMAIL, 'test-test.test@test-email.com'); - $this->assertEquals(true, $iv->isValid()); - - // Valid email address - $iv = new InputValidator(InputValidator::EMAIL, 'test_test55@test.what.NET'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('test_test55@test.what.net', $iv->getNormalized()); - - // Invalid email address - $iv = new InputValidator(InputValidator::EMAIL, 'test55@test'); - $this->assertEquals(false, $iv->isValid()); - - // Invalid email address - $iv = new InputValidator(InputValidator::EMAIL, 'test(55)@test'); - $this->assertEquals(false, $iv->isValid()); - - } - - public function testCreditCardNumber() { - - // Valid VISA credit card number - $iv = new InputValidator(InputValidator::CREDIT_ACCT, '4111111111111111'); - $this->assertTrue($iv->isValid()); - - // Valid AMEX credit card number - $iv = new InputValidator(InputValidator::CREDIT_ACCT, '370000000000002'); - $this->assertTrue($iv->isValid()); - $this->assertEquals('xxxxxxxxxxx0002', $iv->getNormalized()); - - // Valid MC credit card number - $iv = new InputValidator(InputValidator::CREDIT_ACCT, '5454 5454 5454 5454'); - $this->assertTrue($iv->isValid()); - $this->assertEquals('5454545454545454', $iv->getNormalized(InputValidator::TYPE_GATEWAY)); - - // Invalid credit card number - $iv = new InputValidator(InputValidator::CREDIT_ACCT, '4111 1111 1111 1112'); - $this->assertFalse($iv->isValid()); - - // Invalid credit card number - $iv = new InputValidator(InputValidator::CREDIT_ACCT, '1111 2222 3333 4444'); - $this->assertFalse($iv->isValid()); - - } - - public function testExpirationDates() { - - // Valid expiration date - $iv = new InputValidator(InputValidator::CREDIT_EXP, '1217'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('12/17', $iv->getNormalized()); - $this->assertEquals('1217', $iv->getNormalized(InputValidator::TYPE_GATEWAY)); - $this->assertEquals('2017-12-31', $iv->getNormalized(InputValidator::TYPE_DB)); - - // Valid expiration date - $iv = new InputValidator(InputValidator::CREDIT_EXP, '5/18'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('0518', $iv->getNormalized(InputValidator::TYPE_GATEWAY)); - - // Valid expiration date - $iv = new InputValidator(InputValidator::CREDIT_EXP, '01/16'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('01/16', $iv->getNormalized()); - $this->assertEquals('2016-01-31', $iv->getNormalized(InputValidator::TYPE_DB)); - - // Invalid expiration date - $iv = new InputValidator(InputValidator::CREDIT_EXP, '1317'); - $this->assertEquals(false, $iv->isValid()); - - // Invalid expiration date - $iv = new InputValidator(InputValidator::CREDIT_EXP, '0016'); - $this->assertEquals(false, $iv->isValid()); - - } - - public function testSecurityCode() { - - // Valid security code - $iv = new InputValidator(InputValidator::CREDIT_CVV, '123'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('xxx', $iv->getNormalized()); - $this->assertEquals('123', $iv->getNormalized(InputValidator::TYPE_GATEWAY)); - - // Valid security code - $iv = new InputValidator(InputValidator::CREDIT_CVV, '0234'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('xxxx', $iv->getNormalized()); - $this->assertEquals('0234', $iv->getNormalized(InputValidator::TYPE_GATEWAY)); - - // Invalid security code - $iv = new InputValidator(InputValidator::CREDIT_CVV, '00'); - $this->assertEquals(false, $iv->isValid()); - - } - - public function testRoutingNumber() { - - // Valid routing number - $iv = new InputValidator(InputValidator::ACH_ROUT, '123123123'); - $this->assertEquals(true, $iv->isValid()); - - // Invalid routing number - $iv = new InputValidator(InputValidator::ACH_ROUT, '123123124'); - $this->assertEquals(false, $iv->isValid()); - - } - - public function testBankAccountNumber() { - - // Valid account number - $iv = new InputValidator(InputValidator::ACH_ACCT, '33213321'); - $this->assertEquals(true, $iv->isValid()); - - // Valid account number - $iv = new InputValidator(InputValidator::ACH_ACCT, '3321-3321'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('xxxx3321', $iv->getNormalized()); - $this->assertEquals('33213321', $iv->getNormalized(InputValidator::TYPE_GATEWAY)); - - // Invalid character - $iv = new InputValidator(InputValidator::ACH_ACCT, '3321@3321'); - $this->assertEquals(false, $iv->isValid()); - - // Valid - $iv = new InputValidator(InputValidator::ACH_ACCT, '3321'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('xx21', $iv->getNormalized()); - - // Too short (3 chars) - $iv = new InputValidator(InputValidator::ACH_ACCT, '332'); - $this->assertEquals(false, $iv->isValid()); - - // Too long (30 chars) - $iv = new InputValidator(InputValidator::ACH_ACCT, '123456789012345678901234567890'); - $this->assertEquals(false, $iv->isValid()); - - } - - public function testBankAccountType() { - - // Valid type: checking - $iv = new InputValidator(InputValidator::ACH_TYPE, 'C'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('Checking', $iv->getNormalized()); - $this->assertEquals('C', $iv->getNormalized(InputValidator::TYPE_DB)); - - // Valid type: checking - $iv = new InputValidator(InputValidator::ACH_TYPE, 'checking'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('Checking', $iv->getNormalized()); - $this->assertEquals('C', $iv->getNormalized(InputValidator::TYPE_DB)); - - // Valid type: savings - $iv = new InputValidator(InputValidator::ACH_TYPE, 'savings'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('Savings', $iv->getNormalized()); - $this->assertEquals('S', $iv->getNormalized(InputValidator::TYPE_DB)); - - // Invalid type - $iv = new InputValidator(InputValidator::ACH_TYPE, 'other'); - $this->assertEquals(false, $iv->isValid()); - - } - - public function testAddress() { - - // Invalid US address - $iv = new InputValidator(InputValidator::ADDRESS, 'North', 'US'); - $this->assertEquals(false, $iv->isValid()); - - // Valid US address - $iv = new InputValidator(InputValidator::ADDRESS, '123 NORTH AVENUE SE', 'US'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('123 North Avenue SE', $iv->getNormalized()); - - // Directional normalization - $iv = new InputValidator(InputValidator::ADDRESS, '123 Upper Ave. s.e.', 'US'); - $this->assertEquals('123 Upper Ave. SE', $iv->getNormalized()); - - $iv = new InputValidator(InputValidator::ADDRESS, '123 Upper Ave. Ne.', 'US'); - $this->assertEquals('123 Upper Ave. NE', $iv->getNormalized()); - - $iv = new InputValidator(InputValidator::ADDRESS, '123 Upper Ave. sw', 'US'); - $this->assertEquals('123 Upper Ave. SW', $iv->getNormalized()); - - $iv = new InputValidator(InputValidator::ADDRESS, '123 Upper Ave. NW', 'US'); - $this->assertEquals('123 Upper Ave. NW', $iv->getNormalized()); - - // Unique count-letter address - $iv = new InputValidator(InputValidator::ADDRESS, 'A-123 Front St', 'US'); - $this->assertEquals(true, $iv->isValid()); - - // Valid no-region address - $iv = new InputValidator(InputValidator::ADDRESS, 'Field 12'); - $this->assertEquals(true, $iv->isValid()); - - // PO Box normalization - $iv = new InputValidator(InputValidator::ADDRESS, 'po box 123'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('PO Box 123', $iv->getNormalized()); - - $iv = new InputValidator(InputValidator::ADDRESS, 'p.o. BOX 123'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('PO Box 123', $iv->getNormalized()); - - $iv = new InputValidator(InputValidator::ADDRESS, 'Po. BOX 123'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('PO Box 123', $iv->getNormalized()); - - } - - public function testPhone() { - - // Valid US phone - $iv = new InputValidator(InputValidator::PHONE, '6165551234', 'US'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('(616) 555-1234', $iv->getNormalized()); - - // US phone with country code - $iv = new InputValidator(InputValidator::PHONE, '+16165551234', 'US'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('(616) 555-1234', $iv->getNormalized()); - - // Invalid US phone (not enough digits) - $iv = new InputValidator(InputValidator::PHONE, '1165551234', 'US'); - $this->assertEquals(false, $iv->isValid()); - - // International - $iv = new InputValidator(InputValidator::PHONE, '(11) 655-5123'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('(11) 655-5123', $iv->getNormalized()); - - // Not required, blank - $iv = new InputValidator(InputValidator::PHONE, '', 'US'); - $iv->setRequired(false); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('', $iv->getNormalized()); - - } - - public function testState() { - - // Valid US state - $iv = new InputValidator(InputValidator::STATE, 'CA', 'US'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('CA', $iv->getNormalized()); - - // Valid lower case US state - $iv = new InputValidator(InputValidator::STATE, 'ca', 'US'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('CA', $iv->getNormalized()); - - // Not a Canadian province - $iv = new InputValidator(InputValidator::STATE, 'MI', 'CA'); - $this->assertEquals(false, $iv->isValid()); - - // Valid Canadian province - $iv = new InputValidator(InputValidator::STATE, 'BC', 'CA'); - $this->assertEquals(true, $iv->isValid()); - - } - - public function testZip() { - - // No-region zip - $iv = new InputValidator(InputValidator::ZIP, '91101'); - $this->assertEquals(true, $iv->isValid()); - - // Valid US zip - $iv = new InputValidator(InputValidator::ZIP, '91101', 'US'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('91101', $iv->getNormalized()); - - // Valid US zip - $iv = new InputValidator(InputValidator::ZIP, '91101-1234', 'US'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('91101-1234', $iv->getNormalized()); - - // Invalid Candian zip - $iv = new InputValidator(InputValidator::ZIP, '91101', 'CA'); - $this->assertEquals(false, $iv->isValid()); - - // No-region zip - $iv = new InputValidator(InputValidator::ZIP, 'K1A 0B1'); - $this->assertEquals(true, $iv->isValid()); - - // Invalid US zip - $iv = new InputValidator(InputValidator::ZIP, '91101-123', 'US'); - $this->assertEquals(false, $iv->isValid()); - - // Invalid US zip - $iv = new InputValidator(InputValidator::ZIP, 'K1A 0B1', 'US'); - $this->assertEquals(false, $iv->isValid()); - - // Valid Candian zip - $iv = new InputValidator(InputValidator::ZIP, 'K1A 0B1', 'CA'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('K1A 0B1', $iv->getNormalized()); - - // Valid unformated Candian zip - $iv = new InputValidator(InputValidator::ZIP, 'K1a0B1', 'CA'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('K1A 0B1', $iv->getNormalized()); - - } - - public function testCountry() { - - // Valid country - $iv = new InputValidator(InputValidator::COUNTRY, 'US'); - $this->assertEquals(true, $iv->isValid()); - - // Invalid country - $iv = new InputValidator(InputValidator::COUNTRY, 'ZZ'); - $this->assertEquals(false, $iv->isValid()); - - } - - public function testURL() { - - // No-http website - $iv = new InputValidator(InputValidator::URL, 'www.yahoo.com'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('http://www.yahoo.com/', $iv->getNormalized()); - - // Website with a port - $iv = new InputValidator(InputValidator::URL, 'www.firelit.com:8080/index.html'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('http://www.firelit.com:8080/index.html', $iv->getNormalized()); - - // No-http website with path - $iv = new InputValidator(InputValidator::URL, 'yahoo.com/some/link.php'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('http://yahoo.com/some/link.php', $iv->getNormalized()); - - // http website - $iv = new InputValidator(InputValidator::URL, 'http://www.GOOGLE.com/'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('http://www.google.com/', $iv->getNormalized()); - - // http website with path - $iv = new InputValidator(InputValidator::URL, 'http://www.GOOGLE.com/THIS/thing.html'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('http://www.google.com/THIS/thing.html', $iv->getNormalized()); - - // https website - $iv = new InputValidator(InputValidator::URL, 'https://slashdot.org'); - $this->assertEquals(true, $iv->isValid()); - $this->assertEquals('https://slashdot.org/', $iv->getNormalized()); - - // Invalid website - $iv = new InputValidator(InputValidator::URL, 'example'); - $this->assertEquals(false, $iv->isValid()); - - // Invalid website - $iv = new InputValidator(InputValidator::URL, 'ftp://example.net'); - $this->assertEquals(false, $iv->isValid()); - - // Invalid website - $iv = new InputValidator(InputValidator::URL, '/www.example.com'); - $this->assertEquals(false, $iv->isValid()); - - } - -} \ No newline at end of file + // Valid MC credit card number + $iv = new InputValidator(InputValidator::CREDIT_ACCT, '5454 5454 5454 5454'); + $this->assertTrue($iv->isValid()); + $this->assertEquals('5454545454545454', $iv->getNormalized(InputValidator::TYPE_GATEWAY)); + + // Invalid credit card number + $iv = new InputValidator(InputValidator::CREDIT_ACCT, '4111 1111 1111 1112'); + $this->assertFalse($iv->isValid()); + + // Invalid credit card number + $iv = new InputValidator(InputValidator::CREDIT_ACCT, '1111 2222 3333 4444'); + $this->assertFalse($iv->isValid()); + } + + public function testExpirationDates() + { + + // Valid expiration date + $iv = new InputValidator(InputValidator::CREDIT_EXP, '1217'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('12/17', $iv->getNormalized()); + $this->assertEquals('1217', $iv->getNormalized(InputValidator::TYPE_GATEWAY)); + $this->assertEquals('2017-12-31', $iv->getNormalized(InputValidator::TYPE_DB)); + + // Valid expiration date + $iv = new InputValidator(InputValidator::CREDIT_EXP, '5/18'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('0518', $iv->getNormalized(InputValidator::TYPE_GATEWAY)); + + // Valid expiration date + $iv = new InputValidator(InputValidator::CREDIT_EXP, '01/16'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('01/16', $iv->getNormalized()); + $this->assertEquals('2016-01-31', $iv->getNormalized(InputValidator::TYPE_DB)); + + // Invalid expiration date + $iv = new InputValidator(InputValidator::CREDIT_EXP, '1317'); + $this->assertEquals(false, $iv->isValid()); + + // Invalid expiration date + $iv = new InputValidator(InputValidator::CREDIT_EXP, '0016'); + $this->assertEquals(false, $iv->isValid()); + } + + public function testSecurityCode() + { + + // Valid security code + $iv = new InputValidator(InputValidator::CREDIT_CVV, '123'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('xxx', $iv->getNormalized()); + $this->assertEquals('123', $iv->getNormalized(InputValidator::TYPE_GATEWAY)); + + // Valid security code + $iv = new InputValidator(InputValidator::CREDIT_CVV, '0234'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('xxxx', $iv->getNormalized()); + $this->assertEquals('0234', $iv->getNormalized(InputValidator::TYPE_GATEWAY)); + + // Invalid security code + $iv = new InputValidator(InputValidator::CREDIT_CVV, '00'); + $this->assertEquals(false, $iv->isValid()); + } + + public function testRoutingNumber() + { + + // Valid routing number + $iv = new InputValidator(InputValidator::ACH_ROUT, '123123123'); + $this->assertEquals(true, $iv->isValid()); + + // Invalid routing number + $iv = new InputValidator(InputValidator::ACH_ROUT, '123123124'); + $this->assertEquals(false, $iv->isValid()); + } + + public function testBankAccountNumber() + { + + // Valid account number + $iv = new InputValidator(InputValidator::ACH_ACCT, '33213321'); + $this->assertEquals(true, $iv->isValid()); + + // Valid account number + $iv = new InputValidator(InputValidator::ACH_ACCT, '3321-3321'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('xxxx3321', $iv->getNormalized()); + $this->assertEquals('33213321', $iv->getNormalized(InputValidator::TYPE_GATEWAY)); + + // Invalid character + $iv = new InputValidator(InputValidator::ACH_ACCT, '3321@3321'); + $this->assertEquals(false, $iv->isValid()); + + // Valid + $iv = new InputValidator(InputValidator::ACH_ACCT, '3321'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('xx21', $iv->getNormalized()); + + // Too short (3 chars) + $iv = new InputValidator(InputValidator::ACH_ACCT, '332'); + $this->assertEquals(false, $iv->isValid()); + + // Too long (30 chars) + $iv = new InputValidator(InputValidator::ACH_ACCT, '123456789012345678901234567890'); + $this->assertEquals(false, $iv->isValid()); + } + + public function testBankAccountType() + { + + // Valid type: checking + $iv = new InputValidator(InputValidator::ACH_TYPE, 'C'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('Checking', $iv->getNormalized()); + $this->assertEquals('C', $iv->getNormalized(InputValidator::TYPE_DB)); + + // Valid type: checking + $iv = new InputValidator(InputValidator::ACH_TYPE, 'checking'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('Checking', $iv->getNormalized()); + $this->assertEquals('C', $iv->getNormalized(InputValidator::TYPE_DB)); + + // Valid type: savings + $iv = new InputValidator(InputValidator::ACH_TYPE, 'savings'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('Savings', $iv->getNormalized()); + $this->assertEquals('S', $iv->getNormalized(InputValidator::TYPE_DB)); + + // Invalid type + $iv = new InputValidator(InputValidator::ACH_TYPE, 'other'); + $this->assertEquals(false, $iv->isValid()); + } + + public function testAddress() + { + + // Invalid US address + $iv = new InputValidator(InputValidator::ADDRESS, 'North', 'US'); + $this->assertEquals(false, $iv->isValid()); + + // Valid US address + $iv = new InputValidator(InputValidator::ADDRESS, '123 NORTH AVENUE SE', 'US'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('123 North Avenue SE', $iv->getNormalized()); + + // Unique count-letter address + $iv = new InputValidator(InputValidator::ADDRESS, 'A-123 Front St', 'US'); + $this->assertEquals(true, $iv->isValid()); + + // Valid no-region address + $iv = new InputValidator(InputValidator::ADDRESS, 'Field 12'); + $this->assertEquals(true, $iv->isValid()); + + // PO Box normalization + $iv = new InputValidator(InputValidator::ADDRESS, 'po box 123'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('PO Box 123', $iv->getNormalized()); + + $iv = new InputValidator(InputValidator::ADDRESS, 'p.o. BOX 123'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('PO Box 123', $iv->getNormalized()); + + $iv = new InputValidator(InputValidator::ADDRESS, 'Po. BOX 123'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('PO Box 123', $iv->getNormalized()); + + // See Firelit\Strings for more address normaliztion tests + } + + public function testPhone() + { + + // Valid US phone + $iv = new InputValidator(InputValidator::PHONE, '6165551234', 'US'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('(616) 555-1234', $iv->getNormalized()); + + // US phone with country code + $iv = new InputValidator(InputValidator::PHONE, '+16165551234', 'US'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('(616) 555-1234', $iv->getNormalized()); + + // Invalid US phone (not enough digits) + $iv = new InputValidator(InputValidator::PHONE, '1165551234', 'US'); + $this->assertEquals(false, $iv->isValid()); + + // International + $iv = new InputValidator(InputValidator::PHONE, '(11) 655-5123'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('(11) 655-5123', $iv->getNormalized()); + + // Not required, blank + $iv = new InputValidator(InputValidator::PHONE, '', 'US'); + $iv->setRequired(false); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('', $iv->getNormalized()); + } + + public function testState() + { + + // Valid US state + $iv = new InputValidator(InputValidator::STATE, 'CA', 'US'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('CA', $iv->getNormalized()); + + // Valid lower case US state + $iv = new InputValidator(InputValidator::STATE, 'ca', 'US'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('CA', $iv->getNormalized()); + + // Not a Canadian province + $iv = new InputValidator(InputValidator::STATE, 'MI', 'CA'); + $this->assertEquals(false, $iv->isValid()); + + // Valid Canadian province + $iv = new InputValidator(InputValidator::STATE, 'BC', 'CA'); + $this->assertEquals(true, $iv->isValid()); + } + + public function testZip() + { + + // No-region zip + $iv = new InputValidator(InputValidator::ZIP, '91101'); + $this->assertEquals(true, $iv->isValid()); + + // Valid US zip + $iv = new InputValidator(InputValidator::ZIP, '91101', 'US'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('91101', $iv->getNormalized()); + + // Valid US zip + $iv = new InputValidator(InputValidator::ZIP, '91101-1234', 'US'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('91101-1234', $iv->getNormalized()); + + // Invalid Candian zip + $iv = new InputValidator(InputValidator::ZIP, '91101', 'CA'); + $this->assertEquals(false, $iv->isValid()); + + // No-region zip + $iv = new InputValidator(InputValidator::ZIP, 'K1A 0B1'); + $this->assertEquals(true, $iv->isValid()); + + // Invalid US zip + $iv = new InputValidator(InputValidator::ZIP, '91101-123', 'US'); + $this->assertEquals(false, $iv->isValid()); + + // Invalid US zip + $iv = new InputValidator(InputValidator::ZIP, 'K1A 0B1', 'US'); + $this->assertEquals(false, $iv->isValid()); + + // Valid Candian zip + $iv = new InputValidator(InputValidator::ZIP, 'K1A 0B1', 'CA'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('K1A 0B1', $iv->getNormalized()); + + // Valid unformated Candian zip + $iv = new InputValidator(InputValidator::ZIP, 'K1a0B1', 'CA'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('K1A 0B1', $iv->getNormalized()); + } + + public function testCountry() + { + + // Valid country + $iv = new InputValidator(InputValidator::COUNTRY, 'US'); + $this->assertEquals(true, $iv->isValid()); + + // Invalid country + $iv = new InputValidator(InputValidator::COUNTRY, 'ZZ'); + $this->assertEquals(false, $iv->isValid()); + } + + public function testURL() + { + + // No-http website + $iv = new InputValidator(InputValidator::URL, 'www.yahoo.com'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('http://www.yahoo.com/', $iv->getNormalized()); + + // Website with a port + $iv = new InputValidator(InputValidator::URL, 'www.firelit.com:8080/index.html'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('http://www.firelit.com:8080/index.html', $iv->getNormalized()); + + // No-http website with path + $iv = new InputValidator(InputValidator::URL, 'yahoo.com/some/link.php'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('http://yahoo.com/some/link.php', $iv->getNormalized()); + + // http website + $iv = new InputValidator(InputValidator::URL, 'http://www.GOOGLE.com/'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('http://www.google.com/', $iv->getNormalized()); + + // http website with path + $iv = new InputValidator(InputValidator::URL, 'http://www.GOOGLE.com/THIS/thing.html'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('http://www.google.com/THIS/thing.html', $iv->getNormalized()); + + // https website + $iv = new InputValidator(InputValidator::URL, 'https://slashdot.org'); + $this->assertEquals(true, $iv->isValid()); + $this->assertEquals('https://slashdot.org/', $iv->getNormalized()); + + // Invalid website + $iv = new InputValidator(InputValidator::URL, 'example'); + $this->assertEquals(false, $iv->isValid()); + + // Invalid website + $iv = new InputValidator(InputValidator::URL, 'ftp://example.net'); + $this->assertEquals(false, $iv->isValid()); + + // Invalid website + $iv = new InputValidator(InputValidator::URL, '/www.example.com'); + $this->assertEquals(false, $iv->isValid()); + } +} diff --git a/tests/QueryIteratorTest.php b/tests/QueryIteratorTest.php index 480d142..bce6cab 100644 --- a/tests/QueryIteratorTest.php +++ b/tests/QueryIteratorTest.php @@ -1,48 +1,51 @@ getMock('Firelit\Query', array('getRow')); - $qmock - ->method('getRow') - ->will($this->onConsecutiveCalls( - array( - 'id' => 1, - 'info' => 'yup' - ), - array( - 'id' => 2, - 'info' => 'uhhuh' - ), - false)); - - - $sut = new Firelit\QueryIterator($qmock); - - $sut->rewind(); - - $this->assertTrue($sut->valid(), 'The first value should be valid'); - $this->assertEquals(0, $sut->key(), 'The first index should be 0'); - $this->assertEquals(array( - 'id' => 1, - 'info' => 'yup' - ), $sut->current(), 'The first record should match'); - - $sut->next(); - - $this->assertTrue($sut->valid(), 'The second value should be valid'); - $this->assertEquals(1, $sut->key(), 'The second index should be 1'); - $this->assertEquals(array( - 'id' => 2, - 'info' => 'uhhuh' - ), $sut->current(), 'The second record should match'); - - $sut->next(); - - $this->assertFalse($sut->valid(), 'The third value should not be valid'); - - } - -} \ No newline at end of file +namespace Firelit; + +class QueryIteratorTest extends \PHPUnit_Framework_TestCase +{ + + public function testQueryIterator() + { + + $qmock = $this->createMock('Firelit\Query', array('getRow')); + $qmock + ->method('getRow') + ->will($this->onConsecutiveCalls( + array( + 'id' => 1, + 'info' => 'yup' + ), + array( + 'id' => 2, + 'info' => 'uhhuh' + ), + false + )); + + + $sut = new QueryIterator($qmock); + + $sut->rewind(); + + $this->assertTrue($sut->valid(), 'The first value should be valid'); + $this->assertEquals(0, $sut->key(), 'The first index should be 0'); + $this->assertEquals(array( + 'id' => 1, + 'info' => 'yup' + ), $sut->current(), 'The first record should match'); + + $sut->next(); + + $this->assertTrue($sut->valid(), 'The second value should be valid'); + $this->assertEquals(1, $sut->key(), 'The second index should be 1'); + $this->assertEquals(array( + 'id' => 2, + 'info' => 'uhhuh' + ), $sut->current(), 'The second record should match'); + + $sut->next(); + + $this->assertFalse($sut->valid(), 'The third value should not be valid'); + } +} diff --git a/tests/QueryTest.php b/tests/QueryTest.php index 1c72722..0fc56eb 100644 --- a/tests/QueryTest.php +++ b/tests/QueryTest.php @@ -1,135 +1,146 @@ 'other', - 'dsn' => 'sqlite::memory:' - )); - - } - - protected function setUp() { - - $this->q = new Firelit\Query(); - - } - - public function testQuery() { - - - $this->res = $this->q->query("CREATE TABLE IF NOT EXISTS `Tester` (`name` VARCHAR(10) PRIMARY KEY, `date` DATETIME, `state` BOOL)"); - $this->assertTrue($this->res); - - } - - /** - * @depends testQuery - */ - public function testInsert() { - - $this->q->insert('Tester', array( - 'name' => 'John', - 'date' => Firelit\Query::SQL("DATETIME('now')"), - 'state' => false - )); - - $this->assertTrue( $this->q->success() ); - $this->assertEquals( 1, $this->q->getAffected() ); - - - } - - /** - * @depends testQuery - */ - public function testReplace() { - - $this->q->replace('Tester', array( - 'name' => 'Sally', - 'date' => Firelit\Query::SQL("DATETIME('now')"), - 'state' => true - )); - - $this->assertTrue( $this->q->success() ); - $this->assertEquals( 1, $this->q->getAffected() ); - - - } - - /** - * @depends testReplace - */ - public function testSelect() { - - $sql = "SELECT * FROM `Tester` WHERE `name`=:name LIMIT 1"; - $this->q->query($sql, array(':name' => 'Sally')); - - $this->assertTrue( $this->q->success() ); - - $rows = $this->q->getAll(); - - $this->assertEquals( 1, sizeof($rows) ); - - $this->assertEquals( 'Sally', $rows[0]['name'] ); - - } - - /** - * @depends testInsert - */ - public function testUpdate() { - - $this->q->update('Tester', array( 'state' => true ), '`name`=:name', array( ':name' => 'John' )); - - $this->assertTrue( $this->q->success() ); - $this->assertEquals( 1, $this->q->getAffected() ); - - // Verify that data was written - $sql = "SELECT * FROM `Tester` WHERE `name`=:name LIMIT 1"; - $this->q->query($sql, array(':name' => 'John')); - - $this->assertTrue( $this->q->success() ); - - $row = $this->q->getRow(); - - $this->assertEquals( true, $row['state'] ); - - } - - /** - * @depends testUpdate - */ - public function testDelete() { - - $sql = "DELETE FROM `Tester` WHERE `name`=:name LIMIT 1"; - $this->q->query($sql, array(':name' => 'John')); - - $this->assertTrue( $this->q->success() ); - $this->assertEquals( 1, $this->q->getAffected() ); - - // Verify that data was deleted - $sql = "SELECT * FROM `Tester` WHERE `name`=:name LIMIT 1"; - $this->q->query($sql, array(':name' => 'John')); - - $this->assertTrue( $this->q->success() ); - - $row = $this->q->getRow(); - - $this->assertFalse( $row ); - - } - - public static function tearDownAfterClass() { - try { - // Why no work with sqlite? "Database error: 6, database table is locked" - $q = new Firelit\Query("DROP TABLE `Tester`"); - - } catch (Exception $e) { } - } -} \ No newline at end of file +namespace Firelit; + +class QueryTest extends \PHPUnit_Framework_TestCase +{ + + protected $q; + protected $res; + protected static $pdo; + + public static function setUpBeforeClass() + { + + Registry::set('database', array( + 'type' => 'other', + 'dsn' => 'sqlite::memory:' + )); + } + + protected function setUp() + { + + $this->q = new Query(); + } + + public function testQuery() + { + + + $this->res = $this->q->query("CREATE TABLE IF NOT EXISTS `Tester` (`name` VARCHAR(10) PRIMARY KEY, `date` DATETIME, `state` BOOL)"); + $this->assertTrue($this->res); + } + + /** + * @depends testQuery + */ + public function testInsert() + { + + $this->q->insert('Tester', array( + 'name' => 'John', + 'date' => Query::SQL("DATETIME('now')"), + 'state' => false + )); + + $this->assertTrue($this->q->success()); + $this->assertEquals(1, $this->q->getAffected()); + } + + /** + * @depends testQuery + */ + public function testReplace() + { + + $this->q->replace('Tester', array( + 'name' => 'Sally', + 'date' => Query::SQL("DATETIME('now')"), + 'state' => true + )); + + $this->assertTrue($this->q->success()); + $this->assertEquals(1, $this->q->getAffected()); + } + + /** + * @depends testReplace + */ + public function testSelect() + { + + $sql = "SELECT * FROM `Tester` WHERE `name`=:name LIMIT 1"; + $this->q->query($sql, array(':name' => 'Sally')); + + $this->assertTrue($this->q->success()); + + $rows = $this->q->getAll(); + + $this->assertEquals(1, sizeof($rows)); + + $this->assertEquals('Sally', $rows[0]['name']); + + $row = $this->q->getRow(); + + $this->assertFalse($row); + } + + /** + * @depends testInsert + */ + public function testUpdate() + { + + $this->q->update('Tester', array( 'state' => true ), '`name`=:name', array( ':name' => 'John' )); + + $this->assertTrue($this->q->success()); + $this->assertEquals(1, $this->q->getAffected()); + + // Verify that data was written + $sql = "SELECT * FROM `Tester` WHERE `name`=:name LIMIT 1"; + $this->q->query($sql, array(':name' => 'John')); + + $this->assertTrue($this->q->success()); + + $row = $this->q->getRow(); + + $this->assertEquals(true, $row['state']); + + $row = $this->q->getRow(); + + $this->assertFalse($row); + } + + /** + * @depends testUpdate + */ + public function testDelete() + { + + $sql = "DELETE FROM `Tester` WHERE `name`=:name"; + $this->q = new Query($sql, array(':name' => 'John')); + + $this->assertTrue($this->q->success()); + $this->assertEquals(1, $this->q->getAffected()); + + // Verify that data was deleted + $sql = "SELECT * FROM `Tester` WHERE `name`=:name LIMIT 1"; + $this->q->query($sql, array(':name' => 'John')); + + $this->assertTrue($this->q->success()); + + $row = $this->q->getRow(); + + $this->assertFalse($row); + } + + public static function tearDownAfterClass() + { + try { + // Why no work with sqlite? "Database error: 6, database table is locked" + $q = new Query("DROP TABLE `Tester`"); + } catch (Exception $e) { + } + } +} diff --git a/tests/RegistryTest.php b/tests/RegistryTest.php index 7dc2fc5..a39919a 100644 --- a/tests/RegistryTest.php +++ b/tests/RegistryTest.php @@ -1,34 +1,39 @@ assertEquals('value', Firelit\Registry::get('test')); + protected function setUp() + { - $this->assertTrue(Firelit\Registry::get('boolean')); + Registry::set('test', 'value'); + } - } + public function testGet() + { - public function testSet() { + Registry::set('boolean', true); - $r = new Firelit\Registry(); + $this->assertEquals('value', Registry::get('test')); - $r->set('Peter', 'Piper'); - $r->set('Red', 'Herring'); + $this->assertTrue(Registry::get('boolean')); + } - $this->assertEquals('Piper', $r->get('Peter')); + public function testSet() + { - } + $r = new Registry(); -} \ No newline at end of file + $r->set('Peter', 'Piper'); + $r->set('Red', 'Herring'); + + $this->assertEquals('Piper', $r->get('Peter')); + } +} diff --git a/tests/RequestTest.php b/tests/RequestTest.php index 0e71448..3463aa2 100644 --- a/tests/RequestTest.php +++ b/tests/RequestTest.php @@ -1,114 +1,118 @@ true, - 'tester' => array( - 0 => 'value0', - 1 => 'value1' - ) - ); + public function testConstructor() + { - $_GET = array(); - $_COOKIE = array(); + $_POST = $orig = array( + 'test' => true, + 'tester' => array( + 0 => 'value0', + 1 => 'value1' + ) + ); - $sr = new Firelit\Request(); + $_GET = array(); + $_COOKIE = array(); - $this->assertEquals( $orig, $sr->post, '$_POST should be copied into internal property.' ); + $sr = new Request(); - } + $this->assertEquals($orig, $sr->post, '$_POST should be copied into internal property.'); + } - public function testUnset() { + public function testUnset() + { - $_POST = $orig = array( - 'test' => true, - 'tester' => array( - 0 => 'value0', - 1 => 'value1' - ) - ); + $_POST = $orig = array( + 'test' => true, + 'tester' => array( + 0 => 'value0', + 1 => 'value1' + ) + ); - $_GET = array(); - $_COOKIE = array(); + $_GET = array(); + $_COOKIE = array(); - $sr = new Firelit\Request(function(&$val) { - Firelit\Strings::cleanUTF8($val); - }); + $sr = new Request(function (&$val) { + Strings::cleanUTF8($val); + }); - $this->assertEquals( $orig, $sr->post, '$_POST was not copied by Request object.' ); - $this->assertNull( $_POST, '$_POST was not set to null by Request object.' ); + $this->assertEquals($orig, $sr->post, '$_POST was not copied by Request object.'); + $this->assertNull($_POST, '$_POST was not set to null by Request object.'); + } - } + public function testJson() + { - public function testJson() { + $data = array( + 'test' => true, + 'test_array' => array( + 0 => 'value22', + 1 => 'value33' + ) + ); - $data = array( - 'test' => true, - 'test_array' => array( - 0 => 'value22', - 1 => 'value33' - ) - ); + Request::$dataInput = json_encode($data); + Request::$methodInput = 'POST'; - Firelit\Request::$dataInput = json_encode($data); - Firelit\Request::$methodInput = 'POST'; + $sut = new Request(false, 'json'); - $sut = new Firelit\Request(false, 'json'); + $this->assertEquals($data, $sut->post, 'Post JSON data was not correctly made available.'); - $this->assertEquals( $data, $sut->post, 'Post JSON data was not correctly made available.' ); + Request::$dataInput = '{"json":"invalid",:}'; + Request::$methodInput = 'PUT'; - Firelit\Request::$dataInput = '{"json":"invalid",:}'; - Firelit\Request::$methodInput = 'PUT'; + try { + $sut = new Request(false, 'json'); - try { - $sut = new Firelit\Request(false, 'json'); + $this->assertTrue(false, 'Invalid JSON data was not caught.'); + } catch (InvalidJsonException $e) { + $this->assertTrue(true, 'Invalid JSON data was not caught.'); + } - $this->assertTrue( false, 'Invalid JSON data was not caught.' ); + Request::$dataInput = null; + Request::$methodInput = null; + } - } catch (Firelit\InvalidJsonException $e) { - $this->assertTrue( true, 'Invalid JSON data was not caught.' ); - } + public function testFilter() + { - Firelit\Request::$dataInput = null; - Firelit\Request::$methodInput = null; + $_POST = array(); - } + $_GET = $orig = array( + 'nested' => array( + 'deep' => array( + 'deeper' => 'bad', + 'other' => 'good' + ) + ), + 'shallow' => 'bad' + ); - public function testFilter() { + $_COOKIE = array(); - $_POST = array(); + $sr = new Request(function (&$val) { + if ($val == 'bad') { + $val = 'clean'; + } + }); - $_GET = $orig = array( - 'nested' => array( - 'deep' => array( - 'deeper' => 'bad', - 'other' => 'good' - ) - ), - 'shallow' => 'bad' - ); + $this->assertNotEquals($orig, $sr->get, '$_GET value remains unchanged.'); + $this->assertEquals('clean', $sr->get['nested']['deep']['deeper'], 'Deep array value not cleaned.'); + $this->assertEquals('good', $sr->get['nested']['deep']['other'], 'Deep array value mistakenly cleaned.'); + $this->assertEquals('clean', $sr->get['shallow'], 'Shallow array value not cleaned.'); + } - $_COOKIE = array(); + public function testCliDetection() + { - $sr = new Firelit\Request(function(&$val) { - if ($val == 'bad') $val = 'clean'; - }); + $sr = new Request(); - $this->assertNotEquals( $orig, $sr->get, '$_GET value remains unchanged.' ); - $this->assertEquals( 'clean', $sr->get['nested']['deep']['deeper'], 'Deep array value not cleaned.' ); - $this->assertEquals( 'good', $sr->get['nested']['deep']['other'], 'Deep array value mistakenly cleaned.' ); - $this->assertEquals( 'clean', $sr->get['shallow'], 'Shallow array value not cleaned.' ); - - } - - public function testCliDetection() { - - $sr = new Firelit\Request(); - - $this->assertEquals('CLI', $sr->method); - - } -} \ No newline at end of file + $this->assertEquals('CLI', $sr->method); + } +} diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index 10a5b7a..06c776d 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -1,47 +1,49 @@ clearBuffer(); - $r->endBuffer(); + $r = Response::init(); - unset($r); - Firelit\Response::destruct(); + echo 'Should be cleared'; - $output = ob_get_contents(); - ob_end_clean(); + $r->clearBuffer(); + $r->endBuffer(); - $this->assertEquals('', $output); + unset($r); + Response::destruct(); - } + $output = ob_get_contents(); + ob_end_clean(); - public function testBufferFlush() { + $this->assertEquals('', $output); + } - ob_start(); + public function testBufferFlush() + { - $r = Firelit\Response::init(); - $r->setCallback(function(&$out) { - $out = preg_replace('/not/', 'NOT', $out); - }); + ob_start(); - echo 'Should not be cleared'; + $r = Response::init(); + $r->setCallback(function (&$out) { + $out = preg_replace('/not/', 'NOT', $out); + }); - unset($r); - Firelit\Response::destruct(); + echo 'Should not be cleared'; - $output = ob_get_contents(); - ob_end_clean(); + unset($r); + Response::destruct(); - $this->assertEquals('Should NOT be cleared', $output); + $output = ob_get_contents(); + ob_end_clean(); - } - -} \ No newline at end of file + $this->assertEquals('Should NOT be cleared', $output); + } +} diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 542306d..4be9874 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -1,86 +1,156 @@ setExpectedException('ExpectedException'); +} + +class UnexpectedException extends \Exception +{ + +} +// @codingStandardsIgnoreEnd + +// @codingStandardsIgnoreLine (ignoring multiple classes in a file) +class RouterTest extends \PHPUnit_Framework_TestCase +{ + + public function testAdd() + { + + $this->setExpectedException('Firelit\ExpectedException'); + + $r = new Router(new Request()); + + $r->add('POST', '/.*/', function () { + // Bad + throw new UnexpectedException(); + }); + + $r->add('*', false, function () { + // Good! + throw new ExpectedException(); + }); + + $r->go(); + + Registry::clear(); + unset($r); + } + + /** + * @group failing + * Tests the api edit form + */ + public function testNesting() + { + + $request = new Request(); + + $reflection = new \ReflectionClass($request); + + $reflection_property = $reflection->getProperty('path'); + $reflection_property->setAccessible(true); + $reflection_property->setValue($request, '/test/nest/path.txt'); + + $reflection_property = $reflection->getProperty('cli'); + $reflection_property->setAccessible(true); + $reflection_property->setValue($request, false); + + $reflection_property = $reflection->getProperty('method'); + $reflection_property->setAccessible(true); + $reflection_property->setValue($request, 'GET'); + + $this->setExpectedException('Firelit\ExpectedException'); + + $subR = new Router($request); + + $subR->add('GET', '!^path\.json$!', function () { + // Bad + throw new UnexpectedException(); + }); + + $subR->add('GET', '!^path\.txt$!', function () { + // Good! + throw new ExpectedException(); + }); + + $r = new Router($request); + + $r->add('GET', '!^/test/node/!', function () { + // Bad + throw new UnexpectedException(); + }); - $r = new Firelit\Router(new Firelit\Request()); + $r->add('GET', '!^/test/nest/!', $subR); - $r->add('POST', '/.*/', function() { - // Bad - throw new UnexpectedException(); - }); + $r->go(); - $r->add('*', false, function() { - // Good! - throw new ExpectedException(); - }); + Registry::clear(); + unset($r); + } - Firelit\Registry::clear(); - unset($r); - } + public function testDefault() + { - public function testDefault() { + $this->setExpectedException('Firelit\ExpectedException'); - $this->setExpectedException('ExpectedException'); + $r = new Router(new Request()); - $r = new Firelit\Router(new Firelit\Request()); + $r->defaultRoute(function () { + // Good! + throw new ExpectedException(); + }); - $r->defaultRoute(function() { - // Good! - throw new ExpectedException(); - }); + $r->add('POST', '/.*/', function () { + // Bad + throw new UnexpectedException(); + }); - $r->add('POST', '/.*/', function() { - // Bad - throw new UnexpectedException(); - }); + $r->add('*', '/.*/', function () { + // Bad + throw new UnexpectedException(); + }); - $r->add('*', '/.*/', function() { - // Bad - throw new UnexpectedException(); - }); + $r->go(); - Firelit\Registry::clear(); - unset($r); + Registry::clear(); + unset($r); + } - } + public function testError() + { - public function testError() { + $this->setExpectedException('Firelit\ExpectedException'); - $this->setExpectedException('ExpectedException'); + $r = new Router(new Request()); - $r = new Firelit\Router(new Firelit\Request()); + $r->add('POST', '/.*/', function () { + // Bad + throw new UnexpectedException(); + }); - $r->errorRoute(500, function() { - // Good! - throw new ExpectedException(); - }); - - $r->errorRoute(404, function() { - // Bad - throw new UnexpectedException(); - }); + $r->add('*', false, function () { + // Should take this route + throw new RouteToError(500); + }); - $r->add('POST', '/.*/', function() { - // Bad - throw new UnexpectedException(); - }); + $r->errorRoute(500, function () { + // Good! + throw new ExpectedException(); + }); - $r->add('*', false, function() { - // Should take this route - throw new Firelit\RouteToError(500); - }); + $r->errorRoute(404, function () { + // Bad + throw new UnexpectedException(); + }); - Firelit\Registry::clear(); - unset($r); - - } + $r->go(); + Registry::clear(); + unset($r); + } } - \ No newline at end of file diff --git a/tests/SessionTest.php b/tests/SessionTest.php index a31f10f..c753caf 100644 --- a/tests/SessionTest.php +++ b/tests/SessionTest.php @@ -1,72 +1,76 @@ store = $this->getMock('Firelit\DatabaseSessionHandler', array('open', 'close', 'read', 'write', 'destroy', 'gc')); - - $varName = 'name'. mt_rand(0, 1000); - $varValue = 'value'. mt_rand(0, 1000); - - $sessionId = Firelit\Session::generateSessionId(); // Of the valid format - - $this->assertRegExp('/^[A-Za-z0-9\+\/=]{50}$/', $sessionId); - - $this->assertTrue(Firelit\Session::sessionIdIsValid($sessionId)); - - $this->store->expects($this->once()) - ->method('open') - ->will($this->returnValue(true)); - - $this->store->expects($this->once()) - ->method('read') - ->with($this->equalTo($sessionId)) - ->will($this->returnValue(false)); - - $session = new Firelit\Session($this->store, $sessionId); - - $session->$varName = $varValue; - - $this->assertEquals($varValue, $session->$varName); - - $session->destroy(); - - } - - public function testOpenReadCookieRead() { - - $this->store = $this->getMock('Firelit\DatabaseSessionHandler', array('open', 'close', 'read', 'write', 'destroy', 'gc')); - - $varName = 'name'. mt_rand(0, 1000); - $varValue = 'value'. mt_rand(0, 1000); - - $sessionId = Firelit\Session::generateSessionId(); // Of the valid format - $_COOKIE[Firelit\Session::$config['cookie']['name']] = $sessionId; - - $this->store->expects($this->once()) - ->method('open') - ->will($this->returnValue(true)); - - $this->store->expects($this->once()) - ->method('read') - ->with($this->equalTo($sessionId)) - ->will($this->returnValue(false)); - - $this->store->expects($this->once()) - ->method('write') - ->with( $this->equalTo($sessionId), - $this->stringContains($varValue)) - ->will($this->returnValue(true)); - - $session = new Firelit\Session($this->store); - - $session->$varName = $varValue; - - $this->assertEquals($varValue, $session->$varName); - - $this->assertEquals($sessionId, $session->getSessionId()); - - } - -} \ No newline at end of file +namespace Firelit; + +class SessionTest extends \PHPUnit_Framework_TestCase +{ + + public function testOpenReadSetSession() + { + + $this->store = $this->createMock('Firelit\DatabaseSessionHandler', array('open', 'close', 'read', 'write', 'destroy', 'gc')); + + $varName = 'name'. mt_rand(0, 1000); + $varValue = 'value'. mt_rand(0, 1000); + + $sessionId = Session::generateSessionId(); // Of the valid format + + $this->assertRegExp('/^[A-Za-z0-9\+\/=]{50}$/', $sessionId); + + $this->assertTrue(Session::sessionIdIsValid($sessionId)); + + $this->store->expects($this->once()) + ->method('open') + ->will($this->returnValue(true)); + + $this->store->expects($this->once()) + ->method('read') + ->with($this->equalTo($sessionId)) + ->will($this->returnValue(false)); + + $session = new Session($this->store, $sessionId); + + $session->$varName = $varValue; + + $this->assertEquals($varValue, $session->$varName); + + $session->destroy(); + } + + public function testOpenReadCookieRead() + { + + $this->store = $this->createMock('Firelit\DatabaseSessionHandler', array('open', 'close', 'read', 'write', 'destroy', 'gc')); + + $varName = 'name'. mt_rand(0, 1000); + $varValue = 'value'. mt_rand(0, 1000); + + $sessionId = Session::generateSessionId(); // Of the valid format + $_COOKIE[Session::$config['cookie']['name']] = $sessionId; + + $this->store->expects($this->once()) + ->method('open') + ->will($this->returnValue(true)); + + $this->store->expects($this->once()) + ->method('read') + ->with($this->equalTo($sessionId)) + ->will($this->returnValue(false)); + + $this->store->expects($this->once()) + ->method('write') + ->with( + $this->equalTo($sessionId), + $this->stringContains($varValue) + ) + ->will($this->returnValue(true)); + + $session = new Session($this->store); + + $session->$varName = $varValue; + + $this->assertEquals($varValue, $session->$varName); + + $this->assertEquals($sessionId, $session->getSessionId()); + } +} diff --git a/tests/StringsTest.php b/tests/StringsTest.php new file mode 100644 index 0000000..4c2cae9 --- /dev/null +++ b/tests/StringsTest.php @@ -0,0 +1,136 @@ +assertTrue(Strings::validEmail('test@test.com')); // Valid + $this->assertTrue(Strings::validEmail('test.test@test.com')); // Valid + $this->assertFalse(Strings::validEmail('test')); // Invalid + $this->assertFalse(Strings::validEmail('test test@test.com')); // Invalid + $this->assertFalse(Strings::validEmail('@test.com')); // Invalid + } + + public function testFilter() + { + + $test = "1ø2\x08"; // Ends with a backspace + + // Clean the strings in the array for all valid UTF8 + Strings::cleanUTF8($test); // The parameter is passed by reference and directly filtered + + $this->assertEquals('1ø2', $test); + + $array = array( + 'first' => 'first', + 'second' => "\x00\x00", // Two nulls + 'third' => 'last' + ); + + Strings::cleanUTF8($array); // The parameter is passed by reference and directly filtered + + $this->assertTrue(is_array($array)); + $this->assertEquals(3, sizeof($array)); + $this->assertEquals('first', $array['first']); + $this->assertEquals('', $array['second']); + $this->assertEquals('last', $array['third']); + } + + public function testNames() + { + + // Name normalization + $out = Strings::nameFix('JOHN P. DePrez SR'); + $this->assertEquals('John P. DePrez Sr', $out); + + $out = Strings::nameFix('j. d. doe i'); + $this->assertEquals('J. D. Doe I', $out); + + $out = Strings::nameFix('JOHN doe-dream ii'); + $this->assertEquals('John Doe-Dream II', $out); + + $out = Strings::nameFix('John mcdonald iii'); + $this->assertEquals('John McDonald III', $out); + + $out = Strings::nameFix('John VanPlaat iV'); + $this->assertEquals('John VanPlaat IV', $out); + + $out = Strings::nameFix('JOHN doe SR'); + $this->assertEquals('John Doe Sr', $out); + + $out = Strings::nameFix('JOHN DeBoer j.r.'); + $this->assertEquals('John DeBoer Jr', $out); + + $out = Strings::nameFix('john di\'vinici'); + $this->assertEquals('John Di\'Vinici', $out); + + $out = Strings::nameFix('Sam + John Smith'); + $this->assertEquals('Sam & John Smith', $out); + + $out = Strings::nameFix('Sam and John Smith'); + $this->assertEquals('Sam and John Smith', $out); + + $out = Strings::nameFix('Sam+John Smith'); + $this->assertEquals('Sam & John Smith', $out); + + $out = Strings::nameFix('Sam&John Smith'); + $this->assertEquals('Sam & John Smith', $out); + } + + public function testAddresses() + { + + $out = Strings::addressFix('123 Upper Ave. s.e.'); + $this->assertEquals('123 Upper Ave. SE', $out); + + $out = Strings::addressFix('123 Upper Ave. Ne.'); + $this->assertEquals('123 Upper Ave. NE', $out); + + $out = Strings::addressFix('123 Upper Ave. sw'); + $this->assertEquals('123 Upper Ave. SW', $out); + + $out = Strings::addressFix('123 Upper Ave. NW'); + $this->assertEquals('123 Upper Ave. NW', $out); + + $out = Strings::addressFix('123 NORTH AVENUE SE'); + $this->assertEquals('123 North Avenue SE', $out); + + $out = Strings::addressFix('po box 3484'); + $this->assertEquals('PO Box 3484', $out); + + $out = Strings::addressFix('P.O. box 3484'); + $this->assertEquals('PO Box 3484', $out); + } + + public function testEscaping() + { + // Multi-byte HTML and XML escaping + $out = Strings::html('You & I Rock'); + $this->assertEquals('You & I Rock', $out); + + $out = Strings::xml('You & I Rock'); + $this->assertEquals('You & I Rock', $out); + } + + public function testCaseManipulation() + { + + // Multi-byte safe string case maniuplation + $out = Strings::upper('this started lower, èric'); + $this->assertEquals('THIS STARTED LOWER, ÈRIC', $out); + + $out = Strings::lower('THIS STARTED UPPER, ÈRIC'); + $this->assertEquals('this started upper, èric', $out); + + $out = Strings::title('this STARTED mixed, èric'); + $this->assertEquals('This Started Mixed, Èric', $out); + + $out = Strings::ucwords('this STARTED mixed, èric'); + $this->assertEquals('This STARTED Mixed, Èric', $out); + } +} diff --git a/tests/VarsTest.php b/tests/VarsTest.php new file mode 100644 index 0000000..707a41f --- /dev/null +++ b/tests/VarsTest.php @@ -0,0 +1,48 @@ + Vars::TYPE_OTHER)); + + // Setting custom getter/setter functions for DB-less testing + Vars::$getter = function ($name) { + if (!isset(static::$store[$name])) { + return null; + } + return static::$store[$name]; + }; + + Vars::$setter = function ($name, $value) { + static::$store[$name] = $value; + }; + } + + public function testVars() + { + + $vars = Vars::init(); + + $this->assertEquals(null, $vars->smith); // Null for not-set + $this->assertEquals(false, isset($vars->smith)); + + $vars->smith = 'peanuts'; + + $this->assertEquals('peanuts', $vars->smith); + $this->assertEquals('peanuts', static::$store['smith']); // Make sure the mocked store is working + $this->assertEquals(true, isset($vars->smith)); + + unset($vars->smith); + + $this->assertEquals(null, $vars->smith); // We're back to null for not-set + $this->assertEquals(false, isset(static::$store['smith'])); // Make sure the mocked store is working + $this->assertEquals(false, isset($vars->smith)); + } +} diff --git a/tests/ViewTest.php b/tests/ViewTest.php index eb874a7..b6b91b1 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -1,34 +1,38 @@ setLayout('Test'); - $view->setTemplate('Temp'); +namespace Firelit; - $this->assertEquals('Test', $view->layout); - $this->assertEquals('Temp', $view->template); - } +class ViewTest extends \PHPUnit_Framework_TestCase +{ - public function testAssetAdder() { - Firelit\View::$assetDirectory = '/assets/'; - Firelit\View::$viewFolder = __DIR__.'/'; + public function testLayoutTemplate() + { + $view = new View(); + $view->setLayout('Test'); + $view->setTemplate('Temp'); - $view = new Firelit\View('ViewTestTemplate'); + $this->assertEquals('Test', $view->layout); + $this->assertEquals('Temp', $view->template); + } - ob_start(); - - $view->render(); // Runs code in ViewTestTemplate.php + public function testAssetAdder() + { + View::$assetDirectory = '/assets/'; + View::$viewFolder = __DIR__.'/'; - $output = trim( ob_get_contents() ); - ob_end_clean(); + $view = new View('ViewTestTemplate'); - $this->assertRegExp('!^$!', $output); + ob_start(); - $this->assertRegExp('!type="text/javascript"!', $output); + $view->render(); // Runs code in ViewTestTemplate.php - $this->assertRegExp('!src="/assets/test\.js!', $output); + $output = trim(ob_get_contents()); + ob_end_clean(); - } -} \ No newline at end of file + $this->assertRegExp('!^$!', $output); + + $this->assertRegExp('!type="text/javascript"!', $output); + + $this->assertRegExp('!src="/assets/test\.js!', $output); + } +} diff --git a/tests/ViewTestTemplate.php b/tests/ViewTestTemplate.php index 6565e48..63a0c9f 100644 --- a/tests/ViewTestTemplate.php +++ b/tests/ViewTestTemplate.php @@ -1,7 +1,5 @@ addAsset('test.js'); - -} \ No newline at end of file + $this->addAsset('test.js'); +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index fba5a2e..6e20122 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,13 +1,3 @@