Compare commits

..

11 Commits

Author SHA1 Message Date
Deon George
59bb437595 Fix for commit c50a9c59 missing protect definition 2015-10-16 15:51:12 +11:00
Deon George
6c5366d366 Fix previous commit by only searching for Model_* 2015-10-16 13:27:47 +11:00
Deon George
31b9d9b3e5 Enabled having a default Model instead of needing Model_* files for each table. 2015-10-09 13:34:24 +11:00
Deon George
07819f8bc5 Enable caching related models 2015-10-09 00:29:04 +11:00
Deon George
cb8c3f88f6 Suppress DB updates when values are not changed 2015-09-30 21:18:41 +10:00
Deon George
c50a9c59ec Add missing disable_column_alias in ORM 2015-09-30 21:08:30 +10:00
Deon George
3291c7d850 Moved Email into Kohana_Email to enable extension 2014-09-29 14:27:21 +10:00
Deon George
3d7ef6a32b Improvements to on() processing for upstream to review 2014-09-06 23:48:54 +10:00
Deon George
7801c70a1b Leenooks customisations for Kohana 2014-09-06 23:48:54 +10:00
Deon George
edd21b4d5c Updated Simple_HTML_Dom and fixed module XML to work with KH 3.3 2014-09-06 23:44:01 +10:00
Deon George
711c11dc03 Added 3rd party KH modules 2014-09-06 23:44:01 +10:00
351 changed files with 22561 additions and 4869 deletions

View File

@ -1,13 +1,13 @@
# Developing locally # Developing locally
Since Kohana maintains many concurrent versions at once, there is no single `master` branch. All versions have branches named with a prefix of its version: Since Kohana maintains many concurrent versions at once, there is no single `master` branch. All versions have branches named with a prefix of it's version:
- 3.2/master - 3.2/master
- 3.2/develop - 3.2/develop
- 3.3/master - 3.3/master
- 3.3/develop - 3.3/develop
and so on. All development of versions happens in the develop branch of that version. Before a release, new features are added here. After a major release is actually released, only bugfixes can happen here. New features and API changes must happen in the develop branch of the next version. and so on. All development of versions happens in the develop branch of that version. Before a release, new features are added here. After a major release is actually released, only bugfixes can happen here. New features and api changes must happen in the develop branch of the next version.
## Branch name meanings ## Branch name meanings
@ -17,7 +17,7 @@ and so on. All development of versions happens in the develop branch of that ver
- **3.3/release/*** - release branches are for maintenance work before a release. This branch should be branched from the develop branch only. Change the version number/code name here, and apply any other maintenance items needed before actually releasing. Merges from master should only come from this branch. It should be merged to develop when it's complete as well. This branch is deleted after it's done. - **3.3/release/*** - release branches are for maintenance work before a release. This branch should be branched from the develop branch only. Change the version number/code name here, and apply any other maintenance items needed before actually releasing. Merges from master should only come from this branch. It should be merged to develop when it's complete as well. This branch is deleted after it's done.
- **3.3/feature/*** - Details on these branches are outlined below. This branch is deleted after it's done. - **3.3/feature/*** - Details on these branches are outlined below. This branch is deleted after it's done.
If an bug/issue applies to multiple versions of Kohana, it is first fixed in the lowest supported version it applies to, then merged to each higher branch it applies to. Each merge should only happen one version up. 3.1 should merge to 3.2, and 3.2 should merge to 3.3. 3.1 should not merge directly to 3.3. If an bug/issue applies to multiple versions of kohana, it is first fixed in the lowest supported version it applies to, then merged to each higher branch it applies to. Each merge should only happen one version up. 3.1 should merge to 3.2, and 3.2 should merge to 3.3. 3.1 should not merge directly to 3.3.
To work on a specific release branch you need to check it out then check out the appropriate system branch. To work on a specific release branch you need to check it out then check out the appropriate system branch.
Release branch names follow the same convention in both kohana/kohana and kohana/core. Release branch names follow the same convention in both kohana/kohana and kohana/core.
@ -37,7 +37,7 @@ To work on 3.3.x you'd do the following:
> git submodule foreach "git fetch && git checkout 3.3/develop" > git submodule foreach "git fetch && git checkout 3.3/develop"
# ... # ...
It's important that you follow the last step, because unlike SVN, Git submodules point at a It's important that you follow the last step, because unlike svn, git submodules point at a
specific commit rather than the tip of a branch. If you cd into the system folder after specific commit rather than the tip of a branch. If you cd into the system folder after
a `git submodule update` and run `git status` you'll be told: a `git submodule update` and run `git status` you'll be told:
@ -48,7 +48,7 @@ a `git submodule update` and run `git status` you'll be told:
# Contributing to the project # Contributing to the project
All features and bugfixes must be fully tested and reference an issue in [GitHub](https://github.com/kohana/kohana/issues), **there are absolutely no exceptions**. All features and bugfixes must be fully tested and reference an issue in the [tracker](http://dev.kohanaframework.org/projects/kohana3), **there are absolutely no exceptions**.
It's highly recommended that you write/run unit tests during development as it can help you pick up on issues early on. See the Unit Testing section below. It's highly recommended that you write/run unit tests during development as it can help you pick up on issues early on. See the Unit Testing section below.
@ -68,7 +68,7 @@ The naming convention for feature branches is:
3.2/feature/4045-rewriting-config-system 3.2/feature/4045-rewriting-config-system
When a new feature is complete and fully tested it can be merged into its respective release branch using When a new feature is complete and fully tested it can be merged into its respective release branch using
`git pull --no-ff`. The `--no-ff` switch is important as it tells Git to always create a commit `git pull --no-ff`. The `--no-ff` switch is important as it tells git to always create a commit
detailing what branch you're merging from. This makes it a lot easier to analyse a feature's history. detailing what branch you're merging from. This makes it a lot easier to analyse a feature's history.
Here's a quick example: Here's a quick example:
@ -81,7 +81,7 @@ Here's a quick example:
> git merge --no-ff 3.2/feature/4045-rewriting-everything > git merge --no-ff 3.2/feature/4045-rewriting-everything
**If a change you make intentionally breaks the API then please correct the relevant tests before pushing!** **If a change you make intentionally breaks the api then please correct the relevant tests before pushing!**
## Bug fixing ## Bug fixing
@ -92,7 +92,7 @@ using the `@ticket` notation in the test to reference the bug's issue number
If you run the unit tests then the one you've just made should fail. If you run the unit tests then the one you've just made should fail.
Once you've written the bugfix, run the tests again before you commit to make sure that the Once you've written the bugfix, run the tests again before you commit to make sure that the
fix actually works, then commit both the fix and the test. fix actually works,then commit both the fix and the test.
**Bug fixes without tests written will be rejected! There are NO exceptions.** **Bug fixes without tests written will be rejected! There are NO exceptions.**
@ -101,11 +101,11 @@ branch is perfectly acceptable.
## Tagging releases ## Tagging releases
Tag names should be prefixed with a `v`, this helps to separate tag references from branch references in Git. Tag names should be prefixed with a `v`, this helps to separate tag references from branch references in git.
For example, if you were creating a tag for the `3.1.0` release the tag name would be `v3.1.0` For example, if you were creating a tag for the `3.1.0` release the tag name would be `v3.1.0`
# Merging changes from remote repositories # Merging Changes from Remote Repositories
Now that you have a remote repository, you can pull changes in the remote "kohana" repository Now that you have a remote repository, you can pull changes in the remote "kohana" repository
into your local repository: into your local repository:
@ -115,14 +115,14 @@ into your local repository:
**Note:** Before you pull changes you should make sure that any modifications you've made locally **Note:** Before you pull changes you should make sure that any modifications you've made locally
have been committed. have been committed.
Sometimes a commit you've made locally will conflict with one made in the remote "kohana" repo. Sometimes a commit you've made locally will conflict with one made in the "kohana" one.
There are a couple of scenarios where this might happen: There are a couple of scenarios where this might happen:
## The conflict is due to a few unrelated commits and you want to keep changes made in both commits ## The conflict is to do with a few unrelated commits and you want to keep changes made in both commits
You'll need to manually modify the files to resolve the conflict, see the "Resolving a merge" You'll need to manually modify the files to resolve the conflict, see the "Resolving a merge"
section [in the Git SCM book](http://book.git-scm.com/3_basic_branching_and_merging.html) for more info section [in the git-scm book](http://book.git-scm.com/3_basic_branching_and_merging.html) for more info
## You've fixed something locally which someone else has already done in the remote repo ## You've fixed something locally which someone else has already done in the remote repo
@ -147,14 +147,14 @@ i.e.
> git rebase -i 57d0b28 > git rebase -i 57d0b28
A text editor will open with a list of commits. Delete the line containing the offending commit A text editor will open with a list of commits, delete the line containing the offending commit
before saving the file & closing your editor. before saving the file & closing your editor.
Git will remove the commit and you can then pull/merge the remote changes. Git will remove the commit and you can then pull/merge the remote changes.
# Unit Testing # Unit Testing
Kohana currently uses PHPUnit for unit testing. This is installed with composer. Kohana currently uses phpunit for unit testing. This is installed with composer.
## How to run the tests ## How to run the tests
@ -164,4 +164,4 @@ Kohana currently uses PHPUnit for unit testing. This is installed with composer.
* Run `php composer.phar install` from the root of this repository * Run `php composer.phar install` from the root of this repository
* Finally, run `phing test` * Finally, run `phing test`
This will run the unit tests for core and all the modules and tell you if anything failed. If you haven't changed anything and you get failures, please create a new issue on [the tracker](http://dev.kohanaframework.org) and paste the output (including the error) in the issue. This will run the unit tests for core and all the modules and tell you if anything failed. If you haven't changed anything and you get failures, please create a new issue on [the tracker](http://dev.kohanaframework.org) and paste the output (including the error) in the issue.

View File

@ -1,151 +0,0 @@
<?php defined('SYSPATH') or die('No direct script access.');
// -- Environment setup --------------------------------------------------------
// Load the core Kohana class
require SYSPATH.'classes/Kohana/Core'.EXT;
if (is_file(APPPATH.'classes/Kohana'.EXT))
{
// Application extends the core
require APPPATH.'classes/Kohana'.EXT;
}
else
{
// Load empty core extension
require SYSPATH.'classes/Kohana'.EXT;
}
/**
* Set the default time zone.
*
* @link http://kohanaframework.org/guide/using.configuration
* @link http://www.php.net/manual/timezones
*/
date_default_timezone_set('America/Chicago');
/**
* Set the default locale.
*
* @link http://kohanaframework.org/guide/using.configuration
* @link http://www.php.net/manual/function.setlocale
*/
setlocale(LC_ALL, 'en_US.utf-8');
/**
* Enable the Kohana auto-loader.
*
* @link http://kohanaframework.org/guide/using.autoloading
* @link http://www.php.net/manual/function.spl-autoload-register
*/
spl_autoload_register(array('Kohana', 'auto_load'));
/**
* Optionally, you can enable a compatibility auto-loader for use with
* older modules that have not been updated for PSR-0.
*
* It is recommended to not enable this unless absolutely necessary.
*/
//spl_autoload_register(array('Kohana', 'auto_load_lowercase'));
/**
* Enable the Kohana auto-loader for unserialization.
*
* @link http://www.php.net/manual/function.spl-autoload-call
* @link http://www.php.net/manual/var.configuration#unserialize-callback-func
*/
ini_set('unserialize_callback_func', 'spl_autoload_call');
/**
* Set the mb_substitute_character to "none"
*
* @link http://www.php.net/manual/function.mb-substitute-character.php
*/
mb_substitute_character('none');
// -- Configuration and initialization -----------------------------------------
/**
* Set the default language
*/
I18n::lang('en-us');
if (isset($_SERVER['SERVER_PROTOCOL']))
{
// Replace the default protocol.
HTTP::$protocol = $_SERVER['SERVER_PROTOCOL'];
}
/**
* Set Kohana::$environment if a 'KOHANA_ENV' environment variable has been supplied.
*
* Note: If you supply an invalid environment name, a PHP warning will be thrown
* saying "Couldn't find constant Kohana::<INVALID_ENV_NAME>"
*/
if (isset($_SERVER['KOHANA_ENV']))
{
Kohana::$environment = constant('Kohana::'.strtoupper($_SERVER['KOHANA_ENV']));
}
/**
* Initialize Kohana, setting the default options.
*
* The following options are available:
*
* - string base_url path, and optionally domain, of your application NULL
* - string index_file name of your index file, usually "index.php" index.php
* - string charset internal character set used for input and output utf-8
* - string cache_dir set the internal cache directory APPPATH/cache
* - integer cache_life lifetime, in seconds, of items cached 60
* - boolean errors enable or disable error handling TRUE
* - boolean profile enable or disable internal profiling TRUE
* - boolean caching enable or disable internal caching FALSE
* - boolean expose set the X-Powered-By header FALSE
*/
Kohana::init(array(
'base_url' => '/kohana/',
));
/**
* Attach the file write to logging. Multiple writers are supported.
*/
Kohana::$log->attach(new Log_File(APPPATH.'logs'));
/**
* Attach a file reader to config. Multiple readers are supported.
*/
Kohana::$config->attach(new Config_File);
/**
* Enable modules. Modules are referenced by a relative or absolute path.
*/
Kohana::modules(array(
// 'auth' => MODPATH.'auth', // Basic authentication
// 'cache' => MODPATH.'cache', // Caching with multiple backends
// 'codebench' => MODPATH.'codebench', // Benchmarking tool
// 'database' => MODPATH.'database', // Database access
// 'image' => MODPATH.'image', // Image manipulation
// 'minion' => MODPATH.'minion', // CLI Tasks
// 'orm' => MODPATH.'orm', // Object Relationship Mapping
// 'unittest' => MODPATH.'unittest', // Unit testing
// 'userguide' => MODPATH.'userguide', // User guide and API documentation
));
/**
* Cookie Salt
* @see http://kohanaframework.org/3.3/guide/kohana/cookies
*
* If you have not defined a cookie salt in your Cookie class then
* uncomment the line below and define a preferrably long salt.
*/
// Cookie::$salt = NULL;
/**
* Set the routes. Each route must have a minimum of a name, a URI and a set of
* defaults for the URI.
*/
Route::set('default', '(<controller>(/<action>(/<id>)))')
->defaults(array(
'controller' => 'welcome',
'action' => 'index',
));

View File

@ -1,10 +0,0 @@
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_Welcome extends Controller {
public function action_index()
{
$this->response->body('hello, world!');
}
} // End Welcome

View File

@ -1,6 +1,6 @@
{ {
"require": { "require": {
"phpunit/phpunit": "3.7.24 - 4", "phpunit/phpunit": "3.7.24",
"phing/phing": "dev-master" "phing/phing": "dev-master"
} }
} }

View File

@ -1,21 +0,0 @@
# Turn on URL rewriting
RewriteEngine On
# Installation directory
RewriteBase /
# Protect hidden files from being viewed
<Files .*>
Order Deny,Allow
Deny From All
</Files>
# Protect application and system files from being viewed
RewriteRule ^(?:application|modules|system)\b.* index.php/$0 [L]
# Allow any files or directories that exist to be displayed directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Rewrite all other URLs to index.php/URL
RewriteRule .* index.php/$0 [PT]

121
index.php
View File

@ -1,121 +0,0 @@
<?php
/**
* The directory in which your application specific resources are located.
* The application directory must contain the bootstrap.php file.
*
* @link http://kohanaframework.org/guide/about.install#application
*/
$application = 'application';
/**
* The directory in which your modules are located.
*
* @link http://kohanaframework.org/guide/about.install#modules
*/
$modules = 'modules';
/**
* The directory in which the Kohana resources are located. The system
* directory must contain the classes/kohana.php file.
*
* @link http://kohanaframework.org/guide/about.install#system
*/
$system = 'system';
/**
* The default extension of resource files. If you change this, all resources
* must be renamed to use the new extension.
*
* @link http://kohanaframework.org/guide/about.install#ext
*/
define('EXT', '.php');
/**
* Set the PHP error reporting level. If you set this in php.ini, you remove this.
* @link http://www.php.net/manual/errorfunc.configuration#ini.error-reporting
*
* When developing your application, it is highly recommended to enable notices
* and strict warnings. Enable them by using: E_ALL | E_STRICT
*
* In a production environment, it is safe to ignore notices and strict warnings.
* Disable them by using: E_ALL ^ E_NOTICE
*
* When using a legacy application with PHP >= 5.3, it is recommended to disable
* deprecated notices. Disable with: E_ALL & ~E_DEPRECATED
*/
error_reporting(E_ALL | E_STRICT);
/**
* End of standard configuration! Changing any of the code below should only be
* attempted by those with a working knowledge of Kohana internals.
*
* @link http://kohanaframework.org/guide/using.configuration
*/
// Set the full path to the docroot
define('DOCROOT', realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR);
// Make the application relative to the docroot, for symlink'd index.php
if ( ! is_dir($application) AND is_dir(DOCROOT.$application))
$application = DOCROOT.$application;
// Make the modules relative to the docroot, for symlink'd index.php
if ( ! is_dir($modules) AND is_dir(DOCROOT.$modules))
$modules = DOCROOT.$modules;
// Make the system relative to the docroot, for symlink'd index.php
if ( ! is_dir($system) AND is_dir(DOCROOT.$system))
$system = DOCROOT.$system;
// Define the absolute paths for configured directories
define('APPPATH', realpath($application).DIRECTORY_SEPARATOR);
define('MODPATH', realpath($modules).DIRECTORY_SEPARATOR);
define('SYSPATH', realpath($system).DIRECTORY_SEPARATOR);
// Clean up the configuration vars
unset($application, $modules, $system);
if (file_exists('install'.EXT))
{
// Load the installation check
return include 'install'.EXT;
}
/**
* Define the start time of the application, used for profiling.
*/
if ( ! defined('KOHANA_START_TIME'))
{
define('KOHANA_START_TIME', microtime(TRUE));
}
/**
* Define the memory usage at the start of the application, used for profiling.
*/
if ( ! defined('KOHANA_START_MEMORY'))
{
define('KOHANA_START_MEMORY', memory_get_usage());
}
// Bootstrap the application
require APPPATH.'bootstrap'.EXT;
if (PHP_SAPI == 'cli') // Try and load minion
{
class_exists('Minion_Task') OR die('Please enable the Minion module for CLI support.');
set_exception_handler(array('Minion_Exception', 'handler'));
Minion_Task::factory(Minion_CLI::options())->execute();
}
else
{
/**
* Execute the main request. A source of the URI can be passed, eg: $_SERVER['PATH_INFO'].
* If no source is specified, the URI will be automatically detected.
*/
echo Request::factory(TRUE, array(), FALSE)
->execute()
->send_headers(TRUE)
->body();
}

View File

@ -1,37 +0,0 @@
sudo: false
language: php
# Only build the main develop/master branches - feature branches will be covered by PRs
branches:
only:
- /^[0-9\.]+\/(develop|master)$/
cache:
directories:
- vendor
- $HOME/.composer/cache
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
matrix:
include:
- php: 5.3
env: 'COMPOSER_PHPUNIT="lowest"'
before_script:
- composer install --prefer-dist
- if [ "$COMPOSER_PHPUNIT" = "lowest" ]; then composer update --prefer-lowest --with-dependencies phpunit/phpunit; fi;
- vendor/bin/koharness
script:
- cd /tmp/koharness && ./vendor/bin/phpunit --bootstrap=modules/unittest/bootstrap.php modules/unittest/tests.php
notifications:
email: false

View File

@ -1,9 +1,5 @@
Kohana auth module New Age Auth
--- ---
| ver | Stable | Develop |
|-------|------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|
| 3.3.x | [![Build Status - 3.3/master](https://travis-ci.org/kohana/auth.svg?branch=3.3%2Fmaster)](https://travis-ci.org/kohana/auth) | [![Build Status - 3.3/develop](https://travis-ci.org/kohana/auth.svg?branch=3.3%2Fdevelop)](https://travis-ci.org/kohana/auth) |
| 3.4.x | [![Build Status - 3.4/master](https://travis-ci.org/kohana/auth.svg?branch=3.4%2Fmaster)](https://travis-ci.org/kohana/auth) | [![Build Status - 3.4/develop](https://travis-ci.org/kohana/auth.svg?branch=3.4%2Fdevelop)](https://travis-ci.org/kohana/auth) |
I've forked the main Auth module because there were some fundamental flaws with it: I've forked the main Auth module because there were some fundamental flaws with it:

View File

@ -24,18 +24,10 @@
"kohana/core": ">=3.3", "kohana/core": ">=3.3",
"php": ">=5.3.3" "php": ">=5.3.3"
}, },
"require-dev": {
"kohana/core": "3.3.*@dev",
"kohana/unittest": "3.3.*@dev",
"kohana/koharness": "*@dev"
},
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-3.3/develop": "3.3.x-dev", "dev-3.3/develop": "3.3.x-dev",
"dev-3.4/develop": "3.4.x-dev" "dev-3.4/develop": "3.4.x-dev"
},
"installer-paths": {
"vendor/{$vendor}/{$name}": ["type:kohana-module"]
} }
} }
} }

View File

@ -1,8 +0,0 @@
<?php
// Configuration for koharness - builds a standalone skeleton Kohana app for running unit tests
return array(
'modules' => array(
'auth' => __DIR__,
'unittest' => __DIR__ . '/vendor/kohana/unittest'
),
);

View File

@ -1,45 +0,0 @@
sudo: false
language: php
# Only build the main develop/master branches - feature branches will be covered by PRs
branches:
only:
- /^[0-9\.]+\/(develop|master)$/
cache:
directories:
- vendor
- $HOME/.composer/cache
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
matrix:
include:
- php: 5.3
env: 'COMPOSER_PHPUNIT="lowest"'
services:
- memcached
before_script:
- if [[ $TRAVIS_PHP_VERSION != "hhvm" ]]; then INI_FILE=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; else INI_FILE=/etc/hhvm/php.ini; fi;
- if [[ $TRAVIS_PHP_VERSION = 5.* ]]; then echo extension = memcache.so >> $INI_FILE; fi;
- if [[ $TRAVIS_PHP_VERSION = 5.* ]]; then (echo yes | pecl install -f apcu-4.0.10 && echo apc.enable_cli = 1 >> $INI_FILE); fi;
- if [[ $TRAVIS_PHP_VERSION = 7.* ]]; then (echo yes | pecl install -f apcu-5.1.2 && echo apc.enable_cli = 1 >> $INI_FILE); fi;
- if [[ $TRAVIS_PHP_VERSION != "hhvm" ]] ; then echo -e 'apc.max_file_size = 0\napc.cache_by_default = 0' >> $INI_FILE ; fi
- composer install --prefer-dist
- if [ "$COMPOSER_PHPUNIT" = "lowest" ]; then composer update --prefer-lowest --with-dependencies phpunit/phpunit; fi;
- vendor/bin/koharness
script:
- cd /tmp/koharness && ./vendor/bin/phpunit --bootstrap=modules/unittest/bootstrap.php modules/unittest/tests.php
notifications:
email: false

View File

@ -1,12 +1,6 @@
Kohana Cache library Kohana Cache library
==================== ====================
| ver | Stable | Develop |
|-------|--------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|
| 3.3.x | [![Build Status - 3.3/master](https://travis-ci.org/kohana/cache.svg?branch=3.3%2Fmaster)](https://travis-ci.org/kohana/cache) | [![Build Status - 3.3/develop](https://travis-ci.org/kohana/cache.svg?branch=3.3%2Fdevelop)](https://travis-ci.org/kohana/cache) |
| 3.4.x | [![Build Status - 3.4/master](https://travis-ci.org/kohana/cache.svg?branch=3.4%2Fmaster)](https://travis-ci.org/kohana/cache) | [![Build Status - 3.4/develop](https://travis-ci.org/kohana/cache.svg?branch=3.4%2Fdevelop)](https://travis-ci.org/kohana/cache) |
The cache library for Kohana 3 provides a simple interface to the most common cache solutions. Developers are free to add their own caching solutions that follow the cache design pattern defined within this module. The cache library for Kohana 3 provides a simple interface to the most common cache solutions. Developers are free to add their own caching solutions that follow the cache design pattern defined within this module.
Supported cache solutions Supported cache solutions

View File

@ -36,7 +36,7 @@
* Below is an example of a _memcache_ server configuration. * Below is an example of a _memcache_ server configuration.
* *
* return array( * return array(
* 'memcache' => array( // Name of group * 'default' => array( // Default group
* 'driver' => 'memcache', // using Memcache driver * 'driver' => 'memcache', // using Memcache driver
* 'servers' => array( // Available server definitions * 'servers' => array( // Available server definitions
* array( * array(
@ -49,12 +49,8 @@
* ), * ),
* ) * )
* *
* In cases where only one cache group is required, set `Cache::$default` (in your bootstrap, * In cases where only one cache group is required, if the group is named `default` there is
* or by extending `Kohana_Cache` class) to the name of the group, and use: * no need to pass the group name when instantiating a cache instance.
*
* $cache = Cache::instance(); // instead of Cache::instance('memcache')
*
* It will return the cache instance of the group it has been set in `Cache::$default`.
* *
* #### General cache group configuration settings * #### General cache group configuration settings
* *

View File

@ -1,174 +0,0 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* [Kohana Cache](api/Kohana_Cache) APCu data store driver for Kohana Cache
* library.
*
* ### Configuration example
*
* Below is an example of an _apcu_ server configuration.
*
* return array(
* 'apcu' => array( // Driver group
* 'driver' => 'apcu', // using APCu driver
* ),
* )
*
* In cases where only one cache group is required, if the group is named `default` there is
* no need to pass the group name when instantiating a cache instance.
*
* #### General cache group configuration settings
*
* Below are the settings available to all types of cache driver.
*
* Name | Required | Description
* -------------- | -------- | ---------------------------------------------------------------
* driver | __YES__ | (_string_) The driver type to use
*
* ### System requirements
*
* * Kohana 3.0.x
* * PHP 5.2.4 or greater
* * APCu PHP extension
*
* @package Kohana/Cache
* @category Base
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Cache_Apcu extends Cache implements Cache_Arithmetic {
/**
* Check for existence of the APCu extension This method cannot be invoked externally. The driver must
* be instantiated using the `Cache::instance()` method.
*
* @param array $config configuration
* @throws Cache_Exception
*/
protected function __construct(array $config)
{
if ( ! extension_loaded('apcu'))
{
throw new Cache_Exception('PHP APCu extension is not available.');
}
parent::__construct($config);
}
/**
* Retrieve a cached value entry by id.
*
* // Retrieve cache entry from apcu group
* $data = Cache::instance('apcu')->get('foo');
*
* // Retrieve cache entry from apcu group and return 'bar' if miss
* $data = Cache::instance('apcu')->get('foo', 'bar');
*
* @param string $id id of cache to entry
* @param string $default default value to return if cache miss
* @return mixed
* @throws Cache_Exception
*/
public function get($id, $default = NULL)
{
$data = apcu_fetch($this->_sanitize_id($id), $success);
return $success ? $data : $default;
}
/**
* Set a value to cache with id and lifetime
*
* $data = 'bar';
*
* // Set 'bar' to 'foo' in apcu group, using default expiry
* Cache::instance('apcu')->set('foo', $data);
*
* // Set 'bar' to 'foo' in apcu group for 30 seconds
* Cache::instance('apcu')->set('foo', $data, 30);
*
* @param string $id id of cache entry
* @param string $data data to set to cache
* @param integer $lifetime lifetime in seconds
* @return boolean
*/
public function set($id, $data, $lifetime = NULL)
{
if ($lifetime === NULL)
{
$lifetime = Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE);
}
return apcu_store($this->_sanitize_id($id), $data, $lifetime);
}
/**
* Delete a cache entry based on id
*
* // Delete 'foo' entry from the apcu group
* Cache::instance('apcu')->delete('foo');
*
* @param string $id id to remove from cache
* @return boolean
*/
public function delete($id)
{
return apcu_delete($this->_sanitize_id($id));
}
/**
* Delete all cache entries.
*
* Beware of using this method when
* using shared memory cache systems, as it will wipe every
* entry within the system for all clients.
*
* // Delete all cache entries in the apcu group
* Cache::instance('apcu')->delete_all();
*
* @return boolean
*/
public function delete_all()
{
return apcu_clear_cache();
}
/**
* Increments a given value by the step value supplied.
* Useful for shared counters and other persistent integer based
* tracking.
*
* @param string id of cache entry to increment
* @param int step value to increment by
* @return integer
* @return boolean
*/
public function increment($id, $step = 1)
{
if (apcu_exists($id)) {
return apcu_inc($id, $step);
} else {
return FALSE;
}
}
/**
* Decrements a given value by the step value supplied.
* Useful for shared counters and other persistent integer based
* tracking.
*
* @param string id of cache entry to decrement
* @param int step value to decrement by
* @return integer
* @return boolean
*/
public function decrement($id, $step = 1)
{
if (apcu_exists($id)) {
return apcu_dec($id, $step);
} else {
return FALSE;
}
}
} // End Kohana_Cache_Apcu

View File

@ -140,21 +140,17 @@ class Kohana_Cache_File extends Cache implements Cache_GarbageCollect {
} }
else else
{ {
// Test the expiry // Open the file and parse data
if ($this->_is_expired($file)) $created = $file->getMTime();
$data = $file->openFile();
$lifetime = $data->fgets();
// If we're at the EOF at this point, corrupted!
if ($data->eof())
{ {
// Delete the file throw new Cache_Exception(__METHOD__.' corrupted cache file!');
$this->_delete_file($file, FALSE, TRUE);
return $default;
} }
// open the file to read data
$data = $file->openFile();
// Run first fgets(). Cache data starts from the second line
// as the first contains the lifetime timestamp
$data->fgets();
$cache = ''; $cache = '';
while ($data->eof() === FALSE) while ($data->eof() === FALSE)
@ -162,7 +158,17 @@ class Kohana_Cache_File extends Cache implements Cache_GarbageCollect {
$cache .= $data->fgets(); $cache .= $data->fgets();
} }
return unserialize($cache); // Test the expiry
if (($created + (int) $lifetime) < time())
{
// Delete the file
$this->_delete_file($file, NULL, TRUE);
return $default;
}
else
{
return unserialize($cache);
}
} }
} }
@ -213,7 +219,14 @@ class Kohana_Cache_File extends Cache implements Cache_GarbageCollect {
// If the directory path is not a directory // If the directory path is not a directory
if ( ! $dir->isDir()) if ( ! $dir->isDir())
{ {
$this->_make_directory($directory, 0777, TRUE); // Create the directory
if ( ! mkdir($directory, 0777, TRUE))
{
throw new Cache_Exception(__METHOD__.' unable to create directory : :directory', array(':directory' => $directory));
}
// chmod to solve potential umask issues
chmod($directory, 0777);
} }
// Open file to inspect // Open file to inspect
@ -254,7 +267,7 @@ class Kohana_Cache_File extends Cache implements Cache_GarbageCollect {
$filename = Cache_File::filename($this->_sanitize_id($id)); $filename = Cache_File::filename($this->_sanitize_id($id));
$directory = $this->_resolve_directory($filename); $directory = $this->_resolve_directory($filename);
return $this->_delete_file(new SplFileInfo($directory.$filename), FALSE, TRUE); return $this->_delete_file(new SplFileInfo($directory.$filename), NULL, TRUE);
} }
/** /**
@ -324,7 +337,9 @@ class Kohana_Cache_File extends Cache implements Cache_GarbageCollect {
else else
{ {
// Assess the file expiry to flag it for deletion // Assess the file expiry to flag it for deletion
$delete = $this->_is_expired($file); $json = $file->openFile('r')->current();
$data = json_decode($json);
$delete = $data->expiry < time();
} }
// If the delete flag is set delete file // If the delete flag is set delete file
@ -360,7 +375,7 @@ class Kohana_Cache_File extends Cache implements Cache_GarbageCollect {
// Create new file resource // Create new file resource
$fp = new SplFileInfo($files->getRealPath()); $fp = new SplFileInfo($files->getRealPath());
// Delete the file // Delete the file
$this->_delete_file($fp, $retain_parent_directory, $ignore_errors, $only_expired); $this->_delete_file($fp);
} }
// Move the file pointer on // Move the file pointer on
@ -431,55 +446,21 @@ class Kohana_Cache_File extends Cache implements Cache_GarbageCollect {
* `mkdir` to ensure DRY principles * `mkdir` to ensure DRY principles
* *
* @link http://php.net/manual/en/function.mkdir.php * @link http://php.net/manual/en/function.mkdir.php
* @param string $directory directory path * @param string $directory
* @param integer $mode chmod mode * @param integer $mode
* @param boolean $recursive allows nested directories creation * @param boolean $recursive
* @param resource $context a stream context * @param resource $context
* @return SplFileInfo * @return SplFileInfo
* @throws Cache_Exception * @throws Cache_Exception
*/ */
protected function _make_directory($directory, $mode = 0777, $recursive = FALSE, $context = NULL) protected function _make_directory($directory, $mode = 0777, $recursive = FALSE, $context = NULL)
{ {
// call mkdir according to the availability of a passed $context param if ( ! mkdir($directory, $mode, $recursive, $context))
$mkdir_result = $context ?
mkdir($directory, $mode, $recursive, $context) :
mkdir($directory, $mode, $recursive);
// throw an exception if unsuccessful
if ( ! $mkdir_result)
{ {
throw new Cache_Exception('Failed to create the defined cache directory : :directory', array(':directory' => $directory)); throw new Cache_Exception('Failed to create the defined cache directory : :directory', array(':directory' => $directory));
} }
// chmod to solve potential umask issues
chmod($directory, $mode); chmod($directory, $mode);
return new SplFileInfo($directory); return new SplFileInfo($directory);
} }
/**
* Test if cache file is expired
*
* @param SplFileInfo $file the cache file
* @return boolean TRUE if expired false otherwise
*/
protected function _is_expired(SplFileInfo $file)
{
// Open the file and parse data
$created = $file->getMTime();
$data = $file->openFile("r");
$lifetime = (int) $data->fgets();
// If we're at the EOF at this point, corrupted!
if ($data->eof())
{
throw new Cache_Exception(__METHOD__ . ' corrupted cache file!');
}
//close file
$data = null;
// test for expiry and return
return (($lifetime !== 0) AND ( ($created + $lifetime) < time()));
}
} }

View File

@ -1,6 +1,6 @@
<?php defined('SYSPATH') or die('No direct script access.'); <?php defined('SYSPATH') or die('No direct script access.');
/** /**
* HTTP Caching adaptor class that provides caching services to the * HTTT Caching adaptor class that provides caching services to the
* [Request_Client] class, using HTTP cache control logic as defined in * [Request_Client] class, using HTTP cache control logic as defined in
* RFC 2616. * RFC 2616.
* *
@ -36,7 +36,7 @@ class Kohana_HTTP_Cache {
* ) * )
* ); * );
* *
* @uses Cache * @uses [Cache]
* @param mixed $cache cache engine to use * @param mixed $cache cache engine to use
* @param array $options options to set to this class * @param array $options options to set to this class
* @return HTTP_Cache * @return HTTP_Cache
@ -298,7 +298,7 @@ class Kohana_HTTP_Cache {
* Controls whether the response can be cached. Uses HTTP * Controls whether the response can be cached. Uses HTTP
* protocol to determine whether the response can be cached. * protocol to determine whether the response can be cached.
* *
* @link http://www.w3.org/Protocols/rfc2616/rfc2616.html RFC 2616 * @link RFC 2616 http://www.w3.org/Protocols/rfc2616/
* @param Response $response The Response * @param Response $response The Response
* @return boolean * @return boolean
*/ */

View File

@ -24,18 +24,10 @@
"kohana/core": ">=3.3", "kohana/core": ">=3.3",
"php": ">=5.3.3" "php": ">=5.3.3"
}, },
"require-dev": {
"kohana/core": "3.3.*@dev",
"kohana/unittest": "3.3.*@dev",
"kohana/koharness": "*@dev"
},
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-3.3/develop": "3.3.x-dev", "dev-3.3/develop": "3.3.x-dev",
"dev-3.4/develop": "3.4.x-dev" "dev-3.4/develop": "3.4.x-dev"
}, }
"installer-paths": {
"vendor/{$vendor}/{$name}": ["type:kohana-module"]
}
} }
} }

View File

@ -55,7 +55,7 @@ failure_callback | __NO__ | (_[callback](http://www.php.net/manual/en/language
'driver' => 'memcache', 'driver' => 'memcache',
'default_expire' => 3600, 'default_expire' => 3600,
'compression' => FALSE, // Use Zlib compression 'compression' => FALSE, // Use Zlib compression
// (can cause issues with integers) (can cause issues with integers)
'servers' => array 'servers' => array
( (
'local' => array 'local' => array
@ -71,7 +71,7 @@ failure_callback | __NO__ | (_[callback](http://www.php.net/manual/en/language
'driver' => 'memcachetag', 'driver' => 'memcachetag',
'default_expire' => 3600, 'default_expire' => 3600,
'compression' => FALSE, // Use Zlib compression 'compression' => FALSE, // Use Zlib compression
// (can cause issues with integers) (can cause issues with integers)
'servers' => array 'servers' => array
( (
'local' => array 'local' => array
@ -90,15 +90,7 @@ failure_callback | __NO__ | (_[callback](http://www.php.net/manual/en/language
'driver' => 'apc', 'driver' => 'apc',
'default_expire' => 3600, 'default_expire' => 3600,
), ),
## APCu settings
'apcu' => array
(
'driver' => 'apcu',
'default_expire' => 3600,
),
## SQLite settings ## SQLite settings
'sqlite' => array 'sqlite' => array

View File

@ -6,7 +6,7 @@ instances of cache engines through a grouped singleton pattern.
## Supported cache engines ## Supported cache engines
* APC/APCu ([Cache_Apc]) * APC ([Cache_Apc])
* File ([Cache_File]) * File ([Cache_File])
* Memcached ([Cache_Memcache]) * Memcached ([Cache_Memcache])
* Memcached-tags ([Cache_Memcachetag]) * Memcached-tags ([Cache_Memcachetag])
@ -16,13 +16,13 @@ instances of cache engines through a grouped singleton pattern.
## Introduction to caching ## Introduction to caching
Caching should be implemented with consideration. Generally, caching the result of resources Caching should be implemented with consideration. Generally, caching the result of resources
is faster than reprocessing them. Choosing what, how and when to cache is vital. [PHP APCu](http://php.net/manual/en/book.apcu.php) is one of the fastest caching systems available, closely followed by [Memcached](http://memcached.org/). [SQLite](http://www.sqlite.org/) and File caching are two of the slowest cache methods, however usually faster than reprocessing is faster than reprocessing them. Choosing what, how and when to cache is vital. [PHP APC](http://php.net/manual/en/book.apc.php) is one of the fastest caching systems available, closely followed by [Memcached](http://memcached.org/). [SQLite](http://www.sqlite.org/) and File caching are two of the slowest cache methods, however usually faster than reprocessing
a complex set of instructions. a complex set of instructions.
Caching engines that use memory are considerably faster than file based alternatives. But Caching engines that use memory are considerably faster than file based alternatives. But
memory is limited whereas disk space is plentiful. If caching large datasets, such as large database result sets, it is best to use file caching. memory is limited whereas disk space is plentiful. If caching large datasets, such as large database result sets, it is best to use file caching.
[!!] Cache drivers require the relevant PHP extensions to be installed. APC, eAccelerator, Memecached and Xcache all require non-standard PHP extensions. [!!] Cache drivers require the relevant PHP extensions to be installed. APC, eAccelerator, Memecached and Xcache all require non-standard PHP extensions.
## What the Kohana Cache module does (and does not do) ## What the Kohana Cache module does (and does not do)
@ -43,7 +43,7 @@ Getting and setting values to cache is very simple when using the _Kohana Cache_
Driver | Storage | Speed | Tags | Distributed | Automatic Garbage Collection | Notes Driver | Storage | Speed | Tags | Distributed | Automatic Garbage Collection | Notes
---------------- | ------------ | --------- | -------- | ----------- | ---------------------------- | ----------------------- ---------------- | ------------ | --------- | -------- | ----------- | ---------------------------- | -----------------------
APC/APCu | __Memory__ | Excellent | No | No | Yes | Widely available PHP opcode caching solution, improves php execution performance APC | __Memory__ | Excellent | No | No | Yes | Widely available PHP opcode caching solution, improves php execution performance
Wincache | __Memory__ | Excellent | No | No | Yes | Windows variant of APC Wincache | __Memory__ | Excellent | No | No | Yes | Windows variant of APC
File | __Disk__ | Poor | No | No | No | Marginally faster than execution File | __Disk__ | Poor | No | No | No | Marginally faster than execution
Memcache (tag) | __Memory__ | Good | No (yes) | Yes | Yes | Generally fast distributed solution, but has a speed hit due to variable network latency and serialization Memcache (tag) | __Memory__ | Good | No (yes) | Yes | Yes | Generally fast distributed solution, but has a speed hit due to variable network latency and serialization
@ -54,4 +54,4 @@ It is possible to have hybrid cache solutions that use a combination of the engi
## Minimum requirements ## Minimum requirements
* Kohana 3.0.4 * Kohana 3.0.4
* PHP 5.2.4 or greater * PHP 5.2.4 or greater

View File

@ -9,7 +9,7 @@ Creating a new _Kohana Cache_ instance is simple, however it must be done using
// Create a new instance of cache using the default group // Create a new instance of cache using the default group
$cache = Cache::instance(); $cache = Cache::instance();
The default group will use whatever is set to [Cache::$default] and must have a corresponding [configuration](config) definition for that group. The default group will use whatever is set to [Cache::$default] and must have a corresponding [configuration](cache.config) definition for that group.
To create a cache instance using a group other than the _default_, simply provide the group name as an argument. To create a cache instance using a group other than the _default_, simply provide the group name as an argument.
@ -18,7 +18,7 @@ To create a cache instance using a group other than the _default_, simply provid
If there is a cache instance already instantiated then you can get it directly from the class member. If there is a cache instance already instantiated then you can get it directly from the class member.
[!!] Beware that this can cause issues if you do not test for the instance before trying to access it. [!!] Beware that this can cause issues if you do not test for the instance before trying to access it.
// Check for the existance of the cache driver // Check for the existance of the cache driver
if (isset(Cache::$instances['memcache'])) if (isset(Cache::$instances['memcache']))
@ -36,11 +36,11 @@ If there is a cache instance already instantiated then you can get it directly f
The cache library supports scalar and object values, utilising object serialization where required (or not supported by the caching engine). This means that the majority or objects can be cached without any modification. The cache library supports scalar and object values, utilising object serialization where required (or not supported by the caching engine). This means that the majority or objects can be cached without any modification.
[!!] Serialisation does not work with resource handles, such as filesystem, curl or socket resources. [!!] Serialisation does not work with resource handles, such as filesystem, curl or socket resources.
### Setting a value to cache ### Setting a value to cache
Setting a value to cache using the [Cache::set] method can be done in one of two ways: either using the Cache instance interface, which is good for atomic operations, or getting an instance and using that for multiple operations. Setting a value to cache using the [Cache::set] method can be done in one of two ways; either using the Cache instance interface, which is good for atomic operations; or getting an instance and using that for multiple operations.
The first example demonstrates how to quickly load and set a value to the default cache instance. The first example demonstrates how to quickly load and set a value to the default cache instance.
@ -104,7 +104,7 @@ In cases where the requested key is not available or the entry has expired, a de
It is possible to retrieve values from cache grouped by tag, using the [Cache::find] method with drivers that support tagging. It is possible to retrieve values from cache grouped by tag, using the [Cache::find] method with drivers that support tagging.
[!!] The __Memcachetag__ driver does not support the `Cache::find($tag)` interface and will throw an exception. [!!] The __Memcachetag__ driver does not support the `Cache::find($tag)` interface and will throw an exception.
// Get an instance of cache // Get an instance of cache
$cache = Cache::instance('memcachetag'); $cache = Cache::instance('memcachetag');

View File

@ -1,9 +0,0 @@
<?php
// Configuration for koharness - builds a standalone skeleton Kohana app for running unit tests
return array(
'kohana_version' => '3.3',
'modules' => array(
'cache' => __DIR__,
'unittest' => __DIR__ . '/vendor/kohana/unittest'
),
);

View File

@ -164,17 +164,6 @@ TESTTEXT;
), ),
'foo bar snafu' 'foo bar snafu'
), ),
array(
array(
'id' => 'test ttl 0 means never expire',
'value' => 'cache value that should last',
'ttl' => 0,
'wait' => 1,
'type' => 'string',
'default' => NULL
),
'cache value that should last'
),
array( array(
array( array(
'id' => 'bar', 'id' => 'bar',
@ -207,28 +196,6 @@ TESTTEXT;
'default' => NULL, 'default' => NULL,
), ),
$html_text $html_text
),
array(
array(
'id' => 'test with 60*5',
'value' => 'blabla',
'ttl' => 60*5,
'wait' => FALSE,
'type' => 'string',
'default' => NULL,
),
'blabla'
),
array(
array(
'id' => 'test with 60*50',
'value' => 'bla bla',
'ttl' => 60*50,
'wait' => FALSE,
'type' => 'string',
'default' => NULL,
),
'bla bla'
) )
); );
} }

View File

@ -90,13 +90,14 @@ class Kohana_CacheTest extends PHPUnit_Framework_TestCase {
*/ */
public function test_cloning_fails() public function test_cloning_fails()
{ {
$cache = $this->getMockBuilder('Cache') if ( ! Kohana::$config->load('cache.file'))
->disableOriginalConstructor() {
->getMockForAbstractClass(); $this->markTestSkipped('Unable to load File configuration');
}
try try
{ {
clone($cache); $cache_clone = clone(Cache::instance('file'));
} }
catch (Cache_Exception $e) catch (Cache_Exception $e)
{ {

View File

@ -30,21 +30,7 @@ class Kohana_Cache_FileTest extends Kohana_CacheBasicMethodsTest {
if ( ! Kohana::$config->load('cache.file')) if ( ! Kohana::$config->load('cache.file'))
{ {
Kohana::$config->load('cache') $this->markTestSkipped('Unable to load File configuration');
->set(
'file',
array(
'driver' => 'file',
'cache_dir' => APPPATH.'cache',
'default_expire' => 3600,
'ignore_on_delete' => array(
'file_we_want_to_keep.cache',
'.gitignore',
'.git',
'.svn'
)
)
);
} }
$this->cache(Cache::instance('file')); $this->cache(Cache::instance('file'));
@ -59,7 +45,7 @@ class Kohana_Cache_FileTest extends Kohana_CacheBasicMethodsTest {
{ {
$cache = $this->cache(); $cache = $this->cache();
$config = Kohana::$config->load('cache')->file; $config = Kohana::$config->load('cache')->file;
$file = $config['cache_dir'].'/file_we_want_to_keep.cache'; $file = $config['cache_dir'].'/.gitignore';
// Lets pollute the cache folder // Lets pollute the cache folder
file_put_contents($file, 'foobar'); file_put_contents($file, 'foobar');
@ -109,55 +95,4 @@ class Kohana_Cache_FileTest extends Kohana_CacheBasicMethodsTest {
$this->assertSame($expected, $cache->get('utf8')); $this->assertSame($expected, $cache->get('utf8'));
} }
/**
* Tests garbage collection.
* Tests if non-expired cache files withstand garbage collection
*
* @test
*/
public function test_garbage_collection()
{
$cache = $this->cache();
$cache->set('persistent', 'dummy persistent data', 3);
$cache->set('volatile', 'dummy volatile data', 1);
$this->assertTrue($this->is_file('persistent'));
$this->assertTrue($this->is_file('volatile'));
// sleep for more than a second
sleep(2);
$cache->garbage_collect();
$this->assertTrue($this->is_file('persistent'));
$this->assertFalse($this->is_file('volatile'));
}
/**
* helper method for test_garbage_collection.
* Tests if cache file exists given cache id.
*
* @param string $id cache id
* @return boolean TRUE if file exists FALSE otherwise
*/
protected function is_file($id)
{
$cache = $this->cache();
$method_sanitize_id = new ReflectionMethod($cache, '_sanitize_id');
$method_sanitize_id->setAccessible(TRUE);
$method_filename = new ReflectionMethod($cache, 'filename');
$method_filename->setAccessible(TRUE);
$method_resolve_directory = new ReflectionMethod($cache, '_resolve_directory');
$method_resolve_directory->setAccessible(TRUE);
$sanitized_id = $method_sanitize_id->invoke($cache, $id);
$filename = $method_filename->invoke($cache, $sanitized_id);
$directory = $method_resolve_directory->invoke($cache, $filename);
$file = new SplFileInfo($directory.$filename);
//var_dump($cache->_is_expired($file));
return $file->isFile();
}
} // End Kohana_SqliteTest } // End Kohana_SqliteTest

View File

@ -35,16 +35,7 @@ class Kohana_SqliteTest extends Kohana_CacheBasicMethodsTest {
if ( ! Kohana::$config->load('cache.sqlite')) if ( ! Kohana::$config->load('cache.sqlite'))
{ {
Kohana::$config->load('cache') $this->markTestIncomplete('Unable to load sqlite configuration');
->set(
'sqlite',
array(
'driver' => 'sqlite',
'default_expire' => 3600,
'database' => 'memory',
'schema' => 'CREATE TABLE caches(id VARCHAR(127) PRIMARY KEY, tags VARCHAR(255), expiration INTEGER, cache TEXT)',
)
);
} }
$this->cache(Cache::instance('sqlite')); $this->cache(Cache::instance('sqlite'));

View File

@ -1,49 +1,39 @@
<?php <?php
if (isset($_ENV['TRAVIS'])) include_once(Kohana::find_file('tests/cache', 'CacheBasicMethodsTest'));
{
// This is really hacky, but without it the result is permanently full of noise that makes it impossible to see /**
// any unexpected skipped tests. * @package Kohana/Cache
print "Skipping all Wincache driver tests as these will never run on Travis.".\PHP_EOL; * @group kohana
return; * @group kohana.cache
} * @category Test
else * @author Kohana Team
{ * @copyright (c) 2009-2012 Kohana Team
include_once(Kohana::find_file('tests/cache', 'CacheBasicMethodsTest')); * @license http://kohanaphp.com/license
*/
class Kohana_WincacheTest extends Kohana_CacheBasicMethodsTest {
/** /**
* @package Kohana/Cache * This method MUST be implemented by each driver to setup the `Cache`
* @group kohana * instance for each test.
* @group kohana.cache *
* @category Test * This method should do the following tasks for each driver test:
* @author Kohana Team *
* @copyright (c) 2009-2012 Kohana Team * - Test the Cache instance driver is available, skip test otherwise
* @license http://kohanaphp.com/license * - Setup the Cache instance
* - Call the parent setup method, `parent::setUp()`
*
* @return void
*/ */
class Kohana_WincacheTest extends Kohana_CacheBasicMethodsTest { public function setUp()
{
parent::setUp();
/** if ( ! extension_loaded('wincache'))
* This method MUST be implemented by each driver to setup the `Cache`
* instance for each test.
*
* This method should do the following tasks for each driver test:
*
* - Test the Cache instance driver is available, skip test otherwise
* - Setup the Cache instance
* - Call the parent setup method, `parent::setUp()`
*
* @return void
*/
public function setUp()
{ {
parent::setUp(); $this->markTestSkipped('Wincache PHP Extension is not available');
if ( ! extension_loaded('wincache'))
{
$this->markTestSkipped('Wincache PHP Extension is not available');
}
$this->cache(Cache::instance('wincache'));
} }
} // End Kohana_WincacheTest $this->cache(Cache::instance('wincache'));
} }
} // End Kohana_WincacheTest

View File

@ -39,18 +39,6 @@ class Kohana_ApcTest extends Kohana_CacheArithmeticMethodsTest {
'place "apc.enable_cli=1" in your php.ini file'); 'place "apc.enable_cli=1" in your php.ini file');
} }
if ( ! Kohana::$config->load('cache.apc'))
{
Kohana::$config->load('cache')
->set(
'apc',
array(
'driver' => 'apc',
'default_expire' => 3600,
)
);
}
$this->cache(Cache::instance('apc')); $this->cache(Cache::instance('apc'));
} }
@ -69,7 +57,7 @@ class Kohana_ApcTest extends Kohana_CacheArithmeticMethodsTest {
* *
* @dataProvider provider_set_get * @dataProvider provider_set_get
* *
* @param array data * @param array data
* @param mixed expected * @param mixed expected
* @return void * @return void
*/ */

View File

@ -1,87 +0,0 @@
<?php
include_once(Kohana::find_file('tests/cache/arithmetic', 'CacheArithmeticMethods'));
/**
* @package Kohana/Cache
* @group kohana
* @group kohana.cache
* @category Test
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_ApcuTest extends Kohana_CacheArithmeticMethodsTest {
/**
* This method MUST be implemented by each driver to setup the `Cache`
* instance for each test.
*
* This method should do the following tasks for each driver test:
*
* - Test the Cache instance driver is available, skip test otherwise
* - Setup the Cache instance
* - Call the parent setup method, `parent::setUp()`
*
* @return void
*/
public function setUp()
{
parent::setUp();
if ( ! extension_loaded('apcu'))
{
$this->markTestSkipped('APCu PHP Extension is not available');
}
if ( ! (ini_get('apc.enabled') AND ini_get('apc.enable_cli')))
{
$this->markTestSkipped('APCu is not enabled. To fix '.
'set "apc.enabled=1" and "apc.enable_cli=1" in your php.ini file');
}
if ( ! Kohana::$config->load('cache.apcu'))
{
Kohana::$config->load('cache')
->set(
'apcu',
array(
'driver' => 'apcu',
'default_expire' => 3600,
)
);
}
$this->cache(Cache::instance('apcu'));
}
/**
* Tests the [Cache::set()] method, testing;
*
* - The value is cached
* - The lifetime is respected
* - The returned value type is as expected
* - The default not-found value is respected
*
* This test doesn't test the TTL as there is a known bug/feature
* in APCu that prevents the same request from killing cache on timeout.
*
* @link http://pecl.php.net/bugs/bug.php?id=16814
*
* @dataProvider provider_set_get
*
* @param array data
* @param mixed expected
* @return void
*/
public function test_set_get(array $data, $expected)
{
if ($data['wait'] !== FALSE)
{
$this->markTestSkipped('Unable to perform TTL test in CLI, see: '.
'http://pecl.php.net/bugs/bug.php?id=16814 for more info!');
}
parent::test_set_get($data, $expected);
}
} // End Kohana_ApcuTest

View File

@ -35,28 +35,7 @@ class Kohana_CacheArithmeticMemcacheTest extends Kohana_CacheArithmeticMethodsTe
} }
if ( ! $config = Kohana::$config->load('cache.memcache')) if ( ! $config = Kohana::$config->load('cache.memcache'))
{ {
Kohana::$config->load('cache') $this->markTestSkipped('Unable to load Memcache configuration');
->set(
'memcache',
array(
'driver' => 'memcache',
'default_expire' => 3600,
'compression' => FALSE, // Use Zlib compression (can cause issues with integers)
'servers' => array(
'local' => array(
'host' => 'localhost', // Memcache Server
'port' => 11211, // Memcache port number
'persistent' => FALSE, // Persistent connection
'weight' => 1,
'timeout' => 1,
'retry_interval' => 15,
'status' => TRUE,
),
),
'instant_death' => TRUE,
)
);
$config = Kohana::$config->load('cache.memcache');
} }
$memcache = new Memcache; $memcache = new Memcache;

View File

@ -1,37 +0,0 @@
sudo: false
language: php
# Only build the main develop/master branches - feature branches will be covered by PRs
branches:
only:
- /^[0-9\.]+\/(develop|master)$/
cache:
directories:
- vendor
- $HOME/.composer/cache
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
matrix:
include:
- php: 5.3
env: 'COMPOSER_PHPUNIT="lowest"'
before_script:
- composer install --prefer-dist
- if [ "$COMPOSER_PHPUNIT" = "lowest" ]; then composer update --prefer-lowest --with-dependencies phpunit/phpunit; fi;
- vendor/bin/koharness
script:
- cd /tmp/koharness && ./vendor/bin/phpunit --bootstrap=modules/unittest/bootstrap.php modules/unittest/tests.php
notifications:
email: false

View File

@ -24,18 +24,10 @@
"kohana/core": ">=3.3", "kohana/core": ">=3.3",
"php": ">=5.3.3" "php": ">=5.3.3"
}, },
"require-dev": {
"kohana/core": "3.3.*@dev",
"kohana/unittest": "3.3.*@dev",
"kohana/koharness": "*@dev"
},
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-3.3/develop": "3.3.x-dev", "dev-3.3/develop": "3.3.x-dev",
"dev-3.4/develop": "3.4.x-dev" "dev-3.4/develop": "3.4.x-dev"
}, }
"installer-paths": {
"vendor/{$vendor}/{$name}": ["type:kohana-module"]
}
} }
} }

View File

@ -26,7 +26,7 @@ Throwing valuable benchmark data away every time I needed to optimize another re
Obviously providing a visual representation of the benchmark results, via simple graphs, would make interpreting them easier. Having not to think about Internet Explorer for once, made writing CSS a whole lot more easy and fun. It resulted in some fine graphs which are fully resizable. Obviously providing a visual representation of the benchmark results, via simple graphs, would make interpreting them easier. Having not to think about Internet Explorer for once, made writing CSS a whole lot more easy and fun. It resulted in some fine graphs which are fully resizable.
Below are two screenshots of Codebench in action. `Valid_Color` is a class made for benchmarking different ways to validate hexadecimal HTML color values, e.g. `#FFF`. If you are interested in the story behind the actual regular expressions, take a look at [this topic in the Kohana forums](http://forum.kohanaframework.org/discussion/2192). Below are two screenshots of Codebench in action. `Valid_Color` is a class made for benchmarking different ways to validate hexadecimal HTML color values, e.g. `#FFF`. If you are interested in the story behind the actual regular expressions, take a look at [this topic in the Kohana forums](http://forum.kohanaphp.com/comments.php?DiscussionID=2192).
![Benchmarking several ways to validate HTML color values](codebench_screenshot1.png) ![Benchmarking several ways to validate HTML color values](codebench_screenshot1.png)
**Benchmarking seven ways to validate HTML color values** **Benchmarking seven ways to validate HTML color values**
@ -73,4 +73,4 @@ Here is another short example with some extra explanations.
And the winner is… [ltrim](http://php.net/ltrim). Happy benchmarking! And the winner is… [ltrim](http://php.net/ltrim). Happy benchmarking!

View File

@ -1,8 +0,0 @@
<?php
// Configuration for koharness - builds a standalone skeleton Kohana app for running unit tests
return array(
'modules' => array(
'codebench' => __DIR__,
'unittest' => __DIR__ . '/vendor/kohana/unittest'
),
);

View File

@ -0,0 +1,86 @@
# Kohana-Cron
This module provides a way to schedule tasks (jobs) within your Kohana application.
## Installation
Step 1: Download the module into your modules subdirectory.
Step 2: Enable the module in your bootstrap file:
/**
* Enable modules. Modules are referenced by a relative or absolute path.
*/
Kohana::modules(array(
'cron' => MODPATH.'cron',
// 'auth' => MODPATH.'auth', // Basic authentication
// 'codebench' => MODPATH.'codebench', // Benchmarking tool
// 'database' => MODPATH.'database', // Database access
// 'image' => MODPATH.'image', // Image manipulation
// 'orm' => MODPATH.'orm', // Object Relationship Mapping
// 'pagination' => MODPATH.'pagination', // Paging of results
// 'userguide' => MODPATH.'userguide', // User guide and API documentation
));
Step 3: Make sure the settings in `config/cron.php` are correct for your environment.
If not, copy the file to `application/config/cron.php` and change the values accordingly.
## Usage
In its simplest form, a task is a [PHP callback][1] and times at which it should run.
To configure a task call `Cron::set($name, array($frequency, $callback))` where
`$frequency` is a string of date and time fields identical to those found in [crontab][2].
For example,
Cron::set('reindex_catalog', array('@daily', 'Catalog::regenerate_index'));
Cron::set('calendar_notifications', array('*/5 * * * *', 'Calendar::send_emails'));
Configured tasks are run with their appropriate frequency by calling `Cron::run()`. Call
this method in your bootstrap file, and you're done!
## Advanced Usage
A task can also be an instance of `Cron` that extends `next()` and/or `execute()` as
needed. Such a task is configured by calling `Cron::set($name, $instance)`.
If you have access to the system crontab, you can run Cron less (or more) than once
every request. You will need to modify the lines where the request is handled in your
bootstrap file to prevent extraneous output. The default is:
/**
* Execute the main request. A source of the URI can be passed, eg: $_SERVER['PATH_INFO'].
* If no source is specified, the URI will be automatically detected.
*/
echo Request::instance()
->execute()
->send_headers()
->response;
Change it to:
if ( ! defined('SUPPRESS_REQUEST'))
{
/**
* Execute the main request. A source of the URI can be passed, eg: $_SERVER['PATH_INFO'].
* If no source is specified, the URI will be automatically detected.
*/
echo Request::instance()
->execute()
->send_headers()
->response;
}
Then set up a system cron job to run your application's Cron once a minute:
* * * * * /usr/bin/php -f /path/to/kohana/modules/cron/run.php
The included `run.php` should work for most cases, but you are free to call `Cron::run()`
in any way you see fit.
[1]: http://php.net/manual/language.pseudo-types.php#language.types.callback
[2]: http://linux.die.net/man/5/crontab

View File

@ -0,0 +1,10 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* @package Cron
*
* @author Chris Bandy
* @copyright (c) 2010 Chris Bandy
* @license http://www.opensource.org/licenses/isc-license.txt
*/
class Cron extends Kohana_Cron {}

View File

@ -0,0 +1,621 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* @package Cron
*
* @author Chris Bandy
* @copyright (c) 2010 Chris Bandy
* @license http://www.opensource.org/licenses/isc-license.txt
*/
class Kohana_Cron
{
protected static $_jobs = array();
protected static $_times = array();
/**
* Registers a job to be run
*
* @param string Unique name
* @param array|Cron Job to run
*/
public static function set($name, $job)
{
if (is_array($job))
{
$job = new Cron(reset($job), next($job));
}
Cron::$_jobs[$name] = $job;
}
/**
* Retrieve the timestamps of when jobs should run
*/
protected static function _load()
{
Cron::$_times = Kohana::cache("Cron::run()");
}
/**
* Acquire the Cron mutex
*
* @return boolean
*/
protected static function _lock()
{
$config = Kohana::$config->load('cron');
$result = FALSE;
if (file_exists($config->lock) AND ($stat = @stat($config->lock)) AND time() - $config->window < $stat['mtime'])
{
// Lock exists and has not expired
return $result;
}
$fh = fopen($config->lock, 'a');
if (flock($fh, LOCK_EX))
{
fseek($fh, 0, SEEK_END);
if (ftell($fh) === (empty($stat) ? 0 : $stat['size']))
{
// Current size matches expected size
// Claim the file by changing the size
fwrite($fh, '.');
$result = TRUE;
}
// else, Another process acquired during flock()
}
fclose($fh);
return $result;
}
/**
* Store the timestamps of when jobs should run next
*/
protected static function _save()
{
Kohana::cache("Cron::run()", Cron::$_times, Kohana::$config->load('cron')->window * 2);
}
/**
* Release the Cron mutex
*/
protected static function _unlock()
{
return @unlink(Kohana::$config->load('cron')->lock);
}
/**
* @return boolean FALSE when another instance is running
*/
public static function run()
{
if (empty(Cron::$_jobs))
return TRUE;
if ( ! Cron::_lock())
return FALSE;
try
{
Cron::_load();
$now = time();
$threshold = $now - Kohana::$config->load('cron')->window;
foreach (Cron::$_jobs as $name => $job)
{
if (empty(Cron::$_times[$name]) OR Cron::$_times[$name] < $threshold)
{
// Expired
Cron::$_times[$name] = $job->next($now);
if ($job->next($threshold) < $now)
{
// Within the window
$job->execute();
}
}
elseif (Cron::$_times[$name] < $now)
{
// Within the window
Cron::$_times[$name] = $job->next($now);
$job->execute();
}
}
}
catch (Exception $e) {}
Cron::_save();
Cron::_unlock();
if (isset($e))
throw $e;
return TRUE;
}
protected $_callback;
protected $_period;
public function __construct($period, $callback)
{
$this->_period = $period;
$this->_callback = $callback;
}
/**
* Execute this job
*/
public function execute()
{
call_user_func($this->_callback);
}
/**
* Calculates the next timestamp in this period
*
* @param integer Timestamp from which to calculate
* @return integer Next timestamp in this period
*/
public function next($from)
{
// PHP >= 5.3.0
//if ($this->_period instanceof DatePeriod) { return; }
//if (is_string($this->_period) AND preg_match('/^P[\dDHMSTWY]+$/', $period)) { $this->_period = new DateInterval($this->_period); }
//if ($this->_period instanceof DateInterval) { return; }
return $this->_next_crontab($from);
}
/**
* Calculates the next timestamp of this crontab period
*
* @param integer Timestamp from which to calculate
* @return integer Next timestamp in this period
*/
protected function _next_crontab($from)
{
if (is_string($this->_period))
{
// Convert string to lists of valid values
if ($this->_period[0] === '@')
{
switch (substr($this->_period, 1))
{
case 'annually':
case 'yearly':
// '0 0 1 1 *'
$this->_period = array('minutes' => array(0), 'hours' => array(0), 'monthdays' => array(1), 'months' => array(1), 'weekdays' => range(0,6));
break;
case 'daily':
case 'midnight':
// '0 0 * * *'
$this->_period = array('minutes' => array(0), 'hours' => array(0), 'monthdays' => range(1,31), 'months' => range(1,12), 'weekdays' => range(0,6));
break;
case 'hourly':
// '0 * * * *'
$this->_period = array('minutes' => array(0), 'hours' => range(0,23), 'monthdays' => range(1,31), 'months' => range(1,12), 'weekdays' => range(0,6));
break;
case 'monthly':
// '0 0 1 * *'
$this->_period = array('minutes' => array(0), 'hours' => array(0), 'monthdays' => array(1), 'months' => range(1,12), 'weekdays' => range(0,6));
break;
case 'weekly':
// '0 0 * * 0'
$this->_period = array('minutes' => array(0), 'hours' => array(0), 'monthdays' => range(1,31), 'months' => range(1,12), 'weekdays' => array(0));
break;
}
}
else
{
list($minutes, $hours, $monthdays, $months, $weekdays) = explode(' ', $this->_period);
$months = strtr(strtolower($months), array(
'jan' => 1,
'feb' => 2,
'mar' => 3,
'apr' => 4,
'may' => 5,
'jun' => 6,
'jul' => 7,
'aug' => 8,
'sep' => 9,
'oct' => 10,
'nov' => 11,
'dec' => 12,
));
$weekdays = strtr(strtolower($weekdays), array(
'sun' => 0,
'mon' => 1,
'tue' => 2,
'wed' => 3,
'thu' => 4,
'fri' => 5,
'sat' => 6,
));
$this->_period = array(
'minutes' => $this->_parse_crontab_field($minutes, 0, 59),
'hours' => $this->_parse_crontab_field($hours, 0, 23),
'monthdays' => $this->_parse_crontab_field($monthdays, 1, 31),
'months' => $this->_parse_crontab_field($months, 1, 12),
'weekdays' => $this->_parse_crontab_field($weekdays, 0, 7)
);
// Ensure Sunday is zero
if (end($this->_period['weekdays']) === 7)
{
array_pop($this->_period['weekdays']);
if (reset($this->_period['weekdays']) !== 0)
{
array_unshift($this->_period['weekdays'], 0);
}
}
}
}
$from = getdate($from);
if ( ! in_array($from['mon'], $this->_period['months']))
return $this->_next_crontab_month($from);
if (count($this->_period['weekdays']) === 7)
{
// Day of Week is unrestricted, defer to Day of Month
if ( ! in_array($from['mday'], $this->_period['monthdays']))
return $this->_next_crontab_monthday($from);
}
elseif (count($this->_period['monthdays']) === 31)
{
// Day of Month is unrestricted, use Day of Week
if ( ! in_array($from['wday'], $this->_period['weekdays']))
return $this->_next_crontab_weekday($from);
}
else
{
// Both Day of Week and Day of Month are restricted
if ( ! in_array($from['mday'], $this->_period['monthdays']) AND ! in_array($from['wday'], $this->_period['weekdays']))
return $this->_next_crontab_day($from);
}
if ( ! in_array($from['hours'], $this->_period['hours']))
return $this->_next_crontab_hour($from);
return $this->_next_crontab_minute($from);
}
/**
* Calculates the first timestamp in the next day of this period when both
* Day of Week and Day of Month are restricted
*
* @uses _next_crontab_month()
*
* @param array Date array from getdate()
* @return integer Timestamp of next restricted Day
*/
protected function _next_crontab_day(array $from)
{
// Calculate effective Day of Month for next Day of Week
if ($from['wday'] >= end($this->_period['weekdays']))
{
$next = reset($this->_period['weekdays']) + 7;
}
else
{
foreach ($this->_period['weekdays'] as $next)
{
if ($from['wday'] < $next)
break;
}
}
$monthday = $from['mday'] + $next - $from['wday'];
if ($monthday <= (int) date('t', mktime(0, 0, 0, $from['mon'], 1, $from['year'])))
{
// Next Day of Week is in this Month
if ($from['mday'] >= end($this->_period['monthdays']))
{
// No next Day of Month, use next Day of Week
$from['mday'] = $monthday;
}
else
{
// Calculate next Day of Month
foreach ($this->_period['monthdays'] as $next)
{
if ($from['mday'] < $next)
break;
}
// Use earliest day
$from['mday'] = min($monthday, $next);
}
}
else
{
if ($from['mday'] >= end($this->_period['monthdays']))
{
// No next Day of Month, use next Month
return $this->_next_crontab_month($from);
}
// Calculate next Day of Month
foreach ($this->_period['monthdays'] as $next)
{
if ($from['mday'] < $next)
break;
}
// Use next Day of Month
$from['mday'] = $next;
}
// Use first Hour and first Minute
return mktime(reset($this->_period['hours']), reset($this->_period['minutes']), 0, $from['mon'], $from['mday'], $from['year']);
}
/**
* Calculates the first timestamp in the next hour of this period
*
* @uses _next_crontab_day()
* @uses _next_crontab_monthday()
* @uses _next_crontab_weekday()
*
* @param array Date array from getdate()
* @return integer Timestamp of next Hour
*/
protected function _next_crontab_hour(array $from)
{
if ($from['hours'] >= end($this->_period['hours']))
{
// No next Hour
if (count($this->_period['weekdays']) === 7)
{
// Day of Week is unrestricted, defer to Day of Month
return $this->_next_crontab_monthday($from);
}
if (count($this->_period['monthdays']) === 31)
{
// Day of Month is unrestricted, use Day of Week
return $this->_next_crontab_weekday($from);
}
// Both Day of Week and Day of Month are restricted
return $this->_next_crontab_day($from);
}
// Calculate next Hour
foreach ($this->_period['hours'] as $next)
{
if ($from['hours'] < $next)
break;
}
// Use next Hour and first Minute
return mktime($next, reset($this->_period['minutes']), 0, $from['mon'], $from['mday'], $from['year']);
}
/**
* Calculates the timestamp of the next minute in this period
*
* @uses _next_crontab_hour()
*
* @param array Date array from getdate()
* @return integer Timestamp of next Minute
*/
protected function _next_crontab_minute(array $from)
{
if ($from['minutes'] >= end($this->_period['minutes']))
{
// No next Minute, use next Hour
return $this->_next_crontab_hour($from);
}
// Calculate next Minute
foreach ($this->_period['minutes'] as $next)
{
if ($from['minutes'] < $next)
break;
}
// Use next Minute
return mktime($from['hours'], $next, 0, $from['mon'], $from['mday'], $from['year']);
}
/**
* Calculates the first timestamp in the next month of this period
*
* @param array Date array from getdate()
* @return integer Timestamp of next Month
*/
protected function _next_crontab_month(array $from)
{
if ($from['mon'] >= end($this->_period['months']))
{
// No next Month, increment Year and use first Month
++$from['year'];
$from['mon'] = reset($this->_period['months']);
}
else
{
// Calculate next Month
foreach ($this->_period['months'] as $next)
{
if ($from['mon'] < $next)
break;
}
// Use next Month
$from['mon'] = $next;
}
if (count($this->_period['weekdays']) === 7)
{
// Day of Week is unrestricted, use first Day of Month
$from['mday'] = reset($this->_period['monthdays']);
}
else
{
// Calculate Day of Month for the first Day of Week
$indices = array_flip($this->_period['weekdays']);
$monthday = 1;
$weekday = (int) date('w', mktime(0, 0, 0, $from['mon'], 1, $from['year']));
while ( ! isset($indices[$weekday % 7]) AND $monthday < 7)
{
++$monthday;
++$weekday;
}
if (count($this->_period['monthdays']) === 31)
{
// Day of Month is unrestricted, use first Day of Week
$from['mday'] = $monthday;
}
else
{
// Both Day of Month and Day of Week are restricted, use earliest one
$from['mday'] = min($monthday, reset($this->_period['monthdays']));
}
}
// Use first Hour and first Minute
return mktime(reset($this->_period['hours']), reset($this->_period['minutes']), 0, $from['mon'], $from['mday'], $from['year']);
}
/**
* Calculates the first timestamp in the next day of this period when only
* Day of Month is restricted
*
* @uses _next_crontab_month()
*
* @param array Date array from getdate()
* @return integer Timestamp of next Day of Month
*/
protected function _next_crontab_monthday(array $from)
{
if ($from['mday'] >= end($this->_period['monthdays']))
{
// No next Day of Month, use next Month
return $this->_next_crontab_month($from);
}
// Calculate next Day of Month
foreach ($this->_period['monthdays'] as $next)
{
if ($from['mday'] < $next)
break;
}
// Use next Day of Month, first Hour, and first Minute
return mktime(reset($this->_period['hours']), reset($this->_period['minutes']), 0, $from['mon'], $next, $from['year']);
}
/**
* Calculates the first timestamp in the next day of this period when only
* Day of Week is restricted
*
* @uses _next_crontab_month()
*
* @param array Date array from getdate()
* @return integer Timestamp of next Day of Week
*/
protected function _next_crontab_weekday(array $from)
{
// Calculate effective Day of Month for next Day of Week
if ($from['wday'] >= end($this->_period['weekdays']))
{
$next = reset($this->_period['weekdays']) + 7;
}
else
{
foreach ($this->_period['weekdays'] as $next)
{
if ($from['wday'] < $next)
break;
}
}
$monthday = $from['mday'] + $next - $from['wday'];
if ($monthday > (int) date('t', mktime(0, 0, 0, $from['mon'], 1, $from['year'])))
{
// Next Day of Week is not in this Month, use next Month
return $this->_next_crontab_month($from);
}
// Use next Day of Week, first Hour, and first Minute
return mktime(reset($this->_period['hours']), reset($this->_period['minutes']), 0, $from['mon'], $monthday, $from['year']);
}
/**
* Returns a sorted array of all the values indicated in a Crontab field
* @link http://linux.die.net/man/5/crontab
*
* @param string Crontab field
* @param integer Minimum value for this field
* @param integer Maximum value for this field
* @return array
*/
protected function _parse_crontab_field($value, $min, $max)
{
$result = array();
foreach (explode(',', $value) as $value)
{
if ($slash = strrpos($value, '/'))
{
$step = (int) substr($value, $slash + 1);
$value = substr($value, 0, $slash);
}
if ($value === '*')
{
$result = array_merge($result, range($min, $max, $slash ? $step : 1));
}
elseif ($dash = strpos($value, '-'))
{
$result = array_merge($result, range(max($min, (int) substr($value, 0, $dash)), min($max, (int) substr($value, $dash + 1)), $slash ? $step : 1));
}
else
{
$value = (int) $value;
if ($min <= $value AND $value <= $max)
{
$result[] = $value;
}
}
}
sort($result);
return array_unique($result);
}
}

View File

@ -0,0 +1,28 @@
<?php defined('SYSPATH') OR die('No direct access allowed.');
/**
* @package Cron
*
* @author Chris Bandy
* @copyright (c) 2010 Chris Bandy
* @license http://www.opensource.org/licenses/isc-license.txt
*/
return array
(
// Path to a writable directory and lock file
'lock' => Kohana::$cache_dir.DIRECTORY_SEPARATOR.'cron.lck',
/**
* Cron does not run EXACTLY when tasks are scheduled.
* A task can be executed up to this many seconds AFTER its scheduled time.
*
* For example, Cron is run at 10:48 and a task was scheduled to execute at
* 10:45, 180 seconds ago. If window is greater than 180, the task will be
* executed.
*
* This value should always be larger than the time it takes to run all
* your tasks.
*/
'window' => 300,
);

22
modules/cron/run.php Normal file
View File

@ -0,0 +1,22 @@
<?php
/**
* @package Cron
*
* @author Chris Bandy
* @copyright (c) 2010 Chris Bandy
* @license http://www.opensource.org/licenses/isc-license.txt
*/
// Path to Kohana's index.php
$system = dirname(dirname(dirname(__FILE__))).DIRECTORY_SEPARATOR.'index.php';
if (file_exists($system))
{
defined('SUPPRESS_REQUEST') or define('SUPPRESS_REQUEST', TRUE);
include $system;
// If Cron has been run in APPPATH/bootstrap.php, this second call is harmless
Cron::run();
}

View File

@ -0,0 +1,85 @@
<?php
/**
* @package Cron
* @group kohana
* @group kohana.cron
*
* @author Chris Bandy
* @copyright (c) 2010 Chris Bandy
* @license http://www.opensource.org/licenses/isc-license.txt
*/
class Kohana_Cron_Test extends PHPUnit_Framework_TestCase
{
/**
* @test
* @dataProvider provider_next
*
* @param string Period
* @param integer Timestamp from which to calculate
* @param integer Next timestamp in period
*/
public function test_next($period, $from, $expected_result)
{
$cron = new Cron($period, NULL);
$result = $cron->next($from);
$this->assertSame($expected_result, $result);
}
public function provider_next()
{
return array
(
array('@annually', mktime(8, 45, 0, 11, 19, 2009), mktime(0, 0, 0, 1, 1, 2010)),
array('@monthly', mktime(8, 45, 0, 11, 19, 2009), mktime(0, 0, 0, 12, 1, 2009)),
array('@weekly', mktime(8, 45, 0, 11, 19, 2009), mktime(0, 0, 0, 11, 22, 2009)),
array('@daily', mktime(8, 45, 0, 11, 19, 2009), mktime(0, 0, 0, 11, 20, 2009)),
array('@hourly', mktime(8, 45, 0, 11, 19, 2009), mktime(9, 0, 0, 11, 19, 2009)),
array('* * * * *', mktime(8, 45, 0, 11, 19, 2009), mktime(8, 46, 0, 11, 19, 2009)),
array(
'* * * * 0', // Sundays
mktime(0, 0, 0, 11, 30, 2009), // Monday, Nov 30, 2009
mktime(0, 0, 0, 12, 6, 2009) // Sunday, Dec 6, 2009
),
array(
'* * 15 * 6', // 15th and Saturdays
mktime(0, 0, 0, 11, 29, 2009), // Sunday, Nov 29, 2009
mktime(0, 0, 0, 12, 5, 2009) // Saturday, Dec 5, 2009
),
array(
'* * * * 1,5', // Mondays and Fridays
mktime(0, 0, 0, 11, 24, 2009), // Tuesday, Nov 24, 2009
mktime(0, 0, 0, 11, 27, 2009) // Friday, Nov 27, 2009
),
array(
'* * 15 * 6-7', // 15th, Saturdays, and Sundays
mktime(0, 0, 0, 11, 23, 2009), // Monday, Nov 23, 2009
mktime(0, 0, 0, 11, 28, 2009) // Saturday, Nov 28, 2009
),
array(
'* * 15,30 * 2', // 15th, 30th, and Tuesdays
mktime(0, 0, 0, 11, 29, 2009), // Sunday, Nov 29, 2009
mktime(0, 0, 0, 11, 30, 2009) // Monday, Nov 30, 2009
),
array(
'0 0 * * 4', // Midnight on Thursdays
mktime(1, 0, 0, 11, 19, 2009), // 01:00 Thursday, Nov 19, 2009
mktime(0, 0, 0, 11, 26, 2009) // 00:00 Thursday, Nov 26, 2009
),
array(
'0 0 */2 * 4', // Midnight on odd days and Thursdays
mktime(1, 0, 0, 11, 19, 2009), // 01:00 Thursday, Nov 19, 2009
mktime(0, 0, 0, 11, 21, 2009) // 00:00 Saturday, Nov 21, 2009
),
);
}
}

View File

@ -1,37 +0,0 @@
sudo: false
language: php
# Only build the main develop/master branches - feature branches will be covered by PRs
branches:
only:
- /^[0-9\.]+\/(develop|master)$/
cache:
directories:
- vendor
- $HOME/.composer/cache
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
matrix:
include:
- php: 5.3
env: 'COMPOSER_PHPUNIT="lowest"'
before_script:
- composer install --prefer-dist
- if [ "$COMPOSER_PHPUNIT" = "lowest" ]; then composer update --prefer-lowest --with-dependencies phpunit/phpunit; fi;
- vendor/bin/koharness
script:
- cd /tmp/koharness && ./vendor/bin/phpunit --bootstrap=modules/unittest/bootstrap.php modules/unittest/tests.php
notifications:
email: false

View File

@ -1,6 +0,0 @@
# Kohana - database access module
| ver | Stable | Develop |
|-------|--------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|
| 3.3.x | [![Build Status - 3.3/master](https://travis-ci.org/kohana/database.svg?branch=3.3%2Fmaster)](https://travis-ci.org/kohana/database) | [![Build Status - 3.3/develop](https://travis-ci.org/kohana/database.svg?branch=3.3%2Fdevelop)](https://travis-ci.org/kohana/database) |
| 3.4.x | [![Build Status - 3.4/master](https://travis-ci.org/kohana/database.svg?branch=3.4%2Fmaster)](https://travis-ci.org/kohana/database) | [![Build Status - 3.4/develop](https://travis-ci.org/kohana/database.svg?branch=3.4%2Fdevelop)](https://travis-ci.org/kohana/database) |

View File

@ -1,3 +0,0 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Database_MySQLi extends Kohana_Database_MySQLi {}

View File

@ -1,3 +0,0 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Database_MySQLi_Result extends Kohana_Database_MySQLi_Result {}

View File

@ -9,7 +9,7 @@
* @copyright (c) 2012 Kohana Team * @copyright (c) 2012 Kohana Team
* @license http://kohanaframework.org/license * @license http://kohanaframework.org/license
*/ */
class Kohana_Config_Database extends Config_Database_Writer class Kohana_Config_Database extends Kohana_Config_Database_Writer
{ {
} }

View File

@ -3,15 +3,6 @@
/** /**
* Database writer for the config system * Database writer for the config system
* *
* Schema for configuration table:
*
* CREATE TABLE IF NOT EXISTS `config` (
* `group_name` varchar(128) NOT NULL,
* `config_key` varchar(128) NOT NULL,
* `config_value` text,
* PRIMARY KEY (`group_name`,`config_key`)
* ) ENGINE=InnoDB;
*
* @package Kohana * @package Kohana
* @category Configuration * @category Configuration
* @author Kohana Team * @author Kohana Team

View File

@ -51,9 +51,6 @@ class Kohana_Database_MySQL_Result extends Database_Result {
// Increment internal row for optimization assuming rows are fetched in order // Increment internal row for optimization assuming rows are fetched in order
$this->_internal_row++; $this->_internal_row++;
// FIXME mysql_fetch_object has been deprecated as of php 5.5!
// Please use mysqli_fetch_object or PDOStatement::fetch(PDO::FETCH_OBJ) instead.
if ($this->_as_object === TRUE) if ($this->_as_object === TRUE)
{ {
// Return an stdClass // Return an stdClass
@ -61,32 +58,8 @@ class Kohana_Database_MySQL_Result extends Database_Result {
} }
elseif (is_string($this->_as_object)) elseif (is_string($this->_as_object))
{ {
/* The second and third argument for mysql_fetch_object are optional, but do // Return an object of given class name
* not have default values defined. Passing _object_params with a non-array value results return mysql_fetch_object($this->_result, $this->_as_object, $this->_object_params);
* in undefined behavior that varies by PHP version. For example, if NULL is supplied on
* PHP 5.3, the resulting behavior is identical to calling with array(), which results in the
* classes __construct function being called with no arguments. This is only an issue when
* the _as_object class does not have an explicit __construct method resulting in the
* cryptic error "Class %s does not have a constructor hence you cannot use ctor_params."
* In contrast, the same function call on PHP 5.5 will 'functionally' interpret
* _object_params == NULL as an omission of the third argument, resulting in the original
* intended functionally.
*
* Because the backing code for the mysql_fetch_object has not changed between 5.3 and 5.5,
* I suspect this discrepancy is due to the way the classes are instantiated on a boarder
* level. Additionally, mysql_fetch_object has been deprecated in 5.5 and should probably be
* replaced by mysqli_fetch_object or PDOStatement::fetch(PDO::FETCH_OBJ) in Kohana 3.4.
*/
if ($this->_object_params !== NULL)
{
// Return an object of given class name with constructor params
return mysql_fetch_object($this->_result, $this->_as_object, $this->_object_params);
}
else
{
// Return an object of given class name without constructor params
return mysql_fetch_object($this->_result, $this->_as_object);
}
} }
else else
{ {

View File

@ -1,422 +0,0 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* MySQLi database connection.
*
* @package Kohana/Database
* @category Drivers
* @author Kohana Team
* @copyright (c) 2008-2009 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Database_MySQLi extends Database {
// Database in use by each connection
protected static $_current_databases = array();
// Use SET NAMES to set the character set
protected static $_set_names;
// Identifier for this connection within the PHP driver
protected $_connection_id;
// MySQL uses a backtick for identifiers
protected $_identifier = '`';
public function connect()
{
if ($this->_connection)
return;
if (Database_MySQLi::$_set_names === NULL)
{
// Determine if we can use mysqli_set_charset(), which is only
// available on PHP 5.2.3+ when compiled against MySQL 5.0+
Database_MySQLi::$_set_names = ! function_exists('mysqli_set_charset');
}
// Extract the connection parameters, adding required variabels
extract($this->_config['connection'] + array(
'database' => '',
'hostname' => '',
'username' => '',
'password' => '',
'socket' => '',
'port' => 3306,
'ssl' => NULL,
));
// Prevent this information from showing up in traces
unset($this->_config['connection']['username'], $this->_config['connection']['password']);
try
{
if(is_array($ssl))
{
$this->_connection = mysqli_init();
$this->_connection->ssl_set(
Arr::get($ssl, 'client_key_path'),
Arr::get($ssl, 'client_cert_path'),
Arr::get($ssl, 'ca_cert_path'),
Arr::get($ssl, 'ca_dir_path'),
Arr::get($ssl, 'cipher')
);
$this->_connection->real_connect($hostname, $username, $password, $database, $port, $socket, MYSQLI_CLIENT_SSL);
}
else
{
$this->_connection = new mysqli($hostname, $username, $password, $database, $port, $socket);
}
}
catch (Exception $e)
{
// No connection exists
$this->_connection = NULL;
throw new Database_Exception(':error', array(':error' => $e->getMessage()), $e->getCode());
}
// \xFF is a better delimiter, but the PHP driver uses underscore
$this->_connection_id = sha1($hostname.'_'.$username.'_'.$password);
if ( ! empty($this->_config['charset']))
{
// Set the character set
$this->set_charset($this->_config['charset']);
}
if ( ! empty($this->_config['connection']['variables']))
{
// Set session variables
$variables = array();
foreach ($this->_config['connection']['variables'] as $var => $val)
{
$variables[] = 'SESSION '.$var.' = '.$this->quote($val);
}
$this->_connection->query('SET '.implode(', ', $variables));
}
}
public function disconnect()
{
try
{
// Database is assumed disconnected
$status = TRUE;
if (is_resource($this->_connection))
{
if ($status = $this->_connection->close())
{
// Clear the connection
$this->_connection = NULL;
// Clear the instance
parent::disconnect();
}
}
}
catch (Exception $e)
{
// Database is probably not disconnected
$status = ! is_resource($this->_connection);
}
return $status;
}
public function set_charset($charset)
{
// Make sure the database is connected
$this->_connection or $this->connect();
if (Database_MySQLi::$_set_names === TRUE)
{
// PHP is compiled against MySQL 4.x
$status = (bool) $this->_connection->query('SET NAMES '.$this->quote($charset));
}
else
{
// PHP is compiled against MySQL 5.x
$status = $this->_connection->set_charset($charset);
}
if ($status === FALSE)
{
throw new Database_Exception(':error', array(':error' => $this->_connection->error), $this->_connection->errno);
}
}
public function query($type, $sql, $as_object = FALSE, array $params = NULL)
{
// Make sure the database is connected
$this->_connection or $this->connect();
if (Kohana::$profiling)
{
// Benchmark this query for the current instance
$benchmark = Profiler::start("Database ({$this->_instance})", $sql);
}
// Execute the query
if (($result = $this->_connection->query($sql)) === FALSE)
{
if (isset($benchmark))
{
// This benchmark is worthless
Profiler::delete($benchmark);
}
throw new Database_Exception(':error [ :query ]', array(
':error' => $this->_connection->error,
':query' => $sql
), $this->_connection->errno);
}
if (isset($benchmark))
{
Profiler::stop($benchmark);
}
// Set the last query
$this->last_query = $sql;
if ($type === Database::SELECT)
{
// Return an iterator of results
return new Database_MySQLi_Result($result, $sql, $as_object, $params);
}
elseif ($type === Database::INSERT)
{
// Return a list of insert id and rows created
return array(
$this->_connection->insert_id,
$this->_connection->affected_rows,
);
}
else
{
// Return the number of rows affected
return $this->_connection->affected_rows;
}
}
public function datatype($type)
{
static $types = array
(
'blob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '65535'),
'bool' => array('type' => 'bool'),
'bigint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '18446744073709551615'),
'datetime' => array('type' => 'string'),
'decimal unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
'double' => array('type' => 'float'),
'double precision unsigned' => array('type' => 'float', 'min' => '0'),
'double unsigned' => array('type' => 'float', 'min' => '0'),
'enum' => array('type' => 'string'),
'fixed' => array('type' => 'float', 'exact' => TRUE),
'fixed unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
'float unsigned' => array('type' => 'float', 'min' => '0'),
'geometry' => array('type' => 'string', 'binary' => TRUE),
'int unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'),
'integer unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'),
'longblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '4294967295'),
'longtext' => array('type' => 'string', 'character_maximum_length' => '4294967295'),
'mediumblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '16777215'),
'mediumint' => array('type' => 'int', 'min' => '-8388608', 'max' => '8388607'),
'mediumint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '16777215'),
'mediumtext' => array('type' => 'string', 'character_maximum_length' => '16777215'),
'national varchar' => array('type' => 'string'),
'numeric unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
'nvarchar' => array('type' => 'string'),
'point' => array('type' => 'string', 'binary' => TRUE),
'real unsigned' => array('type' => 'float', 'min' => '0'),
'set' => array('type' => 'string'),
'smallint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '65535'),
'text' => array('type' => 'string', 'character_maximum_length' => '65535'),
'tinyblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '255'),
'tinyint' => array('type' => 'int', 'min' => '-128', 'max' => '127'),
'tinyint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '255'),
'tinytext' => array('type' => 'string', 'character_maximum_length' => '255'),
'year' => array('type' => 'string'),
);
$type = str_replace(' zerofill', '', $type);
if (isset($types[$type]))
return $types[$type];
return parent::datatype($type);
}
/**
* Start a SQL transaction
*
* @link http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
*
* @param string $mode Isolation level
* @return boolean
*/
public function begin($mode = NULL)
{
// Make sure the database is connected
$this->_connection or $this->connect();
if ($mode AND ! $this->_connection->query("SET TRANSACTION ISOLATION LEVEL $mode"))
{
throw new Database_Exception(':error', array(
':error' => $this->_connection->error
), $this->_connection->errno);
}
return (bool) $this->_connection->query('START TRANSACTION');
}
/**
* Commit a SQL transaction
*
* @return boolean
*/
public function commit()
{
// Make sure the database is connected
$this->_connection or $this->connect();
return (bool) $this->_connection->query('COMMIT');
}
/**
* Rollback a SQL transaction
*
* @return boolean
*/
public function rollback()
{
// Make sure the database is connected
$this->_connection or $this->connect();
return (bool) $this->_connection->query('ROLLBACK');
}
public function list_tables($like = NULL)
{
if (is_string($like))
{
// Search for table names
$result = $this->query(Database::SELECT, 'SHOW TABLES LIKE '.$this->quote($like), FALSE);
}
else
{
// Find all table names
$result = $this->query(Database::SELECT, 'SHOW TABLES', FALSE);
}
$tables = array();
foreach ($result as $row)
{
$tables[] = reset($row);
}
return $tables;
}
public function list_columns($table, $like = NULL, $add_prefix = TRUE)
{
// Quote the table name
$table = ($add_prefix === TRUE) ? $this->quote_table($table) : $table;
if (is_string($like))
{
// Search for column names
$result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table.' LIKE '.$this->quote($like), FALSE);
}
else
{
// Find all column names
$result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table, FALSE);
}
$count = 0;
$columns = array();
foreach ($result as $row)
{
list($type, $length) = $this->_parse_type($row['Type']);
$column = $this->datatype($type);
$column['column_name'] = $row['Field'];
$column['column_default'] = $row['Default'];
$column['data_type'] = $type;
$column['is_nullable'] = ($row['Null'] == 'YES');
$column['ordinal_position'] = ++$count;
switch ($column['type'])
{
case 'float':
if (isset($length))
{
list($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length);
}
break;
case 'int':
if (isset($length))
{
// MySQL attribute
$column['display'] = $length;
}
break;
case 'string':
switch ($column['data_type'])
{
case 'binary':
case 'varbinary':
$column['character_maximum_length'] = $length;
break;
case 'char':
case 'varchar':
$column['character_maximum_length'] = $length;
case 'text':
case 'tinytext':
case 'mediumtext':
case 'longtext':
$column['collation_name'] = $row['Collation'];
break;
case 'enum':
case 'set':
$column['collation_name'] = $row['Collation'];
$column['options'] = explode('\',\'', substr($length, 1, -1));
break;
}
break;
}
// MySQL attributes
$column['comment'] = $row['Comment'];
$column['extra'] = $row['Extra'];
$column['key'] = $row['Key'];
$column['privileges'] = $row['Privileges'];
$columns[$row['Field']] = $column;
}
return $columns;
}
public function escape($value)
{
// Make sure the database is connected
$this->_connection or $this->connect();
if (($value = $this->_connection->real_escape_string( (string) $value)) === FALSE)
{
throw new Database_Exception(':error', array(
':error' => $this->_connection->error,
), $this->_connection->errno);
}
// SQL standard is to use single-quotes for all values
return "'$value'";
}
} // End Database_MySQLi

View File

@ -1,71 +0,0 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* MySQLi database result. See [Results](/database/results) for usage and examples.
*
* @package Kohana/Database
* @category Query/Result
* @author Kohana Team
* @copyright (c) 2008-2009 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Database_MySQLi_Result extends Database_Result {
protected $_internal_row = 0;
public function __construct($result, $sql, $as_object = FALSE, array $params = NULL)
{
parent::__construct($result, $sql, $as_object, $params);
// Find the number of rows in the result
$this->_total_rows = $result->num_rows;
}
public function __destruct()
{
if (is_resource($this->_result))
{
$this->_result->free();
}
}
public function seek($offset)
{
if ($this->offsetExists($offset) AND $this->_result->data_seek($offset))
{
// Set the current row to the offset
$this->_current_row = $this->_internal_row = $offset;
return TRUE;
}
else
{
return FALSE;
}
}
public function current()
{
if ($this->_current_row !== $this->_internal_row AND ! $this->seek($this->_current_row))
return NULL;
// Increment internal row for optimization assuming rows are fetched in order
$this->_internal_row++;
if ($this->_as_object === TRUE)
{
// Return an stdClass
return $this->_result->fetch_object();
}
elseif (is_string($this->_as_object))
{
// Return an object of given class name
return $this->_result->fetch_object($this->_as_object, (array) $this->_object_params);
}
else
{
// Return an array of the row
return $this->_result->fetch_assoc();
}
}
} // End Database_MySQLi_Result_Select

View File

@ -60,12 +60,6 @@ class Kohana_Database_PDO extends Database {
array(':error' => $e->getMessage()), array(':error' => $e->getMessage()),
$e->getCode()); $e->getCode());
} }
if ( ! empty($this->_config['charset']))
{
// Set the character set
$this->set_charset($this->_config['charset']);
}
} }
/** /**

View File

@ -80,7 +80,7 @@ abstract class Kohana_Database_Query_Builder extends Database_Query {
// Convert "val = NULL" to "val IS NULL" // Convert "val = NULL" to "val IS NULL"
$op = 'IS'; $op = 'IS';
} }
elseif ($op === '!=' OR $op === '<>') elseif ($op === '!=')
{ {
// Convert "val != NULL" to "valu IS NOT NULL" // Convert "val != NULL" to "valu IS NOT NULL"
$op = 'IS NOT'; $op = 'IS NOT';

View File

@ -31,7 +31,7 @@ class Kohana_Database_Query_Builder_Insert extends Database_Query_Builder {
if ($table) if ($table)
{ {
// Set the inital table name // Set the inital table name
$this->table($table); $this->_table = $table;
} }
if ($columns) if ($columns)
@ -47,14 +47,11 @@ class Kohana_Database_Query_Builder_Insert extends Database_Query_Builder {
/** /**
* Sets the table to insert into. * Sets the table to insert into.
* *
* @param string $table table name * @param mixed $table table name or array($table, $alias) or object
* @return $this * @return $this
*/ */
public function table($table) public function table($table)
{ {
if ( ! is_string($table))
throw new Kohana_Exception('INSERT INTO syntax does not allow table aliasing');
$this->_table = $table; $this->_table = $table;
return $this; return $this;
@ -89,11 +86,8 @@ class Kohana_Database_Query_Builder_Insert extends Database_Query_Builder {
// Get all of the passed values // Get all of the passed values
$values = func_get_args(); $values = func_get_args();
foreach ($values as $value) $this->_values = array_merge($this->_values, $values);
{
$this->_values[] = $value;
}
return $this; return $this;
} }

View File

@ -127,8 +127,21 @@ class Kohana_Database_Query_Builder_Join extends Database_Query_Builder {
$op = ' '.strtoupper($op); $op = ' '.strtoupper($op);
} }
// In case C2 is prefixed with table
$c2table = FALSE;
$parts = explode('.', $c2);
// If there are parts, we'll check that first one is the table name.
if (count($parts) == 2)
{
if ($db->list_columns($parts[0],$parts[1]))
{
$c2table = TRUE;
}
}
// Quote each of the columns used for the condition // Quote each of the columns used for the condition
$conditions[] = $db->quote_column($c1).$op.' '.$db->quote_column($c2); $conditions[] = $db->quote_column($c1).$op.' '.($c2table ? $db->quote_column($c2) : $db->quote($c2));
} }
// Concat the conditions "... AND ..." // Concat the conditions "... AND ..."

View File

@ -309,7 +309,7 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where
*/ */
public function offset($number) public function offset($number)
{ {
$this->_offset = ($number === NULL) ? NULL : (int) $number; $this->_offset = $number;
return $this; return $this;
} }
@ -404,14 +404,13 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where
if ( ! empty($this->_union)) if ( ! empty($this->_union))
{ {
$query = '('.$query.')';
foreach ($this->_union as $u) { foreach ($this->_union as $u) {
$query .= ' UNION '; $query .= ' UNION ';
if ($u['all'] === TRUE) if ($u['all'] === TRUE)
{ {
$query .= 'ALL '; $query .= 'ALL ';
} }
$query .= '('.$u['select']->compile($db).')'; $query .= $u['select']->compile($db);
} }
} }

View File

@ -172,7 +172,7 @@ abstract class Kohana_Database_Query_Builder_Where extends Database_Query_Builde
*/ */
public function limit($number) public function limit($number)
{ {
$this->_limit = ($number === NULL) ? NULL : (int) $number; $this->_limit = $number;
return $this; return $this;
} }

View File

@ -204,9 +204,6 @@ class Kohana_Session_Database extends Session {
// Execute the query // Execute the query
$query->execute($this->_db); $query->execute($this->_db);
// Delete the old session id
$this->_update_id = NULL;
// Delete the cookie // Delete the cookie
Cookie::delete($this->_name); Cookie::delete($this->_name);
} }

View File

@ -22,13 +22,8 @@
"require": { "require": {
"composer/installers": "~1.0", "composer/installers": "~1.0",
"kohana/core": ">=3.3", "kohana/core": ">=3.3",
"php": ">=5.3.6" "php": ">=5.3.3"
}, },
"require-dev": {
"kohana/core": "3.3.*@dev",
"kohana/unittest": "3.3.*@dev",
"kohana/koharness": "*@dev"
},
"suggest": { "suggest": {
"ext-mysql": "*", "ext-mysql": "*",
"ext-pdo": "*" "ext-pdo": "*"
@ -37,9 +32,6 @@
"branch-alias": { "branch-alias": {
"dev-3.3/develop": "3.3.x-dev", "dev-3.3/develop": "3.3.x-dev",
"dev-3.4/develop": "3.4.x-dev" "dev-3.4/develop": "3.4.x-dev"
}, }
"installer-paths": {
"vendor/{$vendor}/{$name}": ["type:kohana-module"]
}
} }
} }

View File

@ -53,38 +53,4 @@ return array
'charset' => 'utf8', 'charset' => 'utf8',
'caching' => FALSE, 'caching' => FALSE,
), ),
/**
* MySQLi driver config information
*
* The following options are available for MySQLi:
*
* string hostname server hostname, or socket
* string database database name
* string username database username
* string password database password
* boolean persistent use persistent connections?
* array ssl ssl parameters as "key => value" pairs.
* Available keys: client_key_path, client_cert_path, ca_cert_path, ca_dir_path, cipher
* array variables system variables as "key => value" pairs
*
* Ports and sockets may be appended to the hostname.
*
* MySQLi driver config example:
*
*/
// 'alternate_mysqli' => array
// (
// 'type' => 'MySQLi',
// 'connection' => array(
// 'hostname' => 'localhost',
// 'database' => 'kohana',
// 'username' => FALSE,
// 'password' => FALSE,
// 'persistent' => FALSE,
// 'ssl' => NULL,
// ),
// 'table_prefix' => '',
// 'charset' => 'utf8',
// 'caching' => FALSE,
// ),
); );

View File

@ -10,7 +10,6 @@ The database configuration file contains an array of configuration groups. The s
'table_prefix' => string TABLE_PREFIX, 'table_prefix' => string TABLE_PREFIX,
'charset' => string CHARACTER_SET, 'charset' => string CHARACTER_SET,
), ),
Understanding each of these settings is important. Understanding each of these settings is important.
@ -18,7 +17,7 @@ INSTANCE_NAME
: Connections can be named anything you want, but you should always have at least one connection called "default". : Connections can be named anything you want, but you should always have at least one connection called "default".
DATABASE_TYPE DATABASE_TYPE
: One of the installed database drivers. Kohana comes with "MySQL", "MySQLi", and "PDO" drivers. Drivers must extend the Database class. This parameter is case sensitive. Note the mysql php extension used by the MySQL driver is deprecated as of PHP 5.5 and you should look to use an alternative driver. : One of the installed database drivers. Kohana comes with "mysql" and "pdo" drivers. Drivers must extend the Database class.
CONNECTION_ARRAY CONNECTION_ARRAY
: Specific driver options for connecting to your database. (Driver options are explained [below](#connection-settings).) : Specific driver options for connecting to your database. (Driver options are explained [below](#connection-settings).)
@ -50,7 +49,7 @@ The example file below shows 2 MySQL connections, one local and one remote.
( (
'default' => array 'default' => array
( (
'type' => 'MySQL', 'type' => 'mysql',
'connection' => array( 'connection' => array(
'hostname' => 'localhost', 'hostname' => 'localhost',
'username' => 'dbuser', 'username' => 'dbuser',
@ -62,7 +61,7 @@ The example file below shows 2 MySQL connections, one local and one remote.
'charset' => 'utf8', 'charset' => 'utf8',
), ),
'remote' => array( 'remote' => array(
'type' => 'MySQL', 'type' => 'mysql',
'connection' => array( 'connection' => array(
'hostname' => '55.55.55.55', 'hostname' => '55.55.55.55',
'username' => 'remote_user', 'username' => 'remote_user',
@ -75,8 +74,6 @@ The example file below shows 2 MySQL connections, one local and one remote.
), ),
); );
[!!] Note that the 'type' parameter is case sensitive (eg 'MySQL', 'PDO').
## Connections and Instances ## Connections and Instances
Each configuration group is referred to as a database instance. Each instance can be accessed by calling [Database::instance]. If you don't provide a parameter, the default instance is used. Each configuration group is referred to as a database instance. Each instance can be accessed by calling [Database::instance]. If you don't provide a parameter, the default instance is used.
@ -117,24 +114,6 @@ Type | Option | Description | Default value
`boolean` | persistent | Persistent connections | `FALSE` `boolean` | persistent | Persistent connections | `FALSE`
`string` | database | Database name | `kohana` `string` | database | Database name | `kohana`
### MySQLi
A [MySQL database](http://php.net/manual/en/book.mysqli.php) can accept the following options in the `connection` array:
Type | Option | Description | Default value
----------|------------|----------------------------| -------------------------
`string` | hostname | Hostname of the database | `localhost`
`integer` | port | Port number | `NULL`
`string` | socket | UNIX socket | `NULL`
`string` | username | Database username | `NULL`
`string` | password | Database password | `NULL`
`boolean` | persistent | Persistent connections | `FALSE`
`string` | database | Database name | `kohana`
`array` | ssl | SSL parameters | `NULL`
SSL parameters should be specified as `key` => `value` pairs.
Available keys: client_key_path, client_cert_path, ca_cert_path, ca_dir_path, cipher
### PDO ### PDO
A [PDO database](http://php.net/manual/en/book.pdo.php) can accept these options in the `connection` array: A [PDO database](http://php.net/manual/en/book.pdo.php) can accept these options in the `connection` array:
@ -149,4 +128,4 @@ Type | Option | Description | Default value
The connection character set should be configured using the DSN string or `options` array. The connection character set should be configured using the DSN string or `options` array.
[!!] If you are using PDO and are not sure what to use for the `dsn` option, review [PDO::__construct](http://php.net/pdo.construct). [!!] If you are using PDO and are not sure what to use for the `dsn` option, review [PDO::__construct](http://php.net/pdo.construct).

View File

@ -25,7 +25,7 @@ In this example, we loop through an array of whitelisted input fields and for ea
//copy the query & execute it //copy the query & execute it
$pagination_query = clone $query; $pagination_query = clone $query;
$count = $pagination_query->select(DB::expr('COUNT(*) AS mycount'))->execute()->get('mycount'); $count = $pagination_query->select(DB::expr('COUNT(*)) AS mycount')->execute()->get('mycount');
//pass the total item count to Pagination //pass the total item count to Pagination
$config = Kohana::$config->load('pagination'); $config = Kohana::$config->load('pagination');
@ -49,4 +49,4 @@ In this example, we loop through an array of whitelisted input fields and for ea
TODO: example goes here TODO: example goes here
[!!] We could use more examples on this page. [!!] We could use more examples on this page.

View File

@ -76,7 +76,7 @@ This query would generate the following SQL:
Often you will want the results in a particular order and rather than sorting the results, it's better to have the results returned to you in the correct order. You can do this by using the order_by() method. It takes the column name and an optional direction string as the parameters. Multiple `order_by()` methods can be used to add additional sorting capability. Often you will want the results in a particular order and rather than sorting the results, it's better to have the results returned to you in the correct order. You can do this by using the order_by() method. It takes the column name and an optional direction string as the parameters. Multiple `order_by()` methods can be used to add additional sorting capability.
$query = DB::select()->from('posts')->order_by('published', 'DESC'); $query = DB::select()->from(`posts`)->order_by(`published`, `DESC`);
This query would generate the following SQL: This query would generate the following SQL:
@ -248,4 +248,4 @@ Once you are done building, you can execute the query using `execute()` and use
To use a different database [config group](config) pass either the name or the config object to `execute()`. To use a different database [config group](config) pass either the name or the config object to `execute()`.
$result = $query->execute('config_name') $result = $query->execute('config_name')

View File

@ -1,8 +0,0 @@
<?php
// Configuration for koharness - builds a standalone skeleton Kohana app for running unit tests
return array(
'modules' => array(
'database' => __DIR__,
'unittest' => __DIR__ . '/vendor/kohana/unittest'
),
);

View File

@ -1,37 +0,0 @@
sudo: false
language: php
# Only build the main develop/master branches - feature branches will be covered by PRs
branches:
only:
- /^[0-9\.]+\/(develop|master)$/
cache:
directories:
- vendor
- $HOME/.composer/cache
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
matrix:
include:
- php: 5.3
env: 'COMPOSER_PHPUNIT="lowest"'
before_script:
- composer install --prefer-dist
- if [ "$COMPOSER_PHPUNIT" = "lowest" ]; then composer update --prefer-lowest --with-dependencies phpunit/phpunit; fi;
- vendor/bin/koharness
script:
- cd /tmp/koharness && ./vendor/bin/phpunit --bootstrap=modules/unittest/bootstrap.php modules/unittest/tests.php
notifications:
email: false

View File

@ -1,6 +0,0 @@
# Kohana - image processing module
| ver | Stable | Develop |
|-------|--------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
| 3.3.x | [![Build Status - 3.3/master](https://travis-ci.org/kohana/image.svg?branch=3.3%2Fmaster)](https://travis-ci.org/kohana/image) | [![Build Status - 3.3/develop](https://travis-ci.org/kohana/image.svg?branch=3.3%2Fdevelop)](https://travis-ci.org/kohana/image) |
| 3.4.x | [![Build Status - 3.4/master](https://travis-ci.org/kohana/image.svg?branch=3.4%2Fmaster)](https://travis-ci.org/kohana/image) | [![Build Status - 3.4/develop](https://travis-ci.org/kohana/image.svg?branch=3.4%2Fdevelop)](https://travis-ci.org/kohana/image) |

View File

@ -23,7 +23,6 @@ abstract class Kohana_Image {
const VERTICAL = 0x12; const VERTICAL = 0x12;
/** /**
* @deprecated - provide an image.default_driver value in your configuration instead
* @var string default driver: GD, ImageMagick, etc * @var string default driver: GD, ImageMagick, etc
*/ */
public static $default_driver = 'GD'; public static $default_driver = 'GD';
@ -45,9 +44,8 @@ abstract class Kohana_Image {
{ {
if ($driver === NULL) if ($driver === NULL)
{ {
// Use the driver from configuration file or default one // Use the default driver
$configured_driver = Kohana::$config->load('image.default_driver'); $driver = Image::$default_driver;
$driver = ($configured_driver) ? $configured_driver : Image::$default_driver;
} }
// Set the class name // Set the class name

View File

@ -611,7 +611,6 @@ class Kohana_Image_GD extends Image {
switch (strtolower($extension)) switch (strtolower($extension))
{ {
case 'jpg': case 'jpg':
case 'jpe':
case 'jpeg': case 'jpeg':
// Save a JPG file // Save a JPG file
$save = 'imagejpeg'; $save = 'imagejpeg';

View File

@ -314,7 +314,6 @@ class Kohana_Image_Imagick extends Image {
switch ($format) switch ($format)
{ {
case 'jpg': case 'jpg':
case 'jpe':
case 'jpeg': case 'jpeg':
$type = IMAGETYPE_JPEG; $type = IMAGETYPE_JPEG;
break; break;

View File

@ -24,11 +24,6 @@
"kohana/core": ">=3.3", "kohana/core": ">=3.3",
"php": ">=5.3.3" "php": ">=5.3.3"
}, },
"require-dev": {
"kohana/core": "3.3.*@dev",
"kohana/unittest": "3.3.*@dev",
"kohana/koharness": "*@dev"
},
"suggest": { "suggest": {
"ext-gd": "*" "ext-gd": "*"
}, },
@ -36,9 +31,6 @@
"branch-alias": { "branch-alias": {
"dev-3.3/develop": "3.3.x-dev", "dev-3.3/develop": "3.3.x-dev",
"dev-3.4/develop": "3.4.x-dev" "dev-3.4/develop": "3.4.x-dev"
}, }
"installer-paths": {
"vendor/{$vendor}/{$name}": ["type:kohana-module"]
}
} }
} }

View File

@ -1,8 +0,0 @@
<?php defined('SYSPATH') OR die('No direct script access.');
return array(
// Provide the default driver to use - eg a value of Imagick will use Image_Imagick as the driver class
// If you set a value for this config key, then the Image::$default_driver static variable that was used
// to configure earlier versions will be ignored.
'default_driver' => NULL,
);

View File

@ -4,24 +4,7 @@ Kohana 3.x provides a simple yet powerful image manipulation module. The [Image]
## Drivers ## Drivers
[Image] module ships with [Image_GD] driver which requires `GD` extension enabled in your PHP installation, and [Image] module ships with [Image_GD] driver which requires `GD` extension enabled in your PHP installation. This is the default driver. Additional drivers can be created by extending the [Image] class.
[Image_Imagick] driver which requires the `imagick` PHP extension. Additional drivers can be created by extending
the [Image] class.
The [Image_GD] driver is the default. You can change this by providing an `image.default_driver` configuration option
- for example:
~~~
// application/config/image.php
<?php
return array(
'default_driver' => 'Imagick'
);
~~~
[!!] Older versions of Kohana allowed you to configure the driver with the `Image::$default_driver` static variable in
the bootstrap, an extension class, or elsewhere. That variable is now deprecated and will be ignored if you set a
config value.
## Getting Started ## Getting Started
@ -35,4 +18,4 @@ Kohana::modules(array(
)); ));
~~~ ~~~
Next: [Using the image module](using). Next: [Using the image module](using).

View File

@ -1,8 +0,0 @@
<?php
// Configuration for koharness - builds a standalone skeleton Kohana app for running unit tests
return array(
'modules' => array(
'image' => __DIR__,
'unittest' => __DIR__ . '/vendor/kohana/unittest'
),
);

View File

@ -0,0 +1,39 @@
Email Module For Kohana 3.0
=================================
This is a direct port of the email helper from Kohana 2.3.3 source code.
It has been updated to work with SwiftMailer 4 and includes the libs dir from the 4.0.4 distribution.
Usage should be exactly as with old helper.
Methods defined:
### Email::connect($config = NULL)
Creates SwiftMailer object. $config is an array of configuration values and defaults to using the config file 'email'.
Note: PopBeforeSmtp is not supported in this release as I didn't know what was required to set it up.
It IS supported in Swiftmailer through the Swift_Plugins_PopBeforeSmtpPlugin plugin class. This can be used manually if required.
If anyone can modify and test the connect() methd with this functionality I'll add it but I can't find documentation about how it used to work (i.e. is expected to work) so I have left it out for now.
### Email::send($to, $from, $subject, $message, $html = false)
$to can be any of the following:
* a single string email address e.g. "test@example.com"
* an array specifying an email address and a name e.g. array('test@example.com', 'John Doe')
* an array of recipients in either above format, keyed by type e.g. array('to' => 'test@example.com', 'cc' => array('test2@example.com', 'Jane Doe'), 'bcc' => 'another@example.com')
$from can be either a string email or array of email and name as above
More complex email (multipart, attachments, batch mailing etc.) must be done using the native Swift_Mailer classes. The Swift Mailer autoloader is included by connect() so you can use and class in the Swift library without worrying about including files.
The Swift_Mailer object setup by connect is returned by it so if you need to access it manually use:
$mailer = Email::connect();
// Create complex Swift_Message object stored in $message
$mailer->send($message);

View File

@ -0,0 +1,3 @@
<?php defined('SYSPATH') OR die('No direct access allowed.');
abstract class Email extends Kohana_Email { }

View File

@ -0,0 +1,145 @@
<?php defined('SYSPATH') OR die('No direct access allowed.');
/**
* Email module
*
* Ported from Kohana 2.2.3 Core to Kohana 3.0 module
*
* Updated to use Swiftmailer 4.0.4
*
* @package Core
* @author Kohana Team
* @copyright (c) 2007-2008 Kohana Team
* @license http://kohanaphp.com/license.html
*/
abstract class Kohana_Email {
// SwiftMailer instance
protected static $mail;
/**
* Creates a SwiftMailer instance.
*
* @param string DSN connection string
* @return object Swift object
*/
public static function connect($config = NULL)
{
if ( ! class_exists('Swift_Mailer', FALSE))
{
// Load SwiftMailer
require Kohana::find_file('vendor', 'swift/swift_required');
}
// Load default configuration
($config === NULL) and $config = Kohana::$config->load('email');
switch ($config['driver'])
{
case 'smtp':
// Set port
$port = empty($config['options']['port']) ? 25 : (int) $config['options']['port'];
// Create SMTP Transport
$transport = Swift_SmtpTransport::newInstance($config['options']['hostname'], $port);
if ( ! empty($config['options']['encryption']))
{
// Set encryption
$transport->setEncryption($config['options']['encryption']);
}
// Do authentication, if part of the DSN
empty($config['options']['username']) or $transport->setUsername($config['options']['username']);
empty($config['options']['password']) or $transport->setPassword($config['options']['password']);
// Set the timeout to 5 seconds
$transport->setTimeout(empty($config['options']['timeout']) ? 5 : (int) $config['options']['timeout']);
break;
case 'sendmail':
// Create a sendmail connection
$transport = Swift_SendmailTransport::newInstance(empty($config['options']) ? "/usr/sbin/sendmail -bs" : $config['options']);
break;
default:
// Use the native connection
$transport = Swift_MailTransport::newInstance($config['options']);
break;
}
// Create the SwiftMailer instance
return Email::$mail = Swift_Mailer::newInstance($transport);
}
/**
* Send an email message.
*
* @param string|array recipient email (and name), or an array of To, Cc, Bcc names
* @param string|array sender email (and name)
* @param string message subject
* @param string message body
* @param boolean send email as HTML
* @return integer number of emails sent
*/
public static function send($to, $from, $subject, $message, $html = FALSE)
{
// Connect to SwiftMailer
(Email::$mail === NULL) and email::connect();
// Determine the message type
$html = ($html === TRUE) ? 'text/html' : 'text/plain';
// Create the message
$message = Swift_Message::newInstance($subject, $message, $html, 'utf-8');
if (is_string($to))
{
// Single recipient
$message->setTo($to);
}
elseif (is_array($to))
{
if (isset($to[0]) AND isset($to[1]))
{
// Create To: address set
$to = array('to' => $to);
}
foreach ($to as $method => $set)
{
if ( ! in_array($method, array('to', 'cc', 'bcc')))
{
// Use To: by default
$method = 'to';
}
// Create method name
$method = 'add'.ucfirst($method);
if (is_array($set))
{
// Add a recipient with name
$message->$method($set[0], $set[1]);
}
else
{
// Add a recipient without name
$message->$method($set);
}
}
}
if (is_string($from))
{
// From without a name
$message->setFrom($from);
}
elseif (is_array($from))
{
// From with a name
$message->setFrom($from[0], $from[1]);
}
return Email::$mail->send($message);
}
} // End email

View File

@ -0,0 +1,29 @@
<?php defined('SYSPATH') OR die('No direct access allowed.');
return array(
/**
* SwiftMailer driver, used with the email module.
*
* Valid drivers are: native, sendmail, smtp
*/
'driver' => 'native',
/**
* To use secure connections with SMTP, set "port" to 465 instead of 25.
* To enable TLS, set "encryption" to "tls".
*
* Note for SMTP, 'auth' key no longer exists as it did in 2.3.x helper
* Simply specifying a username and password is enough for all normal auth methods
* as they are autodeteccted in Swiftmailer 4
*
* PopB4Smtp is not supported in this module as I had no way to test it but
* SwiftMailer 4 does have a PopBeforeSMTP plugin so it shouldn't be hard to implement
*
* Encryption can be one of 'ssl' or 'tls' (both require non-default PHP extensions
*
* Driver options:
* @param null native: no options
* @param string sendmail: executable path, with -bs or equivalent attached
* @param array smtp: hostname, (username), (password), (port), (encryption)
*/
'options' => NULL
);

61
modules/khemail/vendor/swift/CHANGES vendored Normal file
View File

@ -0,0 +1,61 @@
Changelog for Swift Mailer, since Version 4.x
---------------------------------------------
09 March 2009: 4.0.0
--------------------
* Complete rewrite of Version 3.x with lots of breaking changes at the interface
level, but for the best in the long run.
* Changed Connections to Transports
* Made sending more robust (less error prone)
* Simplified Swift_Message interface (removed need for separate RecipientList)
* Improved Plugin API (better event management)
* Changed all MIME generated content to be full RFC 2822 (and friends) compliant
11 March 2009: 4.0.1
--------------------
* Fixed regression with cache clearing logic in setBody(), setEncoder() and
setCharset()
13 March 2009: 4.0.2
--------------------
* Added addTo(), addCc() etc methods.
* Allowed setTo(), setCc() etc to accept a $name parameters.
* Patched a bug in MailTransport where failed recipients were not being merged.
* Added Swift::VERSION constant
* Allowed custom autoloaders to be used
20 March 2009: 4.0.3
--------------------
* Fixed Bug where base64 encoded content could exceed 76 chars per line
* Allowed Decorator plugin to accept a custom Replacements object
12 August 2009: 4.0.4
--------------------
* Bugfixes for operating under safe mode and using the MailTransport
* Compatibility for PHP 5.3
* Optimizations for addTo(), addCc() etc operations
* Bugfix for double-escaping issue in batch sending
27 September 2009: 4.0.5
------------------------
* Fixed a warning (#78)
* Clarified license and updated the file headers accordingly
* Added __toString() methods where toString() methods already exists
* Removed constants (SWIFT_LIB_DIRECTORY, SWIFT_MAP_DIRECTORY, SWIFT_CLASS_DIRECTORY)
* Simplified autoloading
* Added a setAuthMode() method to AuthHandler (#54)
20 January 2010: 4.0.6
----------------------
* added a PEAR package and a script to generate PEAR packages
* fixed Swift_Transport_TransportException for SMTP connection not thrown (#109)
* fixed Message-IDs are not updated properly (#118)
-- End of Changes --

165
modules/khemail/vendor/swift/LICENSE vendored Normal file
View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

30
modules/khemail/vendor/swift/README vendored Normal file
View File

@ -0,0 +1,30 @@
Swift Mailer, by Chris Corbyn
-----------------------------
Swift Mailer is a component based mailing solution for PHP 5.
It is released under the LGPL license.
Homepage: http://swiftmailer.org
Documentation: http://swiftmailer.org/docs
Mailing List: http://groups.google.com/group/swiftmailer
Bugs: http://swiftmailer.lighthouseapp.com/
Repository: http://github.com/swiftmailer/swiftmailer
Swift Mailer is highly object-oriented by design and lends itself
to use in complex web application with a great deal of flexibility.
For full details on usage, see the documentation.
IMPORTANT: Users upgrading from version 3.x or earlier absolutely
MUST read the documentation. In short, the API is considerably
different so your old code won't "just work".
If you'd like to make a donation, we are working on a system where
donations are taken on a per-feature-request basis via the website
with target amounts for each feature. In the meantime however you
may donate directly to the author via PayPal:
PayPal: chris@w3style.co.uk
Donations are certainly voluntary, but seriously, you donors are
complete legends and drive this project! :)

1
modules/khemail/vendor/swift/VERSION vendored Normal file
View File

@ -0,0 +1 @@
Swift-4.0.6

View File

@ -0,0 +1,57 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* General utility class in Swift Mailer, not to be instantiated.
*
* @package Swift
*
* @author Chris Corbyn
*/
abstract class Swift
{
/** Swift Mailer Version number generated during dist release process */
const VERSION = '4.0.6';
/**
* Internal autoloader for spl_autoload_register().
*
* @param string $class
*/
public static function autoload($class)
{
//Don't interfere with other autoloaders
if (0 !== strpos($class, 'Swift'))
{
return false;
}
$path = dirname(__FILE__).'/'.str_replace('_', '/', $class).'.php';
if (!file_exists($path))
{
return false;
}
require_once $path;
}
/**
* Configure autoloading using Swift Mailer.
*
* This is designed to play nicely with other autoloaders.
*/
public static function registerAutoload()
{
spl_autoload_register(array('Swift', 'autoload'));
}
}

View File

@ -0,0 +1,75 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/Mime/Attachment.php';
//@require 'Swift/ByteStream/FileByteStream.php';
//@require 'Swift/DependencyContainer.php';
/**
* Attachment class for attaching files to a {@link Swift_Mime_Message}.
* @package Swift
* @subpackage Mime
* @author Chris Corbyn
*/
class Swift_Attachment extends Swift_Mime_Attachment
{
/**
* Create a new Attachment.
* Details may be optionally provided to the constructor.
* @param string|Swift_OutputByteStream $data
* @param string $filename
* @param string $contentType
*/
public function __construct($data = null, $filename = null,
$contentType = null)
{
call_user_func_array(
array($this, 'Swift_Mime_Attachment::__construct'),
Swift_DependencyContainer::getInstance()
->createDependenciesFor('mime.attachment')
);
$this->setBody($data);
$this->setFilename($filename);
if ($contentType)
{
$this->setContentType($contentType);
}
}
/**
* Create a new Attachment.
* @param string|Swift_OutputByteStream $data
* @param string $filename
* @param string $contentType
* @return Swift_Mime_Attachment
*/
public static function newInstance($data = null, $filename = null,
$contentType = null)
{
return new self($data, $filename, $contentType);
}
/**
* Create a new Attachment from a filesystem path.
* @param string $path
* @param string $contentType optional
* @return Swift_Mime_Attachment
*/
public static function fromPath($path, $contentType = null)
{
return self::newInstance()->setFile(
new Swift_ByteStream_FileByteStream($path),
$contentType
);
}
}

View File

@ -0,0 +1,178 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/InputByteStream.php';
//@require 'Swift/Filterable.php';
//@require 'Swift/StreamFilter.php';
/**
* Provides the base functionality for an InputStream supporting filters.
* @package Swift
* @subpackage ByteStream
* @author Chris Corbyn
*/
abstract class Swift_ByteStream_AbstractFilterableInputStream
implements Swift_InputByteStream, Swift_Filterable
{
/** Write sequence */
private $_sequence = 0;
/** StreamFilters */
private $_filters = array();
/** A buffer for writing */
private $_writeBuffer = '';
/** Bound streams */
private $_mirrors = array();
/**
* Commit the given bytes to the storage medium immediately.
* @param string $bytes
* @access protected
*/
abstract protected function _commit($bytes);
/**
* Flush any buffers/content with immediate effect.
* @access protected
*/
abstract protected function _flush();
/**
* Add a StreamFilter to this InputByteStream.
* @param Swift_StreamFilter $filter
* @param string $key
*/
public function addFilter(Swift_StreamFilter $filter, $key)
{
$this->_filters[$key] = $filter;
}
/**
* Remove an already present StreamFilter based on its $key.
* @param string $key
*/
public function removeFilter($key)
{
unset($this->_filters[$key]);
}
/**
* Writes $bytes to the end of the stream.
* @param string $bytes
* @throws Swift_IoException
*/
public function write($bytes)
{
$this->_writeBuffer .= $bytes;
foreach ($this->_filters as $filter)
{
if ($filter->shouldBuffer($this->_writeBuffer))
{
return;
}
}
$this->_doWrite($this->_writeBuffer);
return ++$this->_sequence;
}
/**
* For any bytes that are currently buffered inside the stream, force them
* off the buffer.
*
* @throws Swift_IoException
*/
public function commit()
{
$this->_doWrite($this->_writeBuffer);
}
/**
* Attach $is to this stream.
* The stream acts as an observer, receiving all data that is written.
* All {@link write()} and {@link flushBuffers()} operations will be mirrored.
*
* @param Swift_InputByteStream $is
*/
public function bind(Swift_InputByteStream $is)
{
$this->_mirrors[] = $is;
}
/**
* Remove an already bound stream.
* If $is is not bound, no errors will be raised.
* If the stream currently has any buffered data it will be written to $is
* before unbinding occurs.
*
* @param Swift_InputByteStream $is
*/
public function unbind(Swift_InputByteStream $is)
{
foreach ($this->_mirrors as $k => $stream)
{
if ($is === $stream)
{
if ($this->_writeBuffer !== '')
{
$stream->write($this->_filter($this->_writeBuffer));
}
unset($this->_mirrors[$k]);
}
}
}
/**
* Flush the contents of the stream (empty it) and set the internal pointer
* to the beginning.
* @throws Swift_IoException
*/
public function flushBuffers()
{
if ($this->_writeBuffer !== '')
{
$this->_doWrite($this->_writeBuffer);
}
$this->_flush();
foreach ($this->_mirrors as $stream)
{
$stream->flushBuffers();
}
}
// -- Private methods
/** Run $bytes through all filters */
private function _filter($bytes)
{
foreach ($this->_filters as $filter)
{
$bytes = $filter->filter($bytes);
}
return $bytes;
}
/** Just write the bytes to the stream */
private function _doWrite($bytes)
{
$this->_commit($this->_filter($bytes));
foreach ($this->_mirrors as $stream)
{
$stream->write($bytes);
}
$this->_writeBuffer = '';
}
}

View File

@ -0,0 +1,190 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/InputByteStream.php';
//@require 'Swift/OutputByteStream.php';
/**
* Allows reading and writing of bytes to and from an array.
* @package Swift
* @subpackage ByteStream
* @author Chris Corbyn
*/
class Swift_ByteStream_ArrayByteStream
implements Swift_InputByteStream, Swift_OutputByteStream
{
/**
* The internal stack of bytes.
* @var string[]
* @access private
*/
private $_array = array();
/**
* The size of the stack
* @var int
* @access private
*/
private $_arraySize = 0;
/**
* The internal pointer offset.
* @var int
* @access private
*/
private $_offset = 0;
/** Bound streams */
private $_mirrors = array();
/**
* Create a new ArrayByteStream.
* If $stack is given the stream will be populated with the bytes it contains.
* @param mixed $stack of bytes in string or array form, optional
*/
public function __construct($stack = null)
{
if (is_array($stack))
{
$this->_array = $stack;
$this->_arraySize = count($stack);
}
elseif (is_string($stack))
{
$this->write($stack);
}
else
{
$this->_array = array();
}
}
/**
* Reads $length bytes from the stream into a string and moves the pointer
* through the stream by $length. If less bytes exist than are requested the
* remaining bytes are given instead. If no bytes are remaining at all, boolean
* false is returned.
* @param int $length
* @return string
*/
public function read($length)
{
if ($this->_offset == $this->_arraySize)
{
return false;
}
// Don't use array slice
$end = $length + $this->_offset;
$end = $this->_arraySize<$end
?$this->_arraySize
:$end;
$ret = '';
for (; $this->_offset < $end; ++$this->_offset)
{
$ret .= $this->_array[$this->_offset];
}
return $ret;
}
/**
* Writes $bytes to the end of the stream.
* @param string $bytes
*/
public function write($bytes)
{
$to_add = str_split($bytes);
foreach ($to_add as $value)
{
$this->_array[] = $value;
}
$this->_arraySize = count($this->_array);
foreach ($this->_mirrors as $stream)
{
$stream->write($bytes);
}
}
/**
* Not used.
*/
public function commit()
{
}
/**
* Attach $is to this stream.
* The stream acts as an observer, receiving all data that is written.
* All {@link write()} and {@link flushBuffers()} operations will be mirrored.
*
* @param Swift_InputByteStream $is
*/
public function bind(Swift_InputByteStream $is)
{
$this->_mirrors[] = $is;
}
/**
* Remove an already bound stream.
* If $is is not bound, no errors will be raised.
* If the stream currently has any buffered data it will be written to $is
* before unbinding occurs.
*
* @param Swift_InputByteStream $is
*/
public function unbind(Swift_InputByteStream $is)
{
foreach ($this->_mirrors as $k => $stream)
{
if ($is === $stream)
{
unset($this->_mirrors[$k]);
}
}
}
/**
* Move the internal read pointer to $byteOffset in the stream.
* @param int $byteOffset
* @return boolean
*/
public function setReadPointer($byteOffset)
{
if ($byteOffset > $this->_arraySize)
{
$byteOffset = $this->_arraySize;
}
elseif ($byteOffset < 0)
{
$byteOffset = 0;
}
$this->_offset = $byteOffset;
}
/**
* Flush the contents of the stream (empty it) and set the internal pointer
* to the beginning.
*/
public function flushBuffers()
{
$this->_offset = 0;
$this->_array = array();
$this->_arraySize = 0;
foreach ($this->_mirrors as $stream)
{
$stream->flushBuffers();
}
}
}

View File

@ -0,0 +1,177 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/ByteStream/AbstractFilterableInputStream.php';
//@require 'Swift/InputByteStream.php';
//@require 'Swift/FileStream.php';
//@require 'Swift/IoException.php';
/**
* Allows reading and writing of bytes to and from a file.
* @package Swift
* @subpackage ByteStream
* @author Chris Corbyn
*/
class Swift_ByteStream_FileByteStream
extends Swift_ByteStream_AbstractFilterableInputStream
implements Swift_FileStream
{
/** The internal pointer offset */
private $_offset = 0;
/** The path to the file */
private $_path;
/** The mode this file is opened in for writing */
private $_mode;
/** A lazy-loaded resource handle for reading the file */
private $_reader;
/** A lazy-loaded resource handle for writing the file */
private $_writer;
/** If magic_quotes_runtime is on, this will be true */
private $_quotes = false;
/**
* Create a new FileByteStream for $path.
* @param string $path
* @param string $writable if true
*/
public function __construct($path, $writable = false)
{
$this->_path = $path;
$this->_mode = $writable ? 'w+b' : 'rb';
$this->_quotes = get_magic_quotes_runtime();
}
/**
* Get the complete path to the file.
* @return string
*/
public function getPath()
{
return $this->_path;
}
/**
* Reads $length bytes from the stream into a string and moves the pointer
* through the stream by $length. If less bytes exist than are requested the
* remaining bytes are given instead. If no bytes are remaining at all, boolean
* false is returned.
* @param int $length
* @return string
* @throws Swift_IoException
*/
public function read($length)
{
$fp = $this->_getReadHandle();
if (!feof($fp))
{
if ($this->_quotes)
{
set_magic_quotes_runtime(0);
}
$bytes = fread($fp, $length);
if ($this->_quotes)
{
set_magic_quotes_runtime(1);
}
$this->_offset = ftell($fp);
return $bytes;
}
else
{
return false;
}
}
/**
* Move the internal read pointer to $byteOffset in the stream.
* @param int $byteOffset
* @return boolean
*/
public function setReadPointer($byteOffset)
{
if (isset($this->_reader))
{
fseek($this->_reader, $byteOffset, SEEK_SET);
}
$this->_offset = $byteOffset;
}
// -- Private methods
/** Just write the bytes to the file */
protected function _commit($bytes)
{
fwrite($this->_getWriteHandle(), $bytes);
$this->_resetReadHandle();
}
/** Not used */
protected function _flush()
{
}
/** Get the resource for reading */
private function _getReadHandle()
{
if (!isset($this->_reader))
{
if (!$this->_reader = fopen($this->_path, 'rb'))
{
throw new Swift_IoException(
'Unable to open file for reading [' . $this->_path . ']'
);
}
fseek($this->_reader, $this->_offset, SEEK_SET);
}
return $this->_reader;
}
/** Get the resource for writing */
private function _getWriteHandle()
{
if (!isset($this->_writer))
{
if (!$this->_writer = fopen($this->_path, $this->_mode))
{
throw new Swift_IoException(
'Unable to open file for writing [' . $this->_path . ']'
);
}
}
return $this->_writer;
}
/** Force a reload of the resource for writing */
private function _resetWriteHandle()
{
if (isset($this->_writer))
{
fclose($this->_writer);
$this->_writer = null;
}
}
/** Force a reload of the resource for reading */
private function _resetReadHandle()
{
if (isset($this->_reader))
{
fclose($this->_reader);
$this->_reader = null;
}
}
}

View File

@ -0,0 +1,60 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Analyzes characters for a specific character set.
* @package Swift
* @subpackage Encoder
* @author Chris Corbyn
* @author Xavier De Cock <xdecock@gmail.com>
*/
interface Swift_CharacterReader
{
const MAP_TYPE_INVALID = 0x01;
const MAP_TYPE_FIXED_LEN = 0x02;
const MAP_TYPE_POSITIONS = 0x03;
/**
* Returns the complete charactermap
*
* @param string $string
* @param int $startOffset
* @param array $currentMap
* @param mixed $ignoredChars
* @return int
*/
public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars);
/**
* Returns mapType
* @int mapType
*/
public function getMapType();
/**
* Returns an integer which specifies how many more bytes to read.
* A positive integer indicates the number of more bytes to fetch before invoking
* this method again.
* A value of zero means this is already a valid character.
* A value of -1 means this cannot possibly be a valid character.
* @param int[] $bytes
* @return int
*/
public function validateByteSequence($bytes, $size);
/**
* Returns the number of bytes which should be read to start each character.
* For fixed width character sets this should be the number of
* octets-per-character. For multibyte character sets this will probably be 1.
* @return int
*/
public function getInitialByteSize();
}

View File

@ -0,0 +1,96 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/CharacterReader.php';
/**
* Provides fixed-width byte sizes for reading fixed-width character sets.
* @package Swift
* @subpackage Encoder
* @author Chris Corbyn
* @author Xavier De Cock <xdecock@gmail.com>
*/
class Swift_CharacterReader_GenericFixedWidthReader
implements Swift_CharacterReader
{
/**
* The number of bytes in a single character.
* @var int
* @access private
*/
private $_width;
/**
* Creates a new GenericFixedWidthReader using $width bytes per character.
* @param int $width
*/
public function __construct($width)
{
$this->_width = $width;
}
/**
* Returns the complete charactermap
*
* @param string $string
* @param int $startOffset
* @param array $currentMap
* @param mixed $ignoredChars
* @return $int
*/
public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
{
$strlen = strlen($string);
// % and / are CPU intensive, so, maybe find a better way
$ignored = $strlen%$this->_width;
$ignoredChars = substr($string, - $ignored);
$currentMap = $this->_width;
return ($strlen - $ignored)/$this->_width;
}
/**
* Returns mapType
* @int mapType
*/
public function getMapType()
{
return self::MAP_TYPE_FIXED_LEN;
}
/**
* Returns an integer which specifies how many more bytes to read.
* A positive integer indicates the number of more bytes to fetch before invoking
* this method again.
* A value of zero means this is already a valid character.
* A value of -1 means this cannot possibly be a valid character.
* @param string $bytes
* @return int
*/
public function validateByteSequence($bytes, $size)
{
$needed = $this->_width - $size;
return ($needed > -1)
? $needed
: -1
;
}
/**
* Returns the number of bytes which should be read to start each character.
* @return int
*/
public function getInitialByteSize()
{
return $this->_width;
}
}

View File

@ -0,0 +1,83 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/CharacterReader.php';
/**
* Analyzes US-ASCII characters.
* @package Swift
* @subpackage Encoder
* @author Chris Corbyn
*/
class Swift_CharacterReader_UsAsciiReader
implements Swift_CharacterReader
{
/**
* Returns the complete charactermap
*
* @param string $string
* @param int $startOffset
* @param string $ignoredChars
*/
public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
{
$strlen=strlen($string);
$ignoredChars='';
for( $i = 0; $i < $strlen; ++$i)
{
if ($string[$i]>"\x07F")
{ // Invalid char
$currentMap[$i+$startOffset]=$string[$i];
}
}
return $strlen;
}
/**
* Returns mapType
* @int mapType
*/
public function getMapType()
{
return self::MAP_TYPE_INVALID;
}
/**
* Returns an integer which specifies how many more bytes to read.
* A positive integer indicates the number of more bytes to fetch before invoking
* this method again.
* A value of zero means this is already a valid character.
* A value of -1 means this cannot possibly be a valid character.
* @param string $bytes
* @return int
*/
public function validateByteSequence($bytes, $size)
{
$byte = reset($bytes);
if (1 == count($bytes) && $byte >= 0x00 && $byte <= 0x7F)
{
return 0;
}
else
{
return -1;
}
}
/**
* Returns the number of bytes which should be read to start each character.
* @return int
*/
public function getInitialByteSize()
{
return 1;
}
}

View File

@ -0,0 +1,183 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/CharacterReader.php';
/**
* Analyzes UTF-8 characters.
* @package Swift
* @subpackage Encoder
* @author Chris Corbyn
* @author Xavier De Cock <xdecock@gmail.com>
*/
class Swift_CharacterReader_Utf8Reader
implements Swift_CharacterReader
{
/** Pre-computed for optimization */
private static $length_map=array(
//N=0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x0N
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x1N
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x2N
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x3N
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x4N
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x5N
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x6N
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //0x7N
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x8N
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x9N
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0xAN
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0xBN
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, //0xCN
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, //0xDN
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, //0xEN
4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0 //0xFN
);
private static $s_length_map=array(
"\x00"=>1, "\x01"=>1, "\x02"=>1, "\x03"=>1, "\x04"=>1, "\x05"=>1, "\x06"=>1, "\x07"=>1,
"\x08"=>1, "\x09"=>1, "\x0a"=>1, "\x0b"=>1, "\x0c"=>1, "\x0d"=>1, "\x0e"=>1, "\x0f"=>1,
"\x10"=>1, "\x11"=>1, "\x12"=>1, "\x13"=>1, "\x14"=>1, "\x15"=>1, "\x16"=>1, "\x17"=>1,
"\x18"=>1, "\x19"=>1, "\x1a"=>1, "\x1b"=>1, "\x1c"=>1, "\x1d"=>1, "\x1e"=>1, "\x1f"=>1,
"\x20"=>1, "\x21"=>1, "\x22"=>1, "\x23"=>1, "\x24"=>1, "\x25"=>1, "\x26"=>1, "\x27"=>1,
"\x28"=>1, "\x29"=>1, "\x2a"=>1, "\x2b"=>1, "\x2c"=>1, "\x2d"=>1, "\x2e"=>1, "\x2f"=>1,
"\x30"=>1, "\x31"=>1, "\x32"=>1, "\x33"=>1, "\x34"=>1, "\x35"=>1, "\x36"=>1, "\x37"=>1,
"\x38"=>1, "\x39"=>1, "\x3a"=>1, "\x3b"=>1, "\x3c"=>1, "\x3d"=>1, "\x3e"=>1, "\x3f"=>1,
"\x40"=>1, "\x41"=>1, "\x42"=>1, "\x43"=>1, "\x44"=>1, "\x45"=>1, "\x46"=>1, "\x47"=>1,
"\x48"=>1, "\x49"=>1, "\x4a"=>1, "\x4b"=>1, "\x4c"=>1, "\x4d"=>1, "\x4e"=>1, "\x4f"=>1,
"\x50"=>1, "\x51"=>1, "\x52"=>1, "\x53"=>1, "\x54"=>1, "\x55"=>1, "\x56"=>1, "\x57"=>1,
"\x58"=>1, "\x59"=>1, "\x5a"=>1, "\x5b"=>1, "\x5c"=>1, "\x5d"=>1, "\x5e"=>1, "\x5f"=>1,
"\x60"=>1, "\x61"=>1, "\x62"=>1, "\x63"=>1, "\x64"=>1, "\x65"=>1, "\x66"=>1, "\x67"=>1,
"\x68"=>1, "\x69"=>1, "\x6a"=>1, "\x6b"=>1, "\x6c"=>1, "\x6d"=>1, "\x6e"=>1, "\x6f"=>1,
"\x70"=>1, "\x71"=>1, "\x72"=>1, "\x73"=>1, "\x74"=>1, "\x75"=>1, "\x76"=>1, "\x77"=>1,
"\x78"=>1, "\x79"=>1, "\x7a"=>1, "\x7b"=>1, "\x7c"=>1, "\x7d"=>1, "\x7e"=>1, "\x7f"=>1,
"\x80"=>0, "\x81"=>0, "\x82"=>0, "\x83"=>0, "\x84"=>0, "\x85"=>0, "\x86"=>0, "\x87"=>0,
"\x88"=>0, "\x89"=>0, "\x8a"=>0, "\x8b"=>0, "\x8c"=>0, "\x8d"=>0, "\x8e"=>0, "\x8f"=>0,
"\x90"=>0, "\x91"=>0, "\x92"=>0, "\x93"=>0, "\x94"=>0, "\x95"=>0, "\x96"=>0, "\x97"=>0,
"\x98"=>0, "\x99"=>0, "\x9a"=>0, "\x9b"=>0, "\x9c"=>0, "\x9d"=>0, "\x9e"=>0, "\x9f"=>0,
"\xa0"=>0, "\xa1"=>0, "\xa2"=>0, "\xa3"=>0, "\xa4"=>0, "\xa5"=>0, "\xa6"=>0, "\xa7"=>0,
"\xa8"=>0, "\xa9"=>0, "\xaa"=>0, "\xab"=>0, "\xac"=>0, "\xad"=>0, "\xae"=>0, "\xaf"=>0,
"\xb0"=>0, "\xb1"=>0, "\xb2"=>0, "\xb3"=>0, "\xb4"=>0, "\xb5"=>0, "\xb6"=>0, "\xb7"=>0,
"\xb8"=>0, "\xb9"=>0, "\xba"=>0, "\xbb"=>0, "\xbc"=>0, "\xbd"=>0, "\xbe"=>0, "\xbf"=>0,
"\xc0"=>2, "\xc1"=>2, "\xc2"=>2, "\xc3"=>2, "\xc4"=>2, "\xc5"=>2, "\xc6"=>2, "\xc7"=>2,
"\xc8"=>2, "\xc9"=>2, "\xca"=>2, "\xcb"=>2, "\xcc"=>2, "\xcd"=>2, "\xce"=>2, "\xcf"=>2,
"\xd0"=>2, "\xd1"=>2, "\xd2"=>2, "\xd3"=>2, "\xd4"=>2, "\xd5"=>2, "\xd6"=>2, "\xd7"=>2,
"\xd8"=>2, "\xd9"=>2, "\xda"=>2, "\xdb"=>2, "\xdc"=>2, "\xdd"=>2, "\xde"=>2, "\xdf"=>2,
"\xe0"=>3, "\xe1"=>3, "\xe2"=>3, "\xe3"=>3, "\xe4"=>3, "\xe5"=>3, "\xe6"=>3, "\xe7"=>3,
"\xe8"=>3, "\xe9"=>3, "\xea"=>3, "\xeb"=>3, "\xec"=>3, "\xed"=>3, "\xee"=>3, "\xef"=>3,
"\xf0"=>4, "\xf1"=>4, "\xf2"=>4, "\xf3"=>4, "\xf4"=>4, "\xf5"=>4, "\xf6"=>4, "\xf7"=>4,
"\xf8"=>5, "\xf9"=>5, "\xfa"=>5, "\xfb"=>5, "\xfc"=>6, "\xfd"=>6, "\xfe"=>0, "\xff"=>0,
);
/**
* Returns the complete charactermap
*
* @param string $string
* @param int $startOffset
* @param array $currentMap
* @param mixed $ignoredChars
*/
public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
{
if (!isset($currentMap['i']) || !isset($currentMap['p']))
{
$currentMap['p'] = $currentMap['i'] = array();
}
$strlen=strlen($string);
$charPos=count($currentMap['p']);
$foundChars=0;
$invalid=false;
for ($i=0; $i<$strlen; ++$i)
{
$char=$string[$i];
$size=self::$s_length_map[$char];
if ($size==0)
{
/* char is invalid, we must wait for a resync */
$invalid=true;
continue;
}
else
{
if ($invalid==true)
{
/* We mark the chars as invalid and start a new char */
$currentMap['p'][$charPos+$foundChars]=$startOffset+$i;
$currentMap['i'][$charPos+$foundChars]=true;
++$foundChars;
$invalid=false;
}
if (($i+$size) > $strlen){
$ignoredChars=substr($string, $i);
break;
}
for ($j=1; $j<$size; ++$j)
{
$char=$string[$i+$j];
if ($char>"\x7F" && $char<"\xC0")
{
// Valid - continue parsing
}
else
{
/* char is invalid, we must wait for a resync */
$invalid=true;
continue 2;
}
}
/* Ok we got a complete char here */
$lastChar=$currentMap['p'][$charPos+$foundChars]=$startOffset+$i+$size;
$i+=$j-1;
++$foundChars;
}
}
return $foundChars;
}
/**
* Returns mapType
* @int mapType
*/
public function getMapType()
{
return self::MAP_TYPE_POSITIONS;
}
/**
* Returns an integer which specifies how many more bytes to read.
* A positive integer indicates the number of more bytes to fetch before invoking
* this method again.
* A value of zero means this is already a valid character.
* A value of -1 means this cannot possibly be a valid character.
* @param string $bytes
* @return int
*/
public function validateByteSequence($bytes, $size)
{
if ($size<1){
return -1;
}
$needed = self::$length_map[$bytes[0]] - $size;
return ($needed > -1)
? $needed
: -1
;
}
/**
* Returns the number of bytes which should be read to start each character.
* @return int
*/
public function getInitialByteSize()
{
return 1;
}
}

View File

@ -0,0 +1,29 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/CharacterReader.php';
/**
* A factory for creating CharacterReaders.
* @package Swift
* @subpackage Encoder
* @author Chris Corbyn
*/
interface Swift_CharacterReaderFactory
{
/**
* Returns a CharacterReader suitable for the charset applied.
* @param string $charset
* @return Swift_CharacterReader
*/
public function getReaderFor($charset);
}

View File

@ -0,0 +1,119 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/CharacterReaderFactory.php';
/**
* Standard factory for creating CharacterReaders.
* @package Swift
* @subpackage Encoder
* @author Chris Corbyn
*/
class Swift_CharacterReaderFactory_SimpleCharacterReaderFactory
implements Swift_CharacterReaderFactory
{
/**
* A map of charset patterns to their implementation classes.
* @var array
* @access private
*/
private $_map = array();
/**
* Factories which have already been loaded.
* @var Swift_CharacterReaderFactory[]
* @access private
*/
private $_loaded = array();
/**
* Creates a new CharacterReaderFactory.
*/
public function __construct()
{
$prefix = 'Swift_CharacterReader_';
$singleByte = array(
'class' => $prefix . 'GenericFixedWidthReader',
'constructor' => array(1)
);
$doubleByte = array(
'class' => $prefix . 'GenericFixedWidthReader',
'constructor' => array(2)
);
$fourBytes = array(
'class' => $prefix . 'GenericFixedWidthReader',
'constructor' => array(4)
);
//Utf-8
$this->_map['utf-?8'] = array(
'class' => $prefix . 'Utf8Reader',
'constructor' => array()
);
//7-8 bit charsets
$this->_map['(us-)?ascii'] = $singleByte;
$this->_map['(iso|iec)-?8859-?[0-9]+'] = $singleByte;
$this->_map['windows-?125[0-9]'] = $singleByte;
$this->_map['cp-?[0-9]+'] = $singleByte;
$this->_map['ansi'] = $singleByte;
$this->_map['macintosh'] = $singleByte;
$this->_map['koi-?7'] = $singleByte;
$this->_map['koi-?8-?.+'] = $singleByte;
$this->_map['mik'] = $singleByte;
$this->_map['(cork|t1)'] = $singleByte;
$this->_map['v?iscii'] = $singleByte;
//16 bits
$this->_map['(ucs-?2|utf-?16)'] = $doubleByte;
//32 bits
$this->_map['(ucs-?4|utf-?32)'] = $fourBytes;
//Fallback
$this->_map['.*'] = $singleByte;
}
/**
* Returns a CharacterReader suitable for the charset applied.
* @param string $charset
* @return Swift_CharacterReader
*/
public function getReaderFor($charset)
{
$charset = trim(strtolower($charset));
foreach ($this->_map as $pattern => $spec)
{
$re = '/^' . $pattern . '$/D';
if (preg_match($re, $charset))
{
if (!array_key_exists($pattern, $this->_loaded))
{
$reflector = new ReflectionClass($spec['class']);
if ($reflector->getConstructor())
{
$reader = $reflector->newInstanceArgs($spec['constructor']);
}
else
{
$reader = $reflector->newInstance();
}
$this->_loaded[$pattern] = $reader;
}
return $this->_loaded[$pattern];
}
}
}
}

View File

@ -0,0 +1,86 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require_once dirname(__FILE__) . '/OutputByteStream.php';
require_once dirname(__FILE__) . '/CharacterReaderFactory.php';
/**
* An abstract means of reading and writing data in terms of characters as opposed
* to bytes.
* Classes implementing this interface may use a subsystem which requires less
* memory than working with large strings of data.
* @package Swift
* @subpackage CharacterStream
* @author Chris Corbyn
*/
interface Swift_CharacterStream
{
/**
* Set the character set used in this CharacterStream.
* @param string $charset
*/
public function setCharacterSet($charset);
/**
* Set the CharacterReaderFactory for multi charset support.
* @param Swift_CharacterReaderFactory $factory
*/
public function setCharacterReaderFactory(
Swift_CharacterReaderFactory $factory);
/**
* Overwrite this character stream using the byte sequence in the byte stream.
* @param Swift_OutputByteStream $os output stream to read from
*/
public function importByteStream(Swift_OutputByteStream $os);
/**
* Import a string a bytes into this CharacterStream, overwriting any existing
* data in the stream.
* @param string $string
*/
public function importString($string);
/**
* Read $length characters from the stream and move the internal pointer
* $length further into the stream.
* @param int $length
* @return string
*/
public function read($length);
/**
* Read $length characters from the stream and return a 1-dimensional array
* containing there octet values.
* @param int $length
* @return int[]
*/
public function readBytes($length);
/**
* Write $chars to the end of the stream.
* @param string $chars
*/
public function write($chars);
/**
* Move the internal pointer to $charOffset in the stream.
* @param int $charOffset
*/
public function setPointer($charOffset);
/**
* Empty the stream and reset the internal pointer.
*/
public function flushContents();
}

View File

@ -0,0 +1,319 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/CharacterStream.php';
//@require 'Swift/OutputByteStream.php';
/**
* A CharacterStream implementation which stores characters in an internal array.
* @package Swift
* @subpackage CharacterStream
* @author Chris Corbyn
*/
class Swift_CharacterStream_ArrayCharacterStream
implements Swift_CharacterStream
{
/** A map of byte values and their respective characters */
private static $_charMap;
/** A map of characters and their derivative byte values */
private static $_byteMap;
/** The char reader (lazy-loaded) for the current charset */
private $_charReader;
/** A factory for creatiing CharacterReader instances */
private $_charReaderFactory;
/** The character set this stream is using */
private $_charset;
/** Array of characters */
private $_array = array();
/** Size of the array of character */
private $_array_size = array();
/** The current character offset in the stream */
private $_offset = 0;
/**
* Create a new CharacterStream with the given $chars, if set.
* @param Swift_CharacterReaderFactory $factory for loading validators
* @param string $charset used in the stream
*/
public function __construct(Swift_CharacterReaderFactory $factory,
$charset)
{
self::_initializeMaps();
$this->setCharacterReaderFactory($factory);
$this->setCharacterSet($charset);
}
/**
* Set the character set used in this CharacterStream.
* @param string $charset
*/
public function setCharacterSet($charset)
{
$this->_charset = $charset;
$this->_charReader = null;
}
/**
* Set the CharacterReaderFactory for multi charset support.
* @param Swift_CharacterReaderFactory $factory
*/
public function setCharacterReaderFactory(
Swift_CharacterReaderFactory $factory)
{
$this->_charReaderFactory = $factory;
}
/**
* Overwrite this character stream using the byte sequence in the byte stream.
* @param Swift_OutputByteStream $os output stream to read from
*/
public function importByteStream(Swift_OutputByteStream $os)
{
if (!isset($this->_charReader))
{
$this->_charReader = $this->_charReaderFactory
->getReaderFor($this->_charset);
}
$startLength = $this->_charReader->getInitialByteSize();
while (false !== $bytes = $os->read($startLength))
{
$c = array();
for ($i = 0, $len = strlen($bytes); $i < $len; ++$i)
{
$c[] = self::$_byteMap[$bytes[$i]];
}
$size = count($c);
$need = $this->_charReader
->validateByteSequence($c, $size);
if ($need > 0 &&
false !== $bytes = $os->read($need))
{
for ($i = 0, $len = strlen($bytes); $i < $len; ++$i)
{
$c[] = self::$_byteMap[$bytes[$i]];
}
}
$this->_array[] = $c;
++$this->_array_size;
}
}
/**
* Import a string a bytes into this CharacterStream, overwriting any existing
* data in the stream.
* @param string $string
*/
public function importString($string)
{
$this->flushContents();
$this->write($string);
}
/**
* Read $length characters from the stream and move the internal pointer
* $length further into the stream.
* @param int $length
* @return string
*/
public function read($length)
{
if ($this->_offset == $this->_array_size)
{
return false;
}
// Don't use array slice
$arrays = array();
$end = $length + $this->_offset;
for ($i = $this->_offset; $i < $end; ++$i)
{
if (!isset($this->_array[$i]))
{
break;
}
$arrays[] = $this->_array[$i];
}
$this->_offset += $i - $this->_offset; // Limit function calls
$chars = false;
foreach ($arrays as $array)
{
$chars .= implode('', array_map('chr', $array));
}
return $chars;
}
/**
* Read $length characters from the stream and return a 1-dimensional array
* containing there octet values.
* @param int $length
* @return int[]
*/
public function readBytes($length)
{
if ($this->_offset == $this->_array_size)
{
return false;
}
$arrays = array();
$end = $length + $this->_offset;
for ($i = $this->_offset; $i < $end; ++$i)
{
if (!isset($this->_array[$i]))
{
break;
}
$arrays[] = $this->_array[$i];
}
$this->_offset += ($i - $this->_offset); // Limit function calls
return call_user_func_array('array_merge', $arrays);
}
/**
* Write $chars to the end of the stream.
* @param string $chars
*/
public function write($chars)
{
if (!isset($this->_charReader))
{
$this->_charReader = $this->_charReaderFactory->getReaderFor(
$this->_charset);
}
$startLength = $this->_charReader->getInitialByteSize();
$fp = fopen('php://memory', 'w+b');
fwrite($fp, $chars);
unset($chars);
fseek($fp, 0, SEEK_SET);
$buffer = array(0);
$buf_pos = 1;
$buf_len = 1;
$has_datas = true;
do
{
$bytes = array();
// Buffer Filing
if ($buf_len - $buf_pos < $startLength)
{
$buf = array_splice($buffer, $buf_pos);
$new = $this->_reloadBuffer($fp, 100);
if ($new)
{
$buffer = array_merge($buf, $new);
$buf_len = count($buffer);
$buf_pos = 0;
}
else
{
$has_datas = false;
}
}
if ($buf_len - $buf_pos > 0)
{
$size = 0;
for ($i = 0; $i < $startLength && isset($buffer[$buf_pos]); ++$i)
{
++$size;
$bytes[] = $buffer[$buf_pos++];
}
$need = $this->_charReader->validateByteSequence(
$bytes, $size);
if ($need > 0)
{
if ($buf_len - $buf_pos < $need)
{
$new = $this->_reloadBuffer($fp, $need);
if ($new)
{
$buffer = array_merge($buffer, $new);
$buf_len = count($buffer);
}
}
for ($i = 0; $i < $need && isset($buffer[$buf_pos]); ++$i)
{
$bytes[] = $buffer[$buf_pos++];
}
}
$this->_array[] = $bytes;
++$this->_array_size;
}
}
while ($has_datas);
fclose($fp);
}
/**
* Move the internal pointer to $charOffset in the stream.
* @param int $charOffset
*/
public function setPointer($charOffset)
{
if ($charOffset > $this->_array_size)
{
$charOffset = $this->_array_size;
}
elseif ($charOffset < 0)
{
$charOffset = 0;
}
$this->_offset = $charOffset;
}
/**
* Empty the stream and reset the internal pointer.
*/
public function flushContents()
{
$this->_offset = 0;
$this->_array = array();
$this->_array_size = 0;
}
private function _reloadBuffer($fp, $len)
{
if (!feof($fp) && ($bytes = fread($fp, $len)) !== false)
{
$buf = array();
for ($i = 0, $len = strlen($bytes); $i < $len; ++$i)
{
$buf[] = self::$_byteMap[$bytes[$i]];
}
return $buf;
}
return false;
}
private static function _initializeMaps()
{
if (!isset(self::$_charMap))
{
self::$_charMap = array();
for ($byte = 0; $byte < 256; ++$byte)
{
self::$_charMap[$byte] = chr($byte);
}
self::$_byteMap = array_flip(self::$_charMap);
}
}
}

View File

@ -0,0 +1,300 @@
<?php
/*
CharacterStream implementation using an array in Swift Mailer.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//@require 'Swift/CharacterStream.php';
//@require 'Swift/OutputByteStream.php';
/**
* A CharacterStream implementation which stores characters in an internal array.
* @package Swift
* @subpackage CharacterStream
* @author Xavier De Cock <xdecock@gmail.com>
*/
Class Swift_CharacterStream_NgCharacterStream
implements Swift_CharacterStream
{
/**
* The char reader (lazy-loaded) for the current charset.
* @var Swift_CharacterReader
* @access private
*/
private $_charReader;
/**
* A factory for creatiing CharacterReader instances.
* @var Swift_CharacterReaderFactory
* @access private
*/
private $_charReaderFactory;
/**
* The character set this stream is using.
* @var string
* @access private
*/
private $_charset;
/**
* The datas stored as is
*
* @var string
*/
private $_datas = "";
/**
* Number of bytes in the stream
*
* @var int
*/
private $_datasSize = 0;
/**
* Map
*
* @var mixed
*/
private $_map;
/**
* Map Type
*
* @var int
*/
private $_mapType = 0;
/**
* Number of characters in the stream
*
* @var int
*/
private $_charCount = 0;
/**
* Position in the stream
*
* @var unknown_type
*/
private $_currentPos = 0;
/**
* The constructor
*
* @param Swift_CharacterReaderFactory $factory
* @param unknown_type $charset
*/
public function __construct(Swift_CharacterReaderFactory $factory,
$charset)
{
$this->setCharacterReaderFactory($factory);
$this->setCharacterSet($charset);
}
/* -- Changing parameters of the stream -- */
/**
* Set the character set used in this CharacterStream.
* @param string $charset
*/
public function setCharacterSet($charset)
{
$this->_charset = $charset;
$this->_charReader = null;
$this->_mapType = 0;
}
/**
* Set the CharacterReaderFactory for multi charset support.
* @param Swift_CharacterReaderFactory $factory
*/
public function setCharacterReaderFactory(
Swift_CharacterReaderFactory $factory)
{
$this->_charReaderFactory = $factory;
}
/**
* @see Swift_CharacterStream::flushContents()
*
*/
public function flushContents()
{
$this->_datas = null;
$this->_map = null;
$this->_charCount = 0;
$this->_currentPos = 0;
$this->_datasSize = 0;
}
/**
* @see Swift_CharacterStream::importByteStream()
*
* @param Swift_OutputByteStream $os
*/
public function importByteStream(Swift_OutputByteStream $os)
{
$this->flushContents();
$blocks=512;
$os->setReadPointer(0);
while(false!==($read = $os->read($blocks)))
$this->write($read);
}
/**
* @see Swift_CharacterStream::importString()
*
* @param string $string
*/
public function importString($string)
{
$this->flushContents();
$this->write($string);
}
/**
* @see Swift_CharacterStream::read()
*
* @param int $length
* @return string
*/
public function read($length)
{
if ($this->_currentPos>=$this->_charCount)
{
return false;
}
$ret=false;
$length = ($this->_currentPos+$length > $this->_charCount)
? $this->_charCount - $this->_currentPos
: $length;
switch ($this->_mapType)
{
case Swift_CharacterReader::MAP_TYPE_FIXED_LEN:
$len = $length*$this->_map;
$ret = substr($this->_datas,
$this->_currentPos * $this->_map,
$len);
$this->_currentPos += $length;
break;
case Swift_CharacterReader::MAP_TYPE_INVALID:
$end = $this->_currentPos + $length;
$end = $end > $this->_charCount
?$this->_charCount
:$end;
$ret = '';
for (; $this->_currentPos < $length; ++$this->_currentPos)
{
if (isset ($this->_map[$this->_currentPos]))
{
$ret .= '?';
}
else
{
$ret .= $this->_datas[$this->_currentPos];
}
}
break;
case Swift_CharacterReader::MAP_TYPE_POSITIONS:
$end = $this->_currentPos + $length;
$end = $end > $this->_charCount
?$this->_charCount
:$end;
$ret = '';
$start = 0;
if ($this->_currentPos>0)
{
$start = $this->_map['p'][$this->_currentPos-1];
}
$to = $start;
for (; $this->_currentPos < $end; ++$this->_currentPos)
{
if (isset($this->_map['i'][$this->_currentPos])) {
$ret .= substr($this->_datas, $start, $to - $start).'?';
$start = $this->_map['p'][$this->_currentPos];
} else {
$to = $this->_map['p'][$this->_currentPos];
}
}
$ret .= substr($this->_datas, $start, $to - $start);
break;
}
return $ret;
}
/**
* @see Swift_CharacterStream::readBytes()
*
* @param int $length
* @return int[]
*/
public function readBytes($length)
{
$read=$this->read($length);
if ($read!==false)
{
$ret = array_map('ord', str_split($read, 1));
return $ret;
}
return false;
}
/**
* @see Swift_CharacterStream::setPointer()
*
* @param int $charOffset
*/
public function setPointer($charOffset)
{
if ($this->_charCount<$charOffset){
$charOffset=$this->_charCount;
}
$this->_currentPos = $charOffset;
}
/**
* @see Swift_CharacterStream::write()
*
* @param string $chars
*/
public function write($chars)
{
if (!isset($this->_charReader))
{
$this->_charReader = $this->_charReaderFactory->getReaderFor(
$this->_charset);
$this->_map = array();
$this->_mapType = $this->_charReader->getMapType();
}
$ignored='';
$this->_datas .= $chars;
$this->_charCount += $this->_charReader->getCharPositions(substr($this->_datas, $this->_datasSize), $this->_datasSize, $this->_map, $ignored);
if ($ignored!==false) {
$this->_datasSize=strlen($this->_datas)-strlen($ignored);
}
else
{
$this->_datasSize=strlen($this->_datas);
}
}
}

View File

@ -0,0 +1,349 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/DependencyException.php';
/**
* Dependency Injection container.
* @package Swift
* @author Chris Corbyn
*/
class Swift_DependencyContainer
{
/** Constant for literal value types */
const TYPE_VALUE = 0x0001;
/** Constant for new instance types */
const TYPE_INSTANCE = 0x0010;
/** Constant for shared instance types */
const TYPE_SHARED = 0x0100;
/** Constant for aliases */
const TYPE_ALIAS = 0x1000;
/** Singleton instance */
private static $_instance = null;
/** The data container */
private $_store = array();
/** The current endpoint in the data container */
private $_endPoint;
/**
* Constructor should not be used.
* Use {@link getInstance()} instead.
*/
public function __construct() { }
/**
* Returns a singleton of the DependencyContainer.
* @return Swift_DependencyContainer
*/
public static function getInstance()
{
if (!isset(self::$_instance))
{
self::$_instance = new self();
}
return self::$_instance;
}
/**
* List the names of all items stored in the Container.
* @return array
*/
public function listItems()
{
return array_keys($this->_store);
}
/**
* Test if an item is registered in this container with the given name.
* @param string $itemName
* @return boolean
* @see register()
*/
public function has($itemName)
{
return array_key_exists($itemName, $this->_store)
&& isset($this->_store[$itemName]['lookupType']);
}
/**
* Lookup the item with the given $itemName.
* @param string $itemName
* @return mixed
* @throws Swift_DependencyException If the dependency is not found
* @see register()
*/
public function lookup($itemName)
{
if (!$this->has($itemName))
{
throw new Swift_DependencyException(
'Cannot lookup dependency "' . $itemName . '" since it is not registered.'
);
}
switch ($this->_store[$itemName]['lookupType'])
{
case self::TYPE_ALIAS:
return $this->_createAlias($itemName);
case self::TYPE_VALUE:
return $this->_getValue($itemName);
case self::TYPE_INSTANCE:
return $this->_createNewInstance($itemName);
case self::TYPE_SHARED:
return $this->_createSharedInstance($itemName);
}
}
/**
* Create an array of arguments passed to the constructor of $itemName.
* @param string $itemName
* @return array
*/
public function createDependenciesFor($itemName)
{
$args = array();
if (isset($this->_store[$itemName]['args']))
{
$args = $this->_resolveArgs($this->_store[$itemName]['args']);
}
return $args;
}
/**
* Register a new dependency with $itemName.
* This method returns the current DependencyContainer instance because it
* requires the use of the fluid interface to set the specific details for the
* dependency.
*
* @param string $itemName
* @return Swift_DependencyContainer
* @see asNewInstanceOf(), asSharedInstanceOf(), asValue()
*/
public function register($itemName)
{
$this->_store[$itemName] = array();
$this->_endPoint =& $this->_store[$itemName];
return $this;
}
/**
* Specify the previously registered item as a literal value.
* {@link register()} must be called before this will work.
*
* @param mixed $value
* @return Swift_DependencyContainer
*/
public function asValue($value)
{
$endPoint =& $this->_getEndPoint();
$endPoint['lookupType'] = self::TYPE_VALUE;
$endPoint['value'] = $value;
return $this;
}
/**
* Specify the previously registered item as an alias of another item.
* @param string $lookup
* @return Swift_DependencyContainer
*/
public function asAliasOf($lookup)
{
$endPoint =& $this->_getEndPoint();
$endPoint['lookupType'] = self::TYPE_ALIAS;
$endPoint['ref'] = $lookup;
return $this;
}
/**
* Specify the previously registered item as a new instance of $className.
* {@link register()} must be called before this will work.
* Any arguments can be set with {@link withDependencies()},
* {@link addConstructorValue()} or {@link addConstructorLookup()}.
*
* @param string $className
* @return Swift_DependencyContainer
* @see withDependencies(), addConstructorValue(), addConstructorLookup()
*/
public function asNewInstanceOf($className)
{
$endPoint =& $this->_getEndPoint();
$endPoint['lookupType'] = self::TYPE_INSTANCE;
$endPoint['className'] = $className;
return $this;
}
/**
* Specify the previously registered item as a shared instance of $className.
* {@link register()} must be called before this will work.
* @param string $className
* @return Swift_DependencyContainer
*/
public function asSharedInstanceOf($className)
{
$endPoint =& $this->_getEndPoint();
$endPoint['lookupType'] = self::TYPE_SHARED;
$endPoint['className'] = $className;
return $this;
}
/**
* Specify a list of injected dependencies for the previously registered item.
* This method takes an array of lookup names.
*
* @param array $lookups
* @return Swift_DependencyContainer
* @see addConstructorValue(), addConstructorLookup()
*/
public function withDependencies(array $lookups)
{
$endPoint =& $this->_getEndPoint();
$endPoint['args'] = array();
foreach ($lookups as $lookup)
{
$this->addConstructorLookup($lookup);
}
return $this;
}
/**
* Specify a literal (non looked up) value for the constructor of the
* previously registered item.
*
* @param mixed $value
* @return Swift_DependencyContainer
* @see withDependencies(), addConstructorLookup()
*/
public function addConstructorValue($value)
{
$endPoint =& $this->_getEndPoint();
if (!isset($endPoint['args']))
{
$endPoint['args'] = array();
}
$endPoint['args'][] = array('type' => 'value', 'item' => $value);
return $this;
}
/**
* Specify a dependency lookup for the constructor of the previously
* registered item.
*
* @param string $lookup
* @return Swift_DependencyContainer
* @see withDependencies(), addConstructorValue()
*/
public function addConstructorLookup($lookup)
{
$endPoint =& $this->_getEndPoint();
if (!isset($this->_endPoint['args']))
{
$endPoint['args'] = array();
}
$endPoint['args'][] = array('type' => 'lookup', 'item' => $lookup);
return $this;
}
// -- Private methods
/** Get the literal value with $itemName */
private function _getValue($itemName)
{
return $this->_store[$itemName]['value'];
}
/** Resolve an alias to another item */
private function _createAlias($itemName)
{
return $this->lookup($this->_store[$itemName]['ref']);
}
/** Create a fresh instance of $itemName */
private function _createNewInstance($itemName)
{
$reflector = new ReflectionClass($this->_store[$itemName]['className']);
if ($reflector->getConstructor())
{
return $reflector->newInstanceArgs(
$this->createDependenciesFor($itemName)
);
}
else
{
return $reflector->newInstance();
}
}
/** Create and register a shared instance of $itemName */
private function _createSharedInstance($itemName)
{
if (!isset($this->_store[$itemName]['instance']))
{
$this->_store[$itemName]['instance'] = $this->_createNewInstance($itemName);
}
return $this->_store[$itemName]['instance'];
}
/** Get the current endpoint in the store */
private function &_getEndPoint()
{
if (!isset($this->_endPoint))
{
throw new BadMethodCallException(
'Component must first be registered by calling register()'
);
}
return $this->_endPoint;
}
/** Get an argument list with dependencies resolved */
private function _resolveArgs(array $args)
{
$resolved = array();
foreach ($args as $argDefinition)
{
switch ($argDefinition['type'])
{
case 'lookup':
$resolved[] = $this->_lookupRecursive($argDefinition['item']);
break;
case 'value':
$resolved[] = $argDefinition['item'];
break;
}
}
return $resolved;
}
/** Resolve a single dependency with an collections */
private function _lookupRecursive($item)
{
if (is_array($item))
{
$collection = array();
foreach ($item as $k => $v)
{
$collection[$k] = $this->_lookupRecursive($v);
}
return $collection;
}
else
{
return $this->lookup($item);
}
}
}

View File

@ -0,0 +1,30 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/SwiftException.php';
/**
* DependencyException thrown when a requested dependeny is missing.
* @package Swift
* @author Chris Corbyn
*/
class Swift_DependencyException extends Swift_SwiftException
{
/**
* Create a new DependencyException with $message.
* @param string $message
*/
public function __construct($message)
{
parent::__construct($message);
}
}

View File

@ -0,0 +1,73 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/Mime/Attachment.php';
//@require 'Swift/DependencyContainer.php';
//@require 'Swift/ByteStream/FileByteStream.php';
/**
* An embedded file, in a multipart message.
* @package Swift
* @subpackage Mime
* @author Chris Corbyn
*/
class Swift_EmbeddedFile extends Swift_Mime_EmbeddedFile
{
/**
* Create a new EmbeddedFile.
* Details may be optionally provided to the constructor.
* @param string|Swift_OutputByteStream $data
* @param string $filename
* @param string $contentType
*/
public function __construct($data = null, $filename = null,
$contentType = null)
{
call_user_func_array(
array($this, 'Swift_Mime_EmbeddedFile::__construct'),
Swift_DependencyContainer::getInstance()
->createDependenciesFor('mime.embeddedfile')
);
$this->setBody($data);
$this->setFilename($filename);
if ($contentType)
{
$this->setContentType($contentType);
}
}
/**
* Create a new EmbeddedFile.
* @param string|Swift_OutputByteStream $data
* @param string $filename
* @param string $contentType
* @return Swift_Mime_EmbeddedFile
*/
public static function newInstance($data = null, $filename = null,
$contentType = null)
{
return new self($data, $filename, $contentType);
}
/**
* Create a new EmbeddedFile from a filesystem path.
* @param string $path
* @return Swift_Mime_EmbeddedFile
*/
public static function fromPath($path)
{
return self::newInstance()->setFile(
new Swift_ByteStream_FileByteStream($path)
);
}
}

View File

@ -0,0 +1,32 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/Mime/CharsetObserver.php';
/**
* Interface for all Encoder schemes.
* @package Swift
* @subpackage Encoder
* @author Chris Corbyn
*/
interface Swift_Encoder extends Swift_Mime_CharsetObserver
{
/**
* Encode a given string to produce an encoded string.
* @param string $string
* @param int $firstLineOffset if first line needs to be shorter
* @param int $maxLineLength - 0 indicates the default length for this encoding
* @return string
*/
public function encodeString($string, $firstLineOffset = 0,
$maxLineLength = 0);
}

View File

@ -0,0 +1,63 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/Encoder.php';
/**
* Handles Base 64 Encoding in Swift Mailer.
* @package Swift
* @subpackage Encoder
* @author Chris Corbyn
*/
class Swift_Encoder_Base64Encoder implements Swift_Encoder
{
/**
* Takes an unencoded string and produces a Base64 encoded string from it.
* Base64 encoded strings have a maximum line length of 76 characters.
* If the first line needs to be shorter, indicate the difference with
* $firstLineOffset.
* @param string $string to encode
* @param int $firstLineOffset
* @param int $maxLineLength, optional, 0 indicates the default of 76 bytes
* @return string
*/
public function encodeString($string, $firstLineOffset = 0,
$maxLineLength = 0)
{
if (0 >= $maxLineLength || 76 < $maxLineLength)
{
$maxLineLength = 76;
}
$encodedString = base64_encode($string);
$firstLine = '';
if (0 != $firstLineOffset)
{
$firstLine = substr(
$encodedString, 0, $maxLineLength - $firstLineOffset
) . "\r\n";
$encodedString = substr(
$encodedString, $maxLineLength - $firstLineOffset
);
}
return $firstLine . trim(chunk_split($encodedString, $maxLineLength, "\r\n"));
}
/**
* Does nothing.
*/
public function charsetChanged($charset)
{
}
}

View File

@ -0,0 +1,263 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/Encoder.php';
//@require 'Swift/CharacterStream.php';
/**
* Handles Quoted Printable (QP) Encoding in Swift Mailer.
* Possibly the most accurate RFC 2045 QP implementation found in PHP.
* @package Swift
* @subpackage Encoder
* @author Chris Corbyn
*/
class Swift_Encoder_QpEncoder implements Swift_Encoder
{
/**
* The CharacterStream used for reading characters (as opposed to bytes).
* @var Swift_CharacterStream
* @access protected
*/
protected $_charStream;
/**
* A filter used if input should be canonicalized.
* @var Swift_StreamFilter
* @access protected
*/
protected $_filter;
/**
* Pre-computed QP for HUGE optmization.
* @var string[]
* @access protected
*/
protected static $_qpMap = array(
0 => '=00', 1 => '=01', 2 => '=02', 3 => '=03', 4 => '=04',
5 => '=05', 6 => '=06', 7 => '=07', 8 => '=08', 9 => '=09',
10 => '=0A', 11 => '=0B', 12 => '=0C', 13 => '=0D', 14 => '=0E',
15 => '=0F', 16 => '=10', 17 => '=11', 18 => '=12', 19 => '=13',
20 => '=14', 21 => '=15', 22 => '=16', 23 => '=17', 24 => '=18',
25 => '=19', 26 => '=1A', 27 => '=1B', 28 => '=1C', 29 => '=1D',
30 => '=1E', 31 => '=1F', 32 => '=20', 33 => '=21', 34 => '=22',
35 => '=23', 36 => '=24', 37 => '=25', 38 => '=26', 39 => '=27',
40 => '=28', 41 => '=29', 42 => '=2A', 43 => '=2B', 44 => '=2C',
45 => '=2D', 46 => '=2E', 47 => '=2F', 48 => '=30', 49 => '=31',
50 => '=32', 51 => '=33', 52 => '=34', 53 => '=35', 54 => '=36',
55 => '=37', 56 => '=38', 57 => '=39', 58 => '=3A', 59 => '=3B',
60 => '=3C', 61 => '=3D', 62 => '=3E', 63 => '=3F', 64 => '=40',
65 => '=41', 66 => '=42', 67 => '=43', 68 => '=44', 69 => '=45',
70 => '=46', 71 => '=47', 72 => '=48', 73 => '=49', 74 => '=4A',
75 => '=4B', 76 => '=4C', 77 => '=4D', 78 => '=4E', 79 => '=4F',
80 => '=50', 81 => '=51', 82 => '=52', 83 => '=53', 84 => '=54',
85 => '=55', 86 => '=56', 87 => '=57', 88 => '=58', 89 => '=59',
90 => '=5A', 91 => '=5B', 92 => '=5C', 93 => '=5D', 94 => '=5E',
95 => '=5F', 96 => '=60', 97 => '=61', 98 => '=62', 99 => '=63',
100 => '=64', 101 => '=65', 102 => '=66', 103 => '=67', 104 => '=68',
105 => '=69', 106 => '=6A', 107 => '=6B', 108 => '=6C', 109 => '=6D',
110 => '=6E', 111 => '=6F', 112 => '=70', 113 => '=71', 114 => '=72',
115 => '=73', 116 => '=74', 117 => '=75', 118 => '=76', 119 => '=77',
120 => '=78', 121 => '=79', 122 => '=7A', 123 => '=7B', 124 => '=7C',
125 => '=7D', 126 => '=7E', 127 => '=7F', 128 => '=80', 129 => '=81',
130 => '=82', 131 => '=83', 132 => '=84', 133 => '=85', 134 => '=86',
135 => '=87', 136 => '=88', 137 => '=89', 138 => '=8A', 139 => '=8B',
140 => '=8C', 141 => '=8D', 142 => '=8E', 143 => '=8F', 144 => '=90',
145 => '=91', 146 => '=92', 147 => '=93', 148 => '=94', 149 => '=95',
150 => '=96', 151 => '=97', 152 => '=98', 153 => '=99', 154 => '=9A',
155 => '=9B', 156 => '=9C', 157 => '=9D', 158 => '=9E', 159 => '=9F',
160 => '=A0', 161 => '=A1', 162 => '=A2', 163 => '=A3', 164 => '=A4',
165 => '=A5', 166 => '=A6', 167 => '=A7', 168 => '=A8', 169 => '=A9',
170 => '=AA', 171 => '=AB', 172 => '=AC', 173 => '=AD', 174 => '=AE',
175 => '=AF', 176 => '=B0', 177 => '=B1', 178 => '=B2', 179 => '=B3',
180 => '=B4', 181 => '=B5', 182 => '=B6', 183 => '=B7', 184 => '=B8',
185 => '=B9', 186 => '=BA', 187 => '=BB', 188 => '=BC', 189 => '=BD',
190 => '=BE', 191 => '=BF', 192 => '=C0', 193 => '=C1', 194 => '=C2',
195 => '=C3', 196 => '=C4', 197 => '=C5', 198 => '=C6', 199 => '=C7',
200 => '=C8', 201 => '=C9', 202 => '=CA', 203 => '=CB', 204 => '=CC',
205 => '=CD', 206 => '=CE', 207 => '=CF', 208 => '=D0', 209 => '=D1',
210 => '=D2', 211 => '=D3', 212 => '=D4', 213 => '=D5', 214 => '=D6',
215 => '=D7', 216 => '=D8', 217 => '=D9', 218 => '=DA', 219 => '=DB',
220 => '=DC', 221 => '=DD', 222 => '=DE', 223 => '=DF', 224 => '=E0',
225 => '=E1', 226 => '=E2', 227 => '=E3', 228 => '=E4', 229 => '=E5',
230 => '=E6', 231 => '=E7', 232 => '=E8', 233 => '=E9', 234 => '=EA',
235 => '=EB', 236 => '=EC', 237 => '=ED', 238 => '=EE', 239 => '=EF',
240 => '=F0', 241 => '=F1', 242 => '=F2', 243 => '=F3', 244 => '=F4',
245 => '=F5', 246 => '=F6', 247 => '=F7', 248 => '=F8', 249 => '=F9',
250 => '=FA', 251 => '=FB', 252 => '=FC', 253 => '=FD', 254 => '=FE',
255 => '=FF'
);
/**
* A map of non-encoded ascii characters.
* @var string[]
* @access protected
*/
protected static $_safeMap = array();
/**
* Creates a new QpEncoder for the given CharacterStream.
* @param Swift_CharacterStream $charStream to use for reading characters
* @param Swift_StreamFilter $filter if input should be canonicalized
*/
public function __construct(Swift_CharacterStream $charStream,
Swift_StreamFilter $filter = null)
{
$this->_charStream = $charStream;
if (empty(self::$_safeMap))
{
foreach (array_merge(
array(0x09, 0x20), range(0x21, 0x3C), range(0x3E, 0x7E)) as $byte)
{
self::$_safeMap[$byte] = chr($byte);
}
}
$this->_filter = $filter;
}
/**
* Takes an unencoded string and produces a QP encoded string from it.
* QP encoded strings have a maximum line length of 76 characters.
* If the first line needs to be shorter, indicate the difference with
* $firstLineOffset.
* @param string $string to encode
* @param int $firstLineOffset, optional
* @param int $maxLineLength, optional, 0 indicates the default of 76 chars
* @return string
*/
public function encodeString($string, $firstLineOffset = 0,
$maxLineLength = 0)
{
if ($maxLineLength > 76 || $maxLineLength <= 0)
{
$maxLineLength = 76;
}
$thisLineLength = $maxLineLength - $firstLineOffset;
$lines = array();
$lNo = 0;
$lines[$lNo] = '';
$currentLine =& $lines[$lNo++];
$size=$lineLen=0;
$this->_charStream->flushContents();
$this->_charStream->importString($string);
//Fetching more than 4 chars at one is slower, as is fetching fewer bytes
// Conveniently 4 chars is the UTF-8 safe number since UTF-8 has up to 6
// bytes per char and (6 * 4 * 3 = 72 chars per line) * =NN is 3 bytes
while (false !== $bytes = $this->_nextSequence())
{
//If we're filtering the input
if (isset($this->_filter))
{
//If we can't filter because we need more bytes
while ($this->_filter->shouldBuffer($bytes))
{
//Then collect bytes into the buffer
if (false === $moreBytes = $this->_nextSequence(1))
{
break;
}
foreach ($moreBytes as $b)
{
$bytes[] = $b;
}
}
//And filter them
$bytes = $this->_filter->filter($bytes);
}
$enc = $this->_encodeByteSequence($bytes, $size);
if ($currentLine && $lineLen+$size >= $thisLineLength)
{
$lines[$lNo] = '';
$currentLine =& $lines[$lNo++];
$thisLineLength = $maxLineLength;
$lineLen=0;
}
$lineLen+=$size;
$currentLine .= $enc;
}
return $this->_standardize(implode("=\r\n", $lines));
}
/**
* Updates the charset used.
* @param string $charset
*/
public function charsetChanged($charset)
{
$this->_charStream->setCharacterSet($charset);
}
// -- Protected methods
/**
* Encode the given byte array into a verbatim QP form.
* @param int[] $bytes
* @return string
* @access protected
*/
protected function _encodeByteSequence(array $bytes, &$size)
{
$ret = '';
$size=0;
foreach ($bytes as $b)
{
if (isset(self::$_safeMap[$b]))
{
$ret .= self::$_safeMap[$b];
++$size;
}
else
{
$ret .= self::$_qpMap[$b];
$size+=3;
}
}
return $ret;
}
/**
* Get the next sequence of bytes to read from the char stream.
* @param int $size number of bytes to read
* @return int[]
* @access protected
*/
protected function _nextSequence($size = 4)
{
return $this->_charStream->readBytes($size);
}
/**
* Make sure CRLF is correct and HT/SPACE are in valid places.
* @param string $string
* @return string
* @access protected
*/
protected function _standardize($string)
{
$string = str_replace(array("\t=0D=0A", " =0D=0A", "=0D=0A"),
array("=09\r\n", "=20\r\n", "\r\n"), $string
);
switch ($end = ord(substr($string, -1)))
{
case 0x09:
case 0x20:
$string = substr_replace($string, self::$_qpMap[$end], -1);
}
return $string;
}
}

View File

@ -0,0 +1,89 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/Encoder.php';
//@require 'Swift/CharacterStream.php';
/**
* Handles RFC 2231 specified Encoding in Swift Mailer.
* @package Swift
* @subpackage Encoder
* @author Chris Corbyn
*/
class Swift_Encoder_Rfc2231Encoder implements Swift_Encoder
{
/**
* A character stream to use when reading a string as characters instead of bytes.
* @var Swift_CharacterStream
* @access private
*/
private $_charStream;
/**
* Creates a new Rfc2231Encoder using the given character stream instance.
* @param Swift_CharacterStream
*/
public function __construct(Swift_CharacterStream $charStream)
{
$this->_charStream = $charStream;
}
/**
* Takes an unencoded string and produces a string encoded according to
* RFC 2231 from it.
* @param string $string to encode
* @param int $firstLineOffset
* @param int $maxLineLength, optional, 0 indicates the default of 75 bytes
* @return string
*/
public function encodeString($string, $firstLineOffset = 0,
$maxLineLength = 0)
{
$lines = array(); $lineCount = 0;
$lines[] = '';
$currentLine =& $lines[$lineCount++];
if (0 >= $maxLineLength)
{
$maxLineLength = 75;
}
$this->_charStream->flushContents();
$this->_charStream->importString($string);
$thisLineLength = $maxLineLength - $firstLineOffset;
while (false !== $char = $this->_charStream->read(4))
{
$encodedChar = rawurlencode($char);
if (0 != strlen($currentLine)
&& strlen($currentLine . $encodedChar) > $thisLineLength)
{
$lines[] = '';
$currentLine =& $lines[$lineCount++];
$thisLineLength = $maxLineLength;
}
$currentLine .= $encodedChar;
}
return implode("\r\n", $lines);
}
/**
* Updates the charset used.
* @param string $charset
*/
public function charsetChanged($charset)
{
$this->_charStream->setCharacterSet($charset);
}
}

View File

@ -0,0 +1,70 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//@require 'Swift/DependencyContainer.php';
/**
* Provides quick access to each encoding type.
*
* @package Swift
* @subpackage Encoder
* @author Chris Corbyn
*/
class Swift_Encoding
{
/**
* Get the Encoder that provides 7-bit encoding.
*
* @return Swift_Mime_ContentEncoder
*/
public static function get7BitEncoding()
{
return self::_lookup('mime.7bitcontentencoder');
}
/**
* Get the Encoder that provides 8-bit encoding.
*
* @return Swift_Mime_ContentEncoder
*/
public static function get8BitEncoding()
{
return self::_lookup('mime.8bitcontentencoder');
}
/**
* Get the Encoder that provides Quoted-Printable (QP) encoding.
*
* @return Swift_Mime_ContentEncoder
*/
public static function getQpEncoding()
{
return self::_lookup('mime.qpcontentencoder');
}
/**
* Get the Encoder that provides Base64 encoding.
*
* @return Swift_Mime_ContentEncoder
*/
public static function getBase64Encoding()
{
return self::_lookup('mime.base64contentencoder');
}
// -- Private Static Methods
private static function _lookup($key)
{
return Swift_DependencyContainer::getInstance()->lookup($key);
}
}

Some files were not shown because too many files have changed in this diff Show More